import 'dotenv/config'; import { eq } from 'drizzle-orm'; import { db, pool } from '../src/lib/server/db/client'; import { users, companyUsers, companies, sessions } from '../src/lib/server/db/schema/tenancy'; import { verifyPassword } from '../src/lib/server/auth/password'; import { normalizeEmail } from '../src/lib/utils/email'; function stripSurroundingQuotes(s: string | undefined): string | undefined { if (!s || s.length < 2) return s; const first = s[0]; const last = s[s.length - 1]; if ((first === "'" && last === "'") || (first === '"' && last === '"')) { return s.slice(1, -1); } return s; } function readArg(flag: string, fallback?: string): string | undefined { const i = process.argv.indexOf(flag); return stripSurroundingQuotes(i >= 0 ? process.argv[i + 1] : fallback); } async function main() { const emailArg = readArg('--email'); const passwordArg = readArg('--password'); if (!emailArg) { console.error('Usage: npx tsx scripts/diag-user.ts --email [--password ]'); process.exit(1); } const normalized = normalizeEmail(emailArg); console.log('Looking up:'); console.log(' input email :', JSON.stringify(emailArg)); console.log(' normalized lookup :', JSON.stringify(normalized)); const [u] = await db .select() .from(users) .where(eq(users.emailNormalized, normalized)) .limit(1); if (!u) { console.log('\n❌ NO USER FOUND at that normalized email.'); const all = await db .select({ email: users.email, en: users.emailNormalized }) .from(users); console.log(`\nAll ${all.length} users in DB:`); for (const row of all) console.log(` ${row.email} (normalized=${row.en})`); await pool.end(); return; } console.log('\n✅ User row:'); console.log(' id :', u.id); console.log(' email :', u.email); console.log(' emailNormalized :', u.emailNormalized); console.log(' displayName :', u.displayName); console.log(' isActive :', u.isActive); console.log(' passwordHash :', u.passwordHash ? `${u.passwordHash.slice(0, 20)}… (len=${u.passwordHash.length})` : 'NULL'); console.log(' oidcSubject :', u.oidcSubject ?? 'null'); console.log(' deletedAt :', u.deletedAt ?? 'null'); console.log(' lastLoginAt :', u.lastLoginAt ?? 'never'); const memberships = await db .select({ companyName: companies.name, role: companyUsers.role }) .from(companyUsers) .innerJoin(companies, eq(companies.id, companyUsers.companyId)) .where(eq(companyUsers.userId, u.id)); console.log('\nCompany memberships:'); if (memberships.length === 0) console.log(' (none)'); for (const m of memberships) console.log(` ${m.companyName} role=${m.role}`); const sessionCount = await db.select().from(sessions).where(eq(sessions.userId, u.id)); console.log('\nActive sessions:', sessionCount.length); if (passwordArg) { console.log('\nVerifying password…'); if (!u.passwordHash) { console.log(' ❌ user has no password hash'); } else { try { const ok = await verifyPassword(u.passwordHash, passwordArg); console.log(' result:', ok ? '✅ MATCH' : '❌ MISMATCH'); } catch (err) { console.log(' ❌ verify threw:', (err as Error).message); } } } await pool.end(); } main().catch((err) => { console.error(err); process.exit(1); });