From 4ec2885b097f33e77f28b837b201ae182c7797a4 Mon Sep 17 00:00:00 2001 From: Barcode Betty Date: Sat, 23 May 2026 20:23:35 +0000 Subject: [PATCH] GRO-1636: seed.ts creates Better Auth credential accounts for UAT personas After creating staff table records for UAT personas, seedKnownUsers() now reads SEED_UAT_*_PASSWORD env vars and creates Better Auth user + account rows so personas can email+password login. Uses the same scrypt hash format (N=16384, r=8, p=1, dkLen=64) as better-auth. For uat-super and uat-groomer, the staff record is linked to the Better Auth user via userId field. Co-Authored-By: Paperclip --- packages/db/src/seed.ts | 76 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/packages/db/src/seed.ts b/packages/db/src/seed.ts index 058b7c9..0c2bd00 100644 --- a/packages/db/src/seed.ts +++ b/packages/db/src/seed.ts @@ -20,6 +20,7 @@ import postgres from "postgres"; import { drizzle } from "drizzle-orm/postgres-js"; import { eq, sql } from "drizzle-orm"; import * as schema from "./schema.js"; +import { randomBytes, scrypt } from "node:crypto"; // ── Seed profile configuration ───────────────────────────────────────────── @@ -509,6 +510,81 @@ async function seedKnownUsers() { } console.log(`✓ Seeded ${demoSvcs.length} services`); + // ── Better Auth credential accounts for UAT personas ───────────────────── + // Creates user + account rows so UAT personas can email+password login. + // Uses the same scrypt config as better-auth (N=16384, r=8, p=1, dkLen=64). + const uatCredAccounts: Array<{ email: string; passwordEnvKey: string; staffId: string }> = [ + { email: "uat-super@groombook.dev", passwordEnvKey: "SEED_UAT_SUPER_PASSWORD", staffId: "00000000-0000-0000-0000-000000000003" }, + { email: "uat-groomer@groombook.dev", passwordEnvKey: "SEED_UAT_GROOMER_PASSWORD", staffId: "00000000-0000-0000-0000-000000000004" }, + { email: "uat-customer@groombook.dev", passwordEnvKey: "SEED_UAT_CUSTOMER_PASSWORD", staffId: "" }, + { email: "uat-tester@groombook.dev", passwordEnvKey: "SEED_UAT_TESTER_PASSWORD", staffId: "" }, + ]; + + for (const acct of uatCredAccounts) { + const password = process.env[acct.passwordEnvKey]; + if (!password) { + console.log(`⊘ No ${acct.passwordEnvKey} set — skipping Better Auth account for ${acct.email}`); + continue; + } + + // Check if user already exists + const [existingUser] = await db + .select() + .from(schema.user) + .where(eq(schema.user.email, acct.email)) + .limit(1); + + let userId: string; + if (existingUser) { + userId = existingUser.id; + console.log(`✓ Better Auth user '${acct.email}' already exists — skipping`); + } else { + // Hash with same scrypt params as better-auth: N=16384, r=8, p=1, dkLen=64 + // Use Promise-based scrypt API (callback pattern, wrapped in Promise) + const salt = randomBytes(16); + const key = await new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + scrypt(password.normalize("NFKC"), salt, 16384, { r: 8, p: 1, dkLen: 64 } as any, (err: Error | null, derivedKey: Buffer) => { + if (err) reject(err); + else resolve(derivedKey); + }); + }); + const passwordHash = `${salt.toString("hex")}:${key.toString("hex")}`; + + const [newUser] = await db.insert(schema.user).values({ + id: uuid(), + name: acct.email.split("@")[0]!, + email: acct.email, + emailVerified: true, + }).returning(); + userId = newUser!.id; + + await db.insert(schema.account).values({ + id: uuid(), + accountId: userId, + providerId: "credential", + userId, + password: passwordHash, + }); + console.log(`✓ Created Better Auth credential account for '${acct.email}'`); + } + + // Link staff record to Better Auth user if staff exists and has no userId yet + if (acct.staffId) { + const [existingStaff] = await db + .select() + .from(schema.staff) + .where(eq(schema.staff.id, acct.staffId)) + .limit(1); + if (existingStaff && !existingStaff.userId) { + await db.update(schema.staff) + .set({ userId }) + .where(eq(schema.staff.id, acct.staffId)); + console.log(` ↳ Linked staff '${acct.email}' to Better Auth user`); + } + } + } + // ── Client: Demo Client ── const [existingClient] = await db .select()