fix(seed): update credential password on existing accounts — not skip (GRO-1977)
Previously, seed.ts skip-inserted a credential account if it already existed, freezing the stored hash at first-seed. Now it re-hashes the current env var value and UPDATE the existing row, enabling password rotation without a full DB wipe. - AC-8: re-seeding with a changed SEED_UAT_*_PASSWORD updates the stored hash - AC-5 still passes: re-seeding with the same password is idempotent (no new rows) - All 540 tests pass Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -173,7 +173,10 @@ async function seedUatCredentials(
|
||||
);
|
||||
|
||||
if (existingAccount) {
|
||||
// skip — already has credential account
|
||||
// Re-hash and update the password (mirrors seed.ts behavior)
|
||||
const { hashPassword } = await import("better-auth/crypto");
|
||||
const passwordHash = await hashPassword(password);
|
||||
existingAccount.password = passwordHash;
|
||||
} else {
|
||||
// Use Better-Auth's hashPassword so test helper matches production seed.ts
|
||||
const { hashPassword } = await import("better-auth/crypto");
|
||||
@@ -351,6 +354,49 @@ describe("seedUatCredentials — credential provisioning logic", () => {
|
||||
expect(insertedAccounts).toHaveLength(0);
|
||||
});
|
||||
|
||||
// ── AC-8: existing account password IS updated (not frozen at first-seed) ──
|
||||
|
||||
it("AC-8: re-seeding with a changed password env var updates the stored hash", async () => {
|
||||
const ORIGINAL_PASSWORD = "original-password";
|
||||
const ROTATED_PASSWORD = "rotated-password-456";
|
||||
|
||||
process.env.SEED_UAT_CUSTOMER_PASSWORD = ROTATED_PASSWORD;
|
||||
|
||||
const preExistingUsers: UserRow[] = [
|
||||
{ id: "pre-existing-user", email: "uat-customer@groombook.dev", name: "UAT Customer", emailVerified: true },
|
||||
];
|
||||
// Account was created with the original password on first seed
|
||||
const originalHash = await hashPassword(ORIGINAL_PASSWORD);
|
||||
const preExistingAccounts: AccountRow[] = [
|
||||
{
|
||||
id: "pre-existing-acct",
|
||||
accountId: "pre-existing-user",
|
||||
providerId: "credential",
|
||||
userId: "pre-existing-user",
|
||||
password: originalHash,
|
||||
},
|
||||
];
|
||||
|
||||
// Re-seed with the rotated password env var
|
||||
await seedUatCredentials([UAT_ACCOUNTS[2]!], {
|
||||
users: preExistingUsers,
|
||||
accounts: preExistingAccounts,
|
||||
staff: [],
|
||||
});
|
||||
|
||||
// No new user or account created
|
||||
expect(insertedUsers).toHaveLength(0);
|
||||
expect(insertedAccounts).toHaveLength(0);
|
||||
|
||||
// The pre-existing account's password WAS updated (not frozen at first-seed).
|
||||
// hashPassword uses a random salt so we verify by format + that it is a new,
|
||||
// different valid hash from the original.
|
||||
const updatedAcct = preExistingAccounts[0]!;
|
||||
expect(updatedAcct.password).toBeDefined();
|
||||
expect(updatedAcct.password).toMatch(/^[a-f0-9]{32}:[a-f0-9]{128}$/);
|
||||
expect(updatedAcct.password).not.toBe(originalHash); // it actually changed
|
||||
});
|
||||
|
||||
// ── AC-6: missing env var skips with warning ────────────────────────────────
|
||||
|
||||
it("AC-6: missing SEED_UAT_*_PASSWORD env var skips that account (no error)", async () => {
|
||||
|
||||
@@ -594,7 +594,15 @@ async function seedKnownUsers() {
|
||||
.limit(1);
|
||||
|
||||
if (existingAccount) {
|
||||
console.log(`✓ Credential account for '${acct.email}' already exists — skipping`);
|
||||
// Re-hash and update the password so that re-seeding rotates credentials
|
||||
// when the env var changes (e.g. after a password rotation). Previously
|
||||
// this branch skipped entirely, freezing the hash at first-seed.
|
||||
const { hashPassword } = await import("better-auth/crypto");
|
||||
const passwordHash = await hashPassword(password);
|
||||
await db.update(schema.account)
|
||||
.set({ password: passwordHash })
|
||||
.where(eq(schema.account.id, existingAccount.id));
|
||||
console.log(`✓ Credential account for '${acct.email}' already exists — password updated`);
|
||||
} else {
|
||||
// Use Better-Auth's own hashPassword to guarantee parameter/encoding match.
|
||||
// better-auth/crypto uses: N=16384, r=16, p=1, dkLen=64, salt as 16-byte random
|
||||
|
||||
Reference in New Issue
Block a user