Monorepo for Aesthetic.Computer aesthetic.computer
at main 302 lines 11 kB view raw
1#!/usr/bin/env node 2// audit-user-creation-sync.mjs 3// Comprehensive audit of user creation flow from Auth0 -> MongoDB -> ATProto 4// Checks: Auth0 signups, verifications collection, users collection, @handles collection, and ATProto accounts 5 6import { shell } from '../../system/backend/shell.mjs'; 7import { connect } from '../../system/backend/database.mjs'; 8import { userEmailFromID } from '../../system/backend/authorization.mjs'; 9import { config } from 'dotenv'; 10import { AtpAgent } from '@atproto/api'; 11 12config(); 13 14const PDS_URL = process.env.PDS_URL || 'https://at.aesthetic.computer'; 15 16// Get Auth0 access token for a tenant 17async function getAuth0Token(tenant = 'aesthetic') { 18 const { got } = await import('got'); 19 20 const clientId = tenant === 'aesthetic' 21 ? process.env.AUTH0_M2M_CLIENT_ID 22 : process.env.SOTCE_AUTH0_M2M_CLIENT_ID; 23 const clientSecret = tenant === 'aesthetic' 24 ? process.env.AUTH0_M2M_SECRET 25 : process.env.SOTCE_AUTH0_M2M_SECRET; 26 const baseURI = tenant === 'aesthetic' 27 ? 'https://aesthetic.us.auth0.com' 28 : 'https://sotce.us.auth0.com'; 29 30 const tokenResponse = await got.post(`${baseURI}/oauth/token`, { 31 json: { 32 client_id: clientId, 33 client_secret: clientSecret, 34 audience: `${baseURI}/api/v2/`, 35 grant_type: 'client_credentials', 36 }, 37 responseType: 'json', 38 }); 39 40 return { token: tokenResponse.body.access_token, baseURI }; 41} 42 43// Get recent Auth0 users 44async function getAuth0RecentUsers(tenant = 'aesthetic', limit = 10) { 45 try { 46 const { got } = await import('got'); 47 const { token, baseURI } = await getAuth0Token(tenant); 48 49 const response = await got(`${baseURI}/api/v2/users`, { 50 searchParams: { 51 sort: 'created_at:-1', 52 per_page: limit, 53 page: 0, 54 fields: 'user_id,email,email_verified,created_at', 55 include_fields: true, 56 }, 57 headers: { Authorization: `Bearer ${token}` }, 58 responseType: 'json', 59 }); 60 61 return response.body; 62 } catch (error) { 63 shell.error(`Error querying Auth0 ${tenant}: ${error.message}`); 64 return null; 65 } 66} 67 68// Check if ATProto account exists and is accessible 69async function checkAtprotoAccount(did, password) { 70 const agent = new AtpAgent({ service: PDS_URL }); 71 try { 72 await agent.login({ identifier: did, password }); 73 return { exists: true, accessible: true }; 74 } catch (error) { 75 if (error.message?.includes('not found') || error.message?.includes('Unknown')) { 76 return { exists: false, accessible: false, error: 'Account not found on PDS' }; 77 } 78 return { exists: true, accessible: false, error: error.message }; 79 } 80} 81 82// Main audit function 83async function auditUserCreationSync(tenant = 'aesthetic', limit = 10) { 84 console.log(`\n${'='.repeat(80)}`); 85 console.log(`🔍 AUDITING USER CREATION & ATPROTO SYNC - ${tenant.toUpperCase()} TENANT`); 86 console.log(`${'='.repeat(80)}\n`); 87 88 const database = await connect(); 89 const users = database.db.collection('users'); 90 const handles = database.db.collection('@handles'); 91 const verifications = database.db.collection('verifications'); 92 93 // 1. Get recent Auth0 signups 94 console.log('📋 STEP 1: Fetching recent Auth0 signups...\n'); 95 const auth0Users = await getAuth0RecentUsers(tenant, limit); 96 97 if (!auth0Users || auth0Users.length === 0) { 98 console.log('❌ No Auth0 users found or error fetching users\n'); 99 await database.disconnect(); 100 return; 101 } 102 103 console.log(`✅ Found ${auth0Users.length} recent Auth0 users\n`); 104 105 // 2. Check each user's sync status 106 const results = []; 107 108 for (const auth0User of auth0Users) { 109 const sub = auth0User.user_id; 110 const email = auth0User.email; 111 const emailVerified = auth0User.email_verified; 112 const createdAt = new Date(auth0User.created_at); 113 const hoursAgo = (Date.now() - createdAt.getTime()) / (1000 * 60 * 60); 114 115 console.log(`\n${'─'.repeat(80)}`); 116 console.log(`👤 User: ${sub}`); 117 console.log(` Email: ${email || 'N/A'}`); 118 console.log(` Email Verified: ${emailVerified ? '✅' : '❌'}`); 119 console.log(` Created: ${createdAt.toISOString()} (${hoursAgo.toFixed(1)}h ago)`); 120 121 const userStatus = { 122 sub, 123 email, 124 emailVerified, 125 createdAt, 126 hoursAgo: hoursAgo.toFixed(1), 127 verification: null, 128 userRecord: null, 129 handleRecord: null, 130 atproto: null, 131 issues: [], 132 }; 133 134 // Check verifications collection 135 const verification = await verifications.findOne({ _id: sub }); 136 if (verification) { 137 console.log(` ✅ Verifications record: count=${verification.count}`); 138 userStatus.verification = verification; 139 } else { 140 console.log(` ❌ No verifications record found`); 141 userStatus.issues.push('Missing verifications record'); 142 } 143 144 // Check users collection 145 const userRecord = await users.findOne({ _id: sub }); 146 if (userRecord) { 147 console.log(` ✅ Users record found:`); 148 console.log(` Code: ${userRecord.code || 'N/A'}`); 149 console.log(` Created: ${userRecord.when ? new Date(userRecord.when).toISOString() : 'N/A'}`); 150 151 if (userRecord.atproto) { 152 console.log(` ATProto:`); 153 console.log(` DID: ${userRecord.atproto.did || 'N/A'}`); 154 console.log(` Handle: ${userRecord.atproto.handle || 'N/A'}`); 155 console.log(` Created: ${userRecord.atproto.created || 'N/A'}`); 156 157 // Check if ATProto account actually exists on PDS 158 if (userRecord.atproto.did && userRecord.atproto.password) { 159 console.log(` 🔍 Checking PDS account...`); 160 const pdsCheck = await checkAtprotoAccount( 161 userRecord.atproto.did, 162 userRecord.atproto.password 163 ); 164 165 if (pdsCheck.accessible) { 166 console.log(` ✅ PDS account exists and is accessible`); 167 } else if (pdsCheck.exists) { 168 console.log(` ⚠️ PDS account exists but login failed: ${pdsCheck.error}`); 169 userStatus.issues.push(`ATProto login failed: ${pdsCheck.error}`); 170 } else { 171 console.log(` ❌ PDS account not found: ${pdsCheck.error}`); 172 userStatus.issues.push(`ATProto account missing on PDS`); 173 } 174 175 userStatus.atproto = { 176 ...userRecord.atproto, 177 pdsCheck, 178 }; 179 } else { 180 console.log(` ⚠️ Missing DID or password`); 181 userStatus.issues.push('ATProto credentials incomplete'); 182 } 183 } else if (emailVerified) { 184 console.log(` ⚠️ No ATProto data (but email is verified!)`); 185 userStatus.issues.push('ATProto account not created despite email verification'); 186 } else { 187 console.log(` ℹ️ No ATProto data (email not verified yet)`); 188 } 189 190 userStatus.userRecord = userRecord; 191 } else { 192 console.log(` ❌ No users record found`); 193 userStatus.issues.push('Missing users record'); 194 } 195 196 // Check @handles collection 197 const handleRecord = await handles.findOne({ _id: sub }); 198 if (handleRecord) { 199 console.log(` ✅ Handle record: @${handleRecord.handle}`); 200 userStatus.handleRecord = handleRecord; 201 } else { 202 console.log(` ℹ️ No handle set yet (optional)`); 203 } 204 205 // Summary for this user 206 if (userStatus.issues.length === 0) { 207 console.log(` ✅ ALL CHECKS PASSED`); 208 } else { 209 console.log(` ⚠️ ISSUES FOUND:`); 210 userStatus.issues.forEach(issue => console.log(` - ${issue}`)); 211 } 212 213 results.push(userStatus); 214 } 215 216 // 3. Generate summary report 217 console.log(`\n\n${'='.repeat(80)}`); 218 console.log(`📊 SUMMARY REPORT - ${tenant.toUpperCase()} TENANT`); 219 console.log(`${'='.repeat(80)}\n`); 220 221 const totalUsers = results.length; 222 const usersWithVerifications = results.filter(r => r.verification).length; 223 const usersWithUserRecord = results.filter(r => r.userRecord).length; 224 const usersWithHandle = results.filter(r => r.handleRecord).length; 225 const usersWithAtproto = results.filter(r => r.atproto).length; 226 const usersWithAtprotoOnPds = results.filter(r => r.atproto?.pdsCheck?.accessible).length; 227 const emailVerified = results.filter(r => r.emailVerified).length; 228 const usersWithIssues = results.filter(r => r.issues.length > 0).length; 229 230 console.log(`Total Users Checked: ${totalUsers}`); 231 console.log(`Email Verified: ${emailVerified}/${totalUsers}`); 232 console.log(`\nMongoDB Collections:`); 233 console.log(` Verifications: ${usersWithVerifications}/${totalUsers}`); 234 console.log(` Users: ${usersWithUserRecord}/${totalUsers}`); 235 console.log(` Handles: ${usersWithHandle}/${totalUsers}`); 236 console.log(`\nATProto Status:`); 237 console.log(` ATProto Data in Mongo: ${usersWithAtproto}/${totalUsers}`); 238 console.log(` ATProto Accounts on PDS: ${usersWithAtprotoOnPds}/${totalUsers}`); 239 console.log(`\nIssues:`); 240 console.log(` Users with Issues: ${usersWithIssues}/${totalUsers}`); 241 242 if (usersWithIssues > 0) { 243 console.log(`\n⚠️ DETAILED ISSUES:\n`); 244 results.filter(r => r.issues.length > 0).forEach(user => { 245 console.log(` ${user.sub}:`); 246 user.issues.forEach(issue => console.log(` - ${issue}`)); 247 }); 248 } 249 250 // 4. Check for orphaned records (in Mongo but not in Auth0) 251 console.log(`\n\n${'='.repeat(80)}`); 252 console.log(`🔍 CHECKING FOR ORPHANED RECORDS`); 253 console.log(`${'='.repeat(80)}\n`); 254 255 const allAuth0Subs = new Set(auth0Users.map(u => u.user_id)); 256 257 // Check recent user records 258 const recentUserRecords = await users.find({}) 259 .sort({ when: -1 }) 260 .limit(limit) 261 .toArray(); 262 263 const orphanedUsers = recentUserRecords.filter(u => !allAuth0Subs.has(u._id)); 264 if (orphanedUsers.length > 0) { 265 console.log(`⚠️ Found ${orphanedUsers.length} user records not in recent Auth0 users:`); 266 orphanedUsers.forEach(u => { 267 console.log(` - ${u._id} (created: ${u.when})`); 268 }); 269 } else { 270 console.log(`✅ No orphaned user records found in recent ${limit} records`); 271 } 272 273 await database.disconnect(); 274 275 console.log(`\n${'='.repeat(80)}`); 276 console.log(`✅ AUDIT COMPLETE`); 277 console.log(`${'='.repeat(80)}\n`); 278 279 return results; 280} 281 282// Run audit for both tenants 283const tenantArg = process.argv[2] || 'both'; 284const limitArg = parseInt(process.argv[3]) || 10; 285 286console.log(`\n⏰ Audit started at: ${new Date().toISOString()}\n`); 287 288try { 289 if (tenantArg === 'both' || tenantArg === 'aesthetic') { 290 await auditUserCreationSync('aesthetic', limitArg); 291 } 292 293 if (tenantArg === 'both' || tenantArg === 'sotce') { 294 console.log('\n\n'); 295 await auditUserCreationSync('sotce', limitArg); 296 } 297} catch (error) { 298 console.error('❌ Audit failed:', error); 299 process.exit(1); 300} 301 302console.log(`\n⏰ Audit completed at: ${new Date().toISOString()}\n`);