GRO-1636: fix scrypt keylen=64 and add email+password UAT test cases
CI / Test (pull_request) Successful in 25s
CI / E2E Tests (pull_request) Failing after 48s
CI / Build (pull_request) Successful in 24s
CI / Lint & Typecheck (pull_request) Successful in 23s
CI / Build & Push Docker Images (pull_request) Has been skipped
CI / Update Infra Image Tags (pull_request) Has been skipped
CI / Web E2E (Dev) (pull_request) Has been cancelled
CI / Deploy PR to groombook-dev (pull_request) Has been cancelled

1. Fix scrypt keylen: positional arg is output key length, not N cost.
   Correct call: scrypt(pass, salt, 64, {N:16384, r:8, p:1})
   This produces a 64-byte key matching Better Auth's expected format.

2. Update UAT_PLAYBOOK.md §4.1 with 6 new email+password login test
   cases covering all 4 UAT personas (super, groomer, customer, tester),
   renumbered session/logout/RBAC tests, and a reset-cycle survival test.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Flea Flicker
2026-05-23 20:41:30 +00:00
parent 4ec2885b09
commit 964c63bbdf
2 changed files with 14 additions and 9 deletions
+11 -6
View File
@@ -35,12 +35,17 @@ GroomBook is an open-source, self-hostable pet grooming business management & CR
| # | Scenario | Steps | Expected | | # | Scenario | Steps | Expected |
|---|----------|-------|----------| |---|----------|-------|----------|
| TC-APP-4.1.1 | OIDC login | 1. Navigate to UAT environment<br>2. Click "Login with Authentik"<br>3. Enter test credentials<br>4. Authorize the application | User is redirected to app dashboard, session is established | | TC-APP-4.1.1 | OIDC login (Authentik) | 1. Navigate to UAT environment<br>2. Click "Login with Authentik"<br>3. Enter test credentials<br>4. Authorize the application | User is redirected to app dashboard, session is established |
| TC-APP-4.1.2 | Session persistence | 1. Log in as any user<br>2. Close browser tab<br>3. Reopen browser and navigate to UAT | User remains logged in, no re-authentication required | | TC-APP-4.1.2 | Email + password login (UAT Super) | 1. Navigate to UAT environment sign-in page<br>2. Select email+password flow<br>3. Enter `uat-super@groombook.dev` and UAT super password<br>4. Submit | User is logged in and redirected to dashboard with manager access |
| TC-APP-4.1.3 | Logout | 1. Log in as any user<br>2. Click logout button<br>3. Attempt to access protected route | User is logged out and redirected to login page | | TC-APP-4.1.3 | Email + password login (UAT Groomer) | 1. Navigate to UAT environment sign-in page<br>2. Select email+password flow<br>3. Enter `uat-groomer@groombook.dev` and UAT groomer password<br>4. Submit | User is logged in and redirected to dashboard with staff/groomer access |
| TC-APP-4.1.4 | RBAC - Manager access | 1. Log in as Manager<br>2. Navigate to Settings, Staff Management, Reports | All administrative features are accessible | | TC-APP-4.1.4 | Email + password login (UAT Customer) | 1. Navigate to UAT environment sign-in page<br>2. Select email+password flow<br>3. Enter `uat-customer@groombook.dev` and UAT customer password<br>4. Submit | User is logged in with client portal access |
| TC-APP-4.1.5 | RBAC - Staff access | 1. Log in as Staff<br>2. Attempt to access Settings, Staff Management | Access denied or limited view, staff can only see assigned appointments | | TC-APP-4.1.5 | Email + password login (UAT Tester) | 1. Navigate to UAT environment sign-in page<br>2. Select email+password flow<br>3. Enter `uat-tester@groombook.dev` and UAT tester password<br>4. Submit | User is logged in with staff/tester access |
| TC-APP-4.1.6 | RBAC - Client access | 1. Log in as Client<br>2. Navigate to portal<br>3. Attempt to access admin areas | Client can only view their own appointments, pets, and profile | | TC-APP-4.1.6 | Session persistence | 1. Log in as any user<br>2. Close browser tab<br>3. Reopen browser and navigate to UAT | User remains logged in, no re-authentication required |
| TC-APP-4.1.7 | Logout | 1. Log in as any user<br>2. Click logout button<br>3. Attempt to access protected route | User is logged out and redirected to login page |
| TC-APP-4.1.8 | RBAC - Manager access | 1. Log in as Manager (OIDC or email+password)<br>2. Navigate to Settings, Staff Management, Reports | All administrative features are accessible |
| TC-APP-4.1.9 | RBAC - Staff access | 1. Log in as Staff (OIDC or email+password)<br>2. Attempt to access Settings, Staff Management | Access denied or limited view, staff can only see assigned appointments |
| TC-APP-4.1.10 | RBAC - Client access | 1. Log in as Client (email+password)<br>2. Navigate to portal<br>3. Attempt to access admin areas | Client can only view their own appointments, pets, and profile |
| TC-APP-4.1.11 | Login after hourly reset | 1. Wait for or trigger `reset-demo-data` CronJob to run<br>2. Attempt email+password login as any UAT persona | Login succeeds — Better Auth credential accounts survive the reset cycle |
### 4.2 Setup Wizard / OOBE ### 4.2 Setup Wizard / OOBE
+3 -3
View File
@@ -512,7 +512,7 @@ async function seedKnownUsers() {
// ── Better Auth credential accounts for UAT personas ───────────────────── // ── Better Auth credential accounts for UAT personas ─────────────────────
// Creates user + account rows so UAT personas can email+password login. // 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). // Uses the same scrypt config as better-auth (keylen=64, N=16384, r=8, p=1).
const uatCredAccounts: Array<{ email: string; passwordEnvKey: string; staffId: string }> = [ 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-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-groomer@groombook.dev", passwordEnvKey: "SEED_UAT_GROOMER_PASSWORD", staffId: "00000000-0000-0000-0000-000000000004" },
@@ -539,12 +539,12 @@ async function seedKnownUsers() {
userId = existingUser.id; userId = existingUser.id;
console.log(`✓ Better Auth user '${acct.email}' already exists — skipping`); console.log(`✓ Better Auth user '${acct.email}' already exists — skipping`);
} else { } else {
// Hash with same scrypt params as better-auth: N=16384, r=8, p=1, dkLen=64 // Hash with same scrypt params as better-auth: keylen=64, N=16384, r=8, p=1
// Use Promise-based scrypt API (callback pattern, wrapped in Promise) // Use Promise-based scrypt API (callback pattern, wrapped in Promise)
const salt = randomBytes(16); const salt = randomBytes(16);
const key = await new Promise<Buffer>((resolve, reject) => { const key = await new Promise<Buffer>((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // 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) => { scrypt(password.normalize("NFKC"), salt, 64, { N: 16384, r: 8, p: 1 } as any, (err: Error | null, derivedKey: Buffer) => {
if (err) reject(err); if (err) reject(err);
else resolve(derivedKey); else resolve(derivedKey);
}); });