Promote dev → uat: SSO bridge endpoint + role scope (GRO-1866) #94
Closed
The Dogfather
wants to merge 0 commits from
dev into uat
pull from: dev
merge into: groombook:uat
groombook:main
groombook:gro-2381-agents-contributing
groombook:flea/uat-to-main-gro-2359-api
groombook:uat
groombook:release/main-GRO-2342-api
groombook:release/main-GRO-2319-api
groombook:flea/promote-main-gro-2311
groombook:promote/GRO-2319-api-to-uat
groombook:feat/GRO-2319-portal-waitlist-surfacing
groombook:flea/promote-main-gro-2172
groombook:dev-to-uat-gro-2311
groombook:flea/gro-2311-seed-portal-statusbadge-appts
groombook:promote/gro-2172-pets-to-uat
groombook:fix/gro-2172-pet-extended-fields
groombook:uat-to-main-gro-2299
groombook:flea/promote-main-gro-2294
groombook:promote/dev-to-uat-gro-2299
groombook:gro-2299-redact-patch-settings
groombook:flea/promote-gro-2294-uat
groombook:flea/gro-2294-route-opt-hardening
groombook:flea/uat-to-main-gro-2157-frozen
groombook:promote/dev-to-uat-gro-2225
groombook:flea/gro-2157-navigation-export
groombook:flea/gro-2235-waitlist-duplicate-409
groombook:feat/gro-2225-uat-seed-route-cohort
groombook:flea/uat-to-main-gro-2234-api
groombook:flea/dev-to-uat-gro-2156
groombook:flea-flicker/gro-2234-portal-session-sliding-ttl
groombook:release/main-6120b96
groombook:flea/gro-2156-travel-buffer-reorder
groombook:release/main-eb92f99
groombook:fix/gro-2214-portal-waitlist-validation
groombook:fix/gro-2203-portal-pet-patch-uuid-validation
groombook:dev-to-uat-gro-2155
groombook:feat/gro-2155-route-optimize-endpoints-dev
groombook:fix/gro-2163-migrate-pre-dns-wait
groombook:fix/gro-2187-portal-photokey-hijack
groombook:dev-to-uat-gro-2154
groombook:feat/gro-2154-geocoding-endpoints-dev
groombook:flea-flicker/gro-2197-ci-api-gate
groombook:dev-to-uat-gro-2153
groombook:dev-to-uat-gro-2187
groombook:fix/gro-2187-portal-pets-patch
groombook:dev-to-uat-gro-2129
groombook:flea-flicker/gro-2123-cleanup-stale-seed-duplicate
groombook:dev-to-uat-gro-2123
groombook:flea-flicker/gro-2123-seed-advisory-lock
groombook:promote/dev-to-uat-gro-2100
groombook:flea/gro-2100-uat-groomer-pet-linkage
groombook:flea/gro-2062-owner-bypass-audit
groombook:flea/gro-2052-rbac-betterauth-user-autoprovision
groombook:dogfather/gro-2013-promote-uat
groombook:flea/gro-2013-owner-bypass-deployed-tree
groombook:flea/gro-2033-idempotent-pet-profile-migrations
groombook:fix/gro-2014-profile-summary-error-handling
groombook:flea/gro-2000-uat-password-source-doc
groombook:fix/gro-1999-uat-seed-extra-large
groombook:fix/gro-1983-seed-pnpm-baked
groombook:fix/GRO-1979-coat-type-pet-size-enum-fix
groombook:fleaflicker/GRO-1962-deterministic-testcoopper-rocky
groombook:flea/gro-1977-seed-idempotency
groombook:fix/GRO-1977-seed-credential-idempotency
groombook:promote/dev-to-uat-gro-1971
groombook:fix/gro-1971-coat-type-enum-missing-short
groombook:fix/GRO-1962-uat-seed-pet-medicalalerts
groombook:flea/GRO-1955-fix-uc-undefined-seed
groombook:fix/GRO-1909-migrate-corepack-offline
groombook:fix/GRO-1953-coat-type-short-missing
groombook:fleaflicker/gro-medical-alert-types-behavioral-skin
groombook:fleaflicker/gro-1921-uat-reset-full-seed
groombook:flea/GRO-1945-pets-visitcount-hotfix
groombook:fix/GRO-1935-uat-customer-client-seed
groombook:fix/GRO-1914-seed-typeof
groombook:feature/GRO-1898-extended-pet-profile-seed
groombook:seed/extended-profile-fields-gro-1898
groombook:fix/gro-1889-reset-demo-data-pnpm
groombook:promote/dev-to-uat-gro-1866
groombook:fix/gro-1866-qa-fixes
groombook:fix/gro-1866-sso-bridge
groombook:fix/gro-1850-pet-profile-migration
groombook:promote/dev-to-uat-gro-1790
groombook:flea-flicker/pet-profile-summary
groombook:ff/gro-1765-trigger-ci
groombook:promo/gro-1764-uat
groombook:ci/gro-1757-build
groombook:fix/gro-1757-sso-auto-provision
groombook:fix/gro-1754-uat-ci
groombook:fix/gro-1754-trigger-ci-v2
groombook:fix/gro-1752-factories-v2
groombook:fix/gro-1752-factories-only
groombook:fix/gro-1746-apply-uat-seed-to-root-src
groombook:fix/gro-1752-extended-pet-profile-fields
groombook:promo/gro-1749-uat
groombook:fix/gro-1749-uat-seed-sync
groombook:fix/gro-1743-uat-seed-data
groombook:fix/gro-1480-portal-pets-patch
groombook:fix/gro-1678-econnreset-robustness
groombook:fix/gro-1576-ci-provenance-false
groombook:fix/gro-1575-ci-provenance
groombook:fix/gro-1566-api-health-auth-bypass
groombook:fix/gro-1544-api-health-endpoint
groombook:fix/gro-1533-migration-0031-coat-type
groombook:fix/gro-1533-missing-migration-0032
groombook:fix/gro-1533-missing-migration-journal
groombook:revert/gro-1533-dockerfile-fix
groombook:fix/gro-1533-revert-dockefile-build-change
groombook:flea-flicker/gro-1531-seed-db-filter
groombook:fix/gro-1522-ci-images-node22
groombook:pr-44
groombook:flea-flicker/gro-1509-better-auth-account-not-linked
groombook:flea-flicker/gro-1162-pet-buffer-time
groombook:fix/gro-1461-uat-playbook-auto-provision
groombook:flea-flicker/pet-profile-editor
groombook:fix/gro-1441-remove-duplicate-coat-props
groombook:fix/gro-1390-pets-test-mock-hoisting
groombook:fix/gro-1395-drizzle-orm-root-dep
groombook:gitea/migrate-workflows
groombook:flea-flicker/fix-gro-1370-ts-and-test-errors
groombook:fix/api/add-devdep-drizzle-orm-fix-vitest
groombook:pr-19
groombook:flea-flicker/uat-email-password-seed
groombook:fleaflicker/gro-1272-v2
groombook:fleaflicker/gro-1272-auto-provision-staff
groombook:add-renovate-config
groombook:flea-flicker/gro-1231-pnpm-workspace-dockerfile
groombook:fix/GRO-1202-rate-limit-override
groombook:fix/typescript-errors
groombook:flea-flicker/pet-profile-extended-fields
groombook:fix/uat-tester-oidc-sub
groombook:flea-flicker/fix-authprovider-mock-path
groombook:flea-flicker/auto-create-staff-oauth-users-v2
groombook:flea-flicker/auto-create-staff-oauth-users
groombook:docs/GRO-1099-uat-playbook-api
groombook:flea-flicker/fix-ci-install-deps-v2
groombook:flea-flicker/fix-ci-install-deps
Labels
Clear labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
Something isn't working
Improvements or additions to documentation
This issue or pull request already exists
New feature or request
Good for newcomers
Extra attention is needed
This doesn't seem right
Further information is requested
This will not be worked on
No Label
Milestone
No items
No Milestone
Projects
Clear projects
No project
Assignees
ai-review (AI Review)
gb_barkley (Barkley Trimsworth)
cpfarhood (Chris Farhood)
ci (Continuous Integration [bot])
gb_flea (Flea Flicker)
flux (Flux CD)
admin (Gitea Admin)
gb_lint (Lint Roller)
renovate (Mend Renovate)
gb_pawla (Pawla Abdul)
gb_scrubs (Scrubs McBarkley)
gb_shedward (Shedward Scissorhands)
gb_dogfather (The Dogfather)
Clear assignees
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: groombook/api#94
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "dev"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Promotes the SSO bridge changes from
devtouatfor QA validation.Changes included:
POST /api/portal/session-from-authendpoint — bridges Better Auth SSO sessions to portal sessions for real customersroleto genericOAuth scopes — propagates Authentik role claim to Better Auth sessionsParent issue
cc @cpfarhood
With noUncheckedIndexedAccess:true, split("@")[0] returns string|undefined, making `name` typed as string|undefined and failing the notNull staff.name insert constraint. Fix by using ?? fallback on the array access. Also add newStaff null guard after .returning() destructure — array destructuring yields T|undefined with noUncheckedIndexedAccess enabled.- Replace .select({ count: appointments.id }).limit(1) + .length with sql<number>`count(*)::int` pattern per project standard (references invoices.ts:86) - Add gte(appointments.startTime, new Date()) to upcomingAppointment query so past appointments in scheduled/confirmed status are excluded - Add visitCount regression tests: 2+ completed appointments → visitCount >= 2, no completed → visitCount = 0 Updated UAT_PLAYBOOK.md §profile-summary (visitCount regression + date filter) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Adds POST /api/portal/session-from-auth which bridges a valid Better Auth customer session (from SSO login) to a portal impersonation session, so real SSO customers can access the client portal. The endpoint is registered before the validatePortalSession catch-all so it is not subject to that middleware. It validates the Better Auth session from request cookies, looks up the client by email, creates an active impersonation session, and returns { sessionId, clientId, clientName }. Also adds "role" to the genericOAuth scopes so Authentik propagates the role claim into Better Auth user objects (GRO-1862 root cause fix). Co-Authored-By: Paperclip <noreply@paperclip.ing>QA Review — Changes Requested
CI is failing and there are code-correctness issues that must be resolved before this can promote to UAT.
❌ CI FAIL — TypeScript build error (blocks merge)
src/routes/portal.ts:138—'portalSession' is possibly 'undefined'The Docker build runs
tsc --project .in strict mode. The destructuringconst [portalSession] = await db.insert(...).returning()producesportalSession: T | undefinedbecause the array may be empty. The code then accessesportalSession.idwithout a guard, which TypeScript (correctly) rejects.Fix: add a null check before accessing
portalSession:❌ Null-dereference risk —
src/middleware/rbac.tsThe refactor from:
to:
introduces a runtime TypeError if
jwt.emailisnullorundefined. The??applies to the result of.split(), not tojwt.emailitself — sonull.split("@")still throws.Fix: restore the null guard:
Also:
email: jwt.email(line ~145) drops the?? ""fallback. Ifjwt.emailis undefined and the column is NOT NULL, the insert will throw at runtime. Restoreemail: jwt.email ?? ""or add the null check above.❌ Hardcoded UUID —
src/routes/portal.tsHardcoded values are prohibited by GroomBook coding standards. This UUID must be externalised (env var, config table, or seeded constant) before merge.
Lint & Test CI jobs passed. All three issues above must be fixed and CI must be fully green before re-submitting.
QA Review — Changes Requested
Two blockers before this PR can be approved:
❌ BLOCKER 1 — Docker image build fails (TypeScript TS18048)
File:
src/routes/portal.ts:138Error:
TS18048: 'portalSession' is possibly 'undefined'The CI Lint & Typecheck job passes (using
pnpm typecheck), but the Dockerfile build step runstsc --project .directly and fails. In thesession-from-authendpoint,const [portalSession] = await db.insert(...).returning()can returnundefinedif no row is returned, and thenportalSession.idthrows a type error.Fix: Add a null guard after the insert:
This is a real runtime guard, not just a type appeaser — DB inserts can fail to return rows in edge cases.
❌ BLOCKER 2 — CI regression: removes
uatfrom CI triggersFile:
.gitea/workflows/ci.yml(4 deletions)The
uatbranch currently hasuatin its push and pull_request branch triggers. Thedevbranch does not. After this PR merges, theuatbranch CI will no longer trigger on pushes or PRs targetinguat.Fix: Before re-submitting, update
devbranch'sci.ymlto adduatto bothpush.branchesandpull_request.branches:This will appear as 0-line net change in the next dev→uat promotion (since uat already has it).
✅ What looks good
src/lib/auth.ts:rolescope correctly added to genericOAuth scopes for Authentik ("openid profile email role")src/routes/portal.ts: SSO bridge endpoint logic is correct — Better Auth session lookup, client email matching, impersonation session creation, registered beforevalidatePortalSessionmiddleware ✓src/__tests__/portalSessionFromAuth.test.ts: Test coverage present (5 test cases matching PR description)UAT_PLAYBOOK.md: TC-API-8.8 through TC-API-8.11 added ✓src/middleware/rbac.ts: Changes look correct for auto-provisioning OIDC staffPlease fix both blockers and re-push to
devfor CI verification before re-submitting this promotion PR.Pull request closed