feat(GRO-1445): provision Better-Auth credential accounts in seed.ts
GRO-1325 was marked done but never implemented. This adds the missing Better-Auth user + account seeding for UAT email+password logins. For each SEED_UAT_*_PASSWORD env var present, the seed now: 1. Creates (or links to existing) a Better-Auth user record with emailVerified: true 2. Creates a credential account with providerId: "credential" and a bcrypt-hashed password (using better-auth/crypto) 3. Links the staff record to the Better-Auth user via userId Idempotent: skips user/account creation if already seeded. Updated UAT_PLAYBOOK.md §4.1 — TC-API-1.4 through 1.9 now reference the new seed provisioning (GRO-1325 was the missing piece). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-auth": "^1.5.6",
|
||||
"drizzle-orm": "^0.38.4",
|
||||
"postgres": "^3.4.5"
|
||||
},
|
||||
|
||||
+85
-1
@@ -18,7 +18,7 @@
|
||||
|
||||
import postgres from "postgres";
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { eq, and, sql } from "drizzle-orm";
|
||||
import * as schema from "./schema.js";
|
||||
|
||||
// ── Seed profile configuration ─────────────────────────────────────────────
|
||||
@@ -490,6 +490,90 @@ async function seedKnownUsers() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Better-Auth email+password credentials for UAT accounts ──────────────────
|
||||
// Provisions Better-Auth user + account records so UAT testers can log in
|
||||
// via email+password (POST /api/auth/sign-in/email) instead of Authentik SSO.
|
||||
const uatPasswordAccounts = [
|
||||
{ email: "uat-super@groombook.dev", name: "UAT Super User", passwordEnv: "SEED_UAT_SUPER_PASSWORD", staffEmail: "uat-super@groombook.dev" },
|
||||
{ email: "uat-groomer@groombook.dev", name: "UAT Staff Groomer", passwordEnv: "SEED_UAT_GROOMER_PASSWORD", staffEmail: "uat-groomer@groombook.dev" },
|
||||
{ email: "uat-customer@groombook.dev", name: "UAT Customer", passwordEnv: "SEED_UAT_CUSTOMER_PASSWORD", staffEmail: null },
|
||||
{ email: "uat-tester@groombook.dev", name: "UAT Tester", passwordEnv: "SEED_UAT_TESTER_PASSWORD", staffEmail: "uat-tester@groombook.dev" },
|
||||
];
|
||||
|
||||
for (const acct of uatPasswordAccounts) {
|
||||
const password = process.env[acct.passwordEnv];
|
||||
if (!password) {
|
||||
console.warn(`⚠ Skipping ${acct.email} — ${acct.passwordEnv} not set`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1. Find or create the Better-Auth user
|
||||
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.name}' already exists — skipping user creation`);
|
||||
} else {
|
||||
userId = uuid();
|
||||
await db.insert(schema.user).values({
|
||||
id: userId,
|
||||
name: acct.name,
|
||||
email: acct.email,
|
||||
emailVerified: true,
|
||||
});
|
||||
console.log(`✓ Created Better-Auth user '${acct.name}' (${acct.email})`);
|
||||
}
|
||||
|
||||
// 2. Check if credential account already exists
|
||||
const [existingAccount] = await db
|
||||
.select()
|
||||
.from(schema.account)
|
||||
.where(and(
|
||||
eq(schema.account.userId, userId),
|
||||
eq(schema.account.providerId, "credential")
|
||||
))
|
||||
.limit(1);
|
||||
|
||||
if (existingAccount) {
|
||||
console.log(`✓ Credential account for '${acct.email}' already exists — skipping`);
|
||||
} else {
|
||||
// Use Better-Auth's own hashPassword so the hash format matches what
|
||||
// better-auth validates at sign-in time.
|
||||
// better-auth/crypto uses: N=16384, r=16, p=1, dkLen=64.
|
||||
const { hashPassword } = await import("better-auth/crypto");
|
||||
const passwordHash = await hashPassword(password);
|
||||
|
||||
await db.insert(schema.account).values({
|
||||
id: uuid(),
|
||||
accountId: userId,
|
||||
providerId: "credential",
|
||||
userId,
|
||||
password: passwordHash,
|
||||
});
|
||||
console.log(`✓ Created credential account for '${acct.email}'`);
|
||||
}
|
||||
|
||||
// 3. Link staff record to Better-Auth user (for accounts that have staff records)
|
||||
if (acct.staffEmail) {
|
||||
const [existingStaff] = await db
|
||||
.select()
|
||||
.from(schema.staff)
|
||||
.where(eq(schema.staff.email, acct.staffEmail))
|
||||
.limit(1);
|
||||
if (existingStaff && !existingStaff.userId) {
|
||||
await db.update(schema.staff)
|
||||
.set({ userId })
|
||||
.where(eq(schema.staff.id, existingStaff.id));
|
||||
console.log(`✓ Linked staff '${acct.staffEmail}' → Better-Auth user`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Services: idempotent upsert using name as unique key ─────────────────────
|
||||
// UNIQUE constraint on services.name (migration 0020) must exist first.
|
||||
// Uses b0000001-... IDs to match main seed servicesDef for same-named services.
|
||||
|
||||
Reference in New Issue
Block a user