e2dc230b7ffc5def52939d403b5a7ff3cf850071
PR #139 (a2b09ba) ported the GRO-2013 owner-bypass into the deployed ./src/routes/pets.ts but did NOT port the rbac auto-provision change. As a result, on UAT (api:2026.06.01-4e9c4c5) the owner-bypass code is unreachable for any Better-Auth email/password customer: ./src/middleware/rbac.ts (the deployed tree) only auto-provisions staff rows when account.providerId IN ('authentik','google','github') for jwt.sub. The UAT customer uat-customer@groombook.dev has a row in the Better-Auth `user` table but no row in `account` for those providers, so resolveStaffMiddleware falls through to: 403 "Forbidden: no staff record found for authenticated user" before pets.ts ever runs. The canonical apps/api/src/middleware/rbac.ts already has a Better-Auth user-table fallback. This commit mirrors that branch into the deployed ./src/middleware/rbac.ts. Behaviour ========= - New: when no staff row exists for jwt.sub but the Better-Auth `user` table has a row whose id matches jwt.sub, INSERT a minimal role='groomer', isSuperUser=false, active=true staff row, set it on the request context, and continue. - The legacy OIDC `account` branch is kept as a fallback for backwards compatibility with any pre-Better-Auth OIDC sessions whose user row may not yet exist in `user`. - Lookup order: staff (userId) -> staff (oidcSub) -> staff (email, user_id IS NULL) -> Better-Auth user -> OIDC account -> 403. - Name derivation: userRow.name -> jwt.name -> email prefix -> "Unknown". Tests ===== src/__tests__/rbac.test.ts: - Mock @groombook/db rewritten to be table-aware so SELECTs from `user`/`account`/`staff` route to distinct lookup queues, and insert(staff).values(...).returning() is supported. - buildApp() helper gains an optional jwtOverride param so tests can set jwt.email/name explicitly. - 5 new cases under "resolveStaffMiddleware — auto-provision": 1. Better-Auth user found -> staff row provisioned with role=groomer 2. INSERT returns no row -> 500 "auto-provision failed" 3. Better-Auth branch runs without jwt.email (regression of the pre-fix gate) 4. OIDC fallback still works when user row is missing but account row exists 5. Neither user nor account row -> 403 with "no staff record" message Existing rbac.test.ts cases all keep passing (15 prior cases retained). Full pnpm test on apps/api: 572/572 pass. pnpm typecheck: clean. Scope ===== - ./src/middleware/rbac.ts only — apps/api/src/middleware/rbac.ts already has this branch and is unchanged. - No schema/migration changes; staff and user tables are unchanged. - pre-existing lint error in src/__tests__/petProfileSummary.test.ts:167 (`servicesTable` declared/assigned but never read) introduced by PR #139a2b09bais NOT addressed here — it is out of this PR's scope. Resolves: GRO-2052 Refs: GRO-2013, GRO-2050, GRO-2035 Co-Authored-By: Paperclip <noreply@paperclip.ing>
GroomBook API
GroomBook API service — extracted from the groombook/app monorepo.
Overview
This repository contains the GroomBook API service, including:
- REST API endpoints
- Database schema and migrations (via Drizzle ORM)
- Authentication (via Better Auth)
- Background job handlers
Structure
src/ # API service source
packages/db/ # Database schema, migrations, and utilities
packages/types/ # Shared TypeScript types
Setup
pnpm install
cp .env.example .env # Fill in required environment variables
pnpm --filter @groombook/api dev
Docker
docker build -t ghcr.io/groombook/api:latest .
docker run -p 3000:3000 ghcr.io/groombook/api:latest
License
AGPL-3.0-only
Description
Languages
TypeScript
99.3%
JavaScript
0.4%
Dockerfile
0.2%