From 5e78df85f10a3a91ddb48e9c5b56f1e71b6a0652 Mon Sep 17 00:00:00 2001 From: Flea Flicker Date: Mon, 25 May 2026 19:16:53 +0000 Subject: [PATCH 1/6] chore: trigger CI for GRO-1754 UAT bump --- trigger-ci-1779736613.tmp | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 trigger-ci-1779736613.tmp diff --git a/trigger-ci-1779736613.tmp b/trigger-ci-1779736613.tmp new file mode 100644 index 0000000..e69de29 -- 2.52.0 From 7a0662541d0ff39b1c97f4dc543fc42d983b6ccb Mon Sep 17 00:00:00 2001 From: Flea Flicker Date: Mon, 25 May 2026 19:20:51 +0000 Subject: [PATCH 2/6] chore: trigger CI for GRO-1754 --- ci-trigger-1779736851.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 ci-trigger-1779736851.txt diff --git a/ci-trigger-1779736851.txt b/ci-trigger-1779736851.txt new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/ci-trigger-1779736851.txt @@ -0,0 +1 @@ +test -- 2.52.0 From b0d9e5816f4729645c6614c307efa52aab7f0cbc Mon Sep 17 00:00:00 2001 From: Flea Flicker Date: Mon, 25 May 2026 23:14:01 +0000 Subject: [PATCH 3/6] chore: trigger CI v2 for GRO-1754 --- ci-trigger-v2.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 ci-trigger-v2.txt diff --git a/ci-trigger-v2.txt b/ci-trigger-v2.txt new file mode 100644 index 0000000..ada9e82 --- /dev/null +++ b/ci-trigger-v2.txt @@ -0,0 +1 @@ +trigger CI v2 -- 2.52.0 From fbcaedf1556290c9192eb523cbd8b74f553a10fe Mon Sep 17 00:00:00 2001 From: Flea Flicker Date: Mon, 25 May 2026 23:20:51 +0000 Subject: [PATCH 4/6] chore: trigger CI for GRO-1754 UAT bump check --- trigger-1779751251.tmp | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 trigger-1779751251.tmp diff --git a/trigger-1779751251.tmp b/trigger-1779751251.tmp new file mode 100644 index 0000000..e69de29 -- 2.52.0 From 38047d5ea33f93daf845e25606272f12796bee06 Mon Sep 17 00:00:00 2001 From: Flea Flicker Date: Mon, 25 May 2026 23:27:16 +0000 Subject: [PATCH 5/6] chore: trigger CI on dev for GRO-1754 --- ci-trigger-1779736851.txt | 1 - ci-trigger-v2.txt | 1 - trigger-1779751251.tmp | 0 trigger-ci-1779736613.tmp | 0 4 files changed, 2 deletions(-) delete mode 100644 ci-trigger-1779736851.txt delete mode 100644 ci-trigger-v2.txt delete mode 100644 trigger-1779751251.tmp delete mode 100644 trigger-ci-1779736613.tmp diff --git a/ci-trigger-1779736851.txt b/ci-trigger-1779736851.txt deleted file mode 100644 index 9daeafb..0000000 --- a/ci-trigger-1779736851.txt +++ /dev/null @@ -1 +0,0 @@ -test diff --git a/ci-trigger-v2.txt b/ci-trigger-v2.txt deleted file mode 100644 index ada9e82..0000000 --- a/ci-trigger-v2.txt +++ /dev/null @@ -1 +0,0 @@ -trigger CI v2 diff --git a/trigger-1779751251.tmp b/trigger-1779751251.tmp deleted file mode 100644 index e69de29..0000000 diff --git a/trigger-ci-1779736613.tmp b/trigger-ci-1779736613.tmp deleted file mode 100644 index e69de29..0000000 -- 2.52.0 From b61d899f814d4e091efa6db9ad61d4a887d02e7a Mon Sep 17 00:00:00 2001 From: Scrubs McBarkley <18+gb_scrubs@noreply.git.farh.net> Date: Mon, 25 May 2026 23:39:57 +0000 Subject: [PATCH 6/6] fix(GRO-1757): auto-provision staff for OIDC users + UAT playbook updates (#83) --- UAT_PLAYBOOK.md | 20 +++++++++++++++++++ src/middleware/rbac.ts | 44 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/UAT_PLAYBOOK.md b/UAT_PLAYBOOK.md index d5887c6..d03aeea 100644 --- a/UAT_PLAYBOOK.md +++ b/UAT_PLAYBOOK.md @@ -48,6 +48,26 @@ GroomBook API is a Hono-based REST service (TypeScript/Node.js) powering the pet | TC-API-1.15 | Name fallback — no name, no email | Auto-provision where Better-Auth user has name = null, email = null | Staff name = "Unknown" | | TC-API-1.16 | OIDC login — Terraform-provisioned user | Initiate OIDC login as any UAT persona (uat-super, uat-groomer, uat-customer, uat-tester), complete authentik callback | 200 OK, session created — no account_not_linked error | +#### SSO Login Journey (Authentik OIDC end-to-end) + +| # | Scenario | Steps | Pass Criteria | Fail Criteria | +|---|----------|-------|---------------|---------------| +| TC-API-1.17 | SSO redirect to Authentik | Navigate to app → sign-in page shown → click "Sign in with SSO" | Redirected to Authentik at auth.farh.net | 403 error, redirect loop, no SSO button | +| TC-API-1.18 | Authenticate with valid OIDC credentials | At Authentik login page, enter valid credentials and authenticate | Redirected back to app with valid session | Redirect loop, 403, missing session cookie | +| TC-API-1.19 | SSO user auto-provisioned as groomer | Complete SSO login as a user with no pre-existing staff record | 200 response; groomer staff record auto-created; session active | 403 Forbidden, staff record not created | +| TC-API-1.20 | Existing staff record resolves correctly | Complete SSO login as uat-groomer (pre-existing staff) | 200 OK, correct staff identity resolved, no duplicate record created | 403, duplicate record, wrong staff data | +| TC-API-1.21 | SSO session grants dashboard access | After TC-API-1.18 SSO login, GET /api/staff/me | 200 OK, valid staff record returned, correct role displayed | 401/403, missing session, wrong identity | + +#### OOBE Flow Post-Login + +| # | Scenario | Steps | Pass Criteria | Fail Criteria | +|---|----------|-------|---------------|---------------| +| TC-API-1.22 | Fresh DB reports needsSetup | On a fresh DB (no super user), GET /api/setup/status | needsSetup: true returned | needsSetup: false when it should be true | +| TC-API-1.23 | Configure OIDC via auth-provider endpoint | POST /api/setup/auth-provider with valid OIDC config | 200 OK, auth provider configured, no 403 | 403, setup blocked, invalid config rejected | +| TC-API-1.24 | Complete setup creates super user | POST /api/setup with business name (after TC-API-1.23) | First user becomes super user, setup completes | Setup errors, 403 on admin endpoints | +| TC-API-1.25 | Super user accesses admin features | After TC-API-1.24, GET /api/staff/me and verify isSuperUser: true | isSuperUser: true, admin endpoints accessible | 403 on admin, isSuperUser: false | +| TC-API-1.26 | Auto-provision skipped during OOBE | During fresh setup (needsSetup: true), complete OIDC login — verify no duplicate staff record created before setup completes | No duplicate staff, OOBE completes successfully | Duplicate staff record, 403 before setup, auto-provision interferes with OOBE | + ### 4.2 Client Management | # | Scenario | Steps | Expected | diff --git a/src/middleware/rbac.ts b/src/middleware/rbac.ts index 1277b2c..bace747 100644 --- a/src/middleware/rbac.ts +++ b/src/middleware/rbac.ts @@ -1,5 +1,5 @@ import type { MiddlewareHandler } from "hono"; -import { and, eq, getDb, sql, staff } from "@groombook/db"; +import { and, eq, getDb, sql, staff, account } from "@groombook/db"; export type StaffRole = "groomer" | "receptionist" | "manager"; export type StaffRow = typeof staff.$inferSelect; @@ -110,6 +110,48 @@ export const resolveStaffMiddleware: MiddlewareHandler = async ( return; } } + + // Auto-provision for OIDC users: check if jwt.sub has an OAuth/OIDC account + // (e.g. authentik). If so, create a groomer staff record on the fly. + if (jwt.email) { + const [oidcAccount] = await db + .select({ id: account.id }) + .from(account) + .where( + and( + eq(account.userId, jwt.sub), + sql`${account.providerId} IN ('authentik', 'google', 'github')` + ) + ) + .limit(1); + + if (oidcAccount) { + // Derive name: prefer jwt.name, fall back to email prefix, then "Unknown" + const name = + jwt.name?.trim() || + (jwt.email ? jwt.email.split("@")[0] : "Unknown"); + + const [newStaff] = await db + .insert(staff) + .values({ + userId: jwt.sub, + email: jwt.email ?? "", + name, + role: "groomer", + isSuperUser: false, + active: true, + }) + .returning(); + + console.log( + `[rbac] auto-provisioned staff record for OIDC user: ${jwt.sub} -> staff:${newStaff.id} (${name})` + ); + c.set("staff", newStaff); + await next(); + return; + } + } + return c.json( { error: "Forbidden: no staff record found for authenticated user" }, 403 -- 2.52.0