diff --git a/scripts/create-user.ts b/scripts/create-user.ts index 35f5804..fac0221 100644 --- a/scripts/create-user.ts +++ b/scripts/create-user.ts @@ -6,9 +6,19 @@ import { users, companies, companyUsers } from '../src/lib/server/db/schema/tena import { hashPassword } 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 i >= 0 ? process.argv[i + 1] : fallback; + return stripSurroundingQuotes(i >= 0 ? process.argv[i + 1] : fallback); } async function main() { diff --git a/scripts/diag-user.ts b/scripts/diag-user.ts new file mode 100644 index 0000000..6daae10 --- /dev/null +++ b/scripts/diag-user.ts @@ -0,0 +1,98 @@ +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); +});