The NetworkFirst route for /api/* was intercepting the OIDC callback
(/api/auth/oauth2/callback/authentik?code=...), returning a cached
index.html instead of forwarding to the API server.
Added navigateFallbackDenylist regex to exclude the callback path
from service worker navigation handling, allowing the callback request
to reach the API server normally.
Fixes GRO-472.
Co-authored-by: Flea Flicker <flea-flicker@groombook.farh.net>
Co-authored-by: Paperclip <noreply@paperclip.ing>
The authProviderRouter was registered twice at /admin/auth-provider in
apps/api/src/index.ts. The second registration is a no-op but creates
confusion. Remove the duplicate line.
Co-authored-by: Paperclip <noreply@paperclip.ing>
Use a 16-byte random salt per encryption instead of the fixed
"groombook-auth-provider-config" salt. This prevents identical
plaintexts from producing identical ciphertexts, closing the
timing/anagram security gap identified in GRO-452.
New format: salt:iv:ciphertext:authTag (all base64).
Legacy format (iv:ciphertext:authTag) is still accepted for
backward-compatible decryption of existing stored values.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Switch the test endpoint from putAuthProviderSchema.omit({ clientSecret })
(which requires providerId, displayName, clientId, scopes) to the
minimal authProviderTestSchema (issuerUrl, internalBaseUrl?) that matches
what the Settings.tsx frontend actually sends.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Switch the test endpoint from putAuthProviderSchema.omit({ clientSecret })
(which requires providerId, displayName, clientId, scopes) to the
minimal authProviderTestSchema (issuerUrl, internalBaseUrl?) that matches
what the Settings.tsx frontend actually sends.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Generate a unique 16-byte random salt for each encryptSecret() call
and store it as a prefix in the ciphertext. Format changed from
iv:ciphertext:authTag → salt:iv:ciphertext:authTag
decryptSecret() detects legacy 3-part format and uses the fixed
package salt for backward compatibility with existing encrypted rows.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
UAT is down (503) because sealed secrets were encrypted with the wrong
key. This commit:
- Adds groombook/overlays/uat/ with fresh postgres and auth sealed
secrets sealed with the correct UAT cluster certificate
- Adds kustomization.yaml that:
- Uses correct image tags (2026.04.03-90be1be)
- Injects all auth env vars from groombook-auth-uat
- Points to groombook-postgres-credentials-uat
- Uses UAT hostname (groombook.uat.farh.net)
- Deletes the base component's postgres-credentials SealedSecret
(namespace-scoped, not namespace-wide, causes noise in UAT)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
reinitAuth was imported by authProvider.ts but never defined.
Added a stub implementation that resolves immediately — proper
restart mechanism is tracked in GRO-390.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
PUT /api/admin/auth-provider was returning HTTP 500 with an HTML error page
when BETTER_AUTH_SECRET was missing, because encryptSecret() throws an
unhandled error. This change wraps both the encryption step and the DB
transaction in try/catch blocks to return a proper JSON error response.
Also adds the missing authProviderConfig schema and encryptSecret crypto
helpers from the feat/gro-392-oobe-auth-provider-bootstrap branch.
Fixes: GRO-441
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Adds a manual workflow_dispatch workflow to promote a specific image tag
to the UAT environment. This separates UAT promotion from the automated
dev pipeline, enforcing the 3-stage SDLC review gate.
- Triggers via workflow_dispatch with image_tag input
- Updates UAT overlay image tags in groombook/infra
- Creates and auto-merges infra PR for UAT only
- Requires GRO-427 (UAT overlay) to be complete first
Co-Authored-By: Paperclip <noreply@paperclip.ing>
vi.mock the auth module so reinitAuth() is a no-op in tests.
This decouples the tests from the BETTER_AUTH_SECRET env var.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
reinitAuth() can throw if BETTER_AUTH_SECRET is missing, causing
an unhandled rejection that returns an HTML error page instead of
JSON. Wrap both PUT and DELETE handlers in try/catch to return a
proper JSON error response.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Add reinitAuth() import and calls to routes/authProvider.ts (active router)
instead of routes/admin/authProvider.ts (dead code, not imported)
- Add AbortSignal.timeout(10_000) to fetch in setup auth-provider/test endpoint
- Add .replace(/\/$/, "") to strip trailing slash from internalBaseUrl
- Delete dead routes/admin/authProvider.ts
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- GET /api/setup/status: verify showAuthProviderStep logic for all cases
(fresh install, env vars present, setup complete, DB config exists)
- POST /api/setup/auth-provider: 403 after complete, 409 if already configured,
creates config with encrypted secret, Zod validation
- POST /api/setup/auth-provider/test: 403 after complete, unreachable issuer,
valid issuer, invalid issuer (non-200)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Test connection was always 400 because testAuthProviderSchema required
clientSecret, but OIDC discovery only needs issuer/internal URLs.
Aligned admin test endpoint with setup.ts behavior:
- Drop providerId, clientId, clientSecret from schema
- Add optional internalBaseUrl; use it for discovery URL when set
- Frontend now sends issuerUrl + internalBaseUrl (when populated)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(oobe): add conditional auth provider bootstrap step (GRO-392)
Backend:
- GET /api/setup/status now returns showAuthProviderStep, authConfigExists,
and authEnvVarsSet to inform the frontend whether to show the step
- POST /api/setup/auth-provider: unauthenticated endpoint for first-time
auth provider configuration during OOBE; guarded by needsSetup check
(returns 403 after setup completes); encrypts clientSecret before storing
Frontend:
- SetupWizard fetches /api/setup/status on mount to determine if the
auth provider step is needed (fresh install with no DB config and no
OIDC env vars)
- When needed, inserts the Auth Provider step after Welcome, before
Business Name; includes full form with Test Connection button
- Endpoint is POST /api/admin/auth-provider/test for connection testing
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix(oobe): add test connection endpoint and fix EOF newline (GRO-392)
- Add POST /api/setup/auth-provider/test endpoint for OOBE test connection
- Guard with same !superUser check as bootstrap endpoint
- Update SetupWizard to call /api/setup/auth-provider/test instead of
/api/admin/auth-provider/test (which requires auth session)
- Add trailing newline at EOF in setup.ts
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix(oobe): remove unused catch variable in setup.ts (GRO-392)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* feat(api): auth provider CRUD endpoints + test-connection (GRO-388)
Implement admin API endpoints for managing auth provider configuration:
- GET /api/admin/auth-provider — get current config (secret redacted)
- PUT /api/admin/auth-provider — create or update provider config
- POST /api/admin/auth-provider/test — validate via OIDC discovery endpoint
- DELETE /api/admin/auth-provider — remove DB config (falls back to env vars)
All endpoints are gated by requireSuperUser(). The clientSecret is
AES-256-GCM encrypted before DB write and always redacted on return.
Test-connection fetches /.well-known/openid-configuration and returns
metadata on success or error detail on failure.
Includes 16 unit tests covering all endpoints and error paths.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix(api): requireRoleOrSuperUser for /admin/* routes (GRO-412)
Fix bug where super users granted via Staff UI were blocked from
admin routes because requireRole("manager") checked role before
isSuperUser. Changed to requireRoleOrSuperUser("manager") so
super users bypass the manager-role check.
Also adds 7 unit tests for requireRoleOrSuperUser middleware
covering: manager access, super user bypass, non-super-user
blocking, and multi-role scenarios.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix(api): remove unused decryptSecret import and eslint-disable directives
Fixes lint error exposed by merge with main (GRO-392 PR #214)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix(tests): use main's authProvider tests after rebase conflict resolution
The rebase introduced incompatible test code from the pre-merge GRO-388
commit. Replaced with the canonical test file from main to ensure tests
pass and reflect the actual router implementation.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix(api): remove duplicate authProviderRouter import and route registration
Rebase introduced duplicate import from ./routes/admin/authProvider.js
and duplicate route registration. Removed duplicates since the correct
import is from ./routes/authProvider.js.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix(e2e): use lean schema for OIDC test endpoint; add trailing newline
Fix CTO review comments on GRO-392:
- POST /api/setup/auth-provider/test now uses authProviderTestSchema
(only issuerUrl + internalBaseUrl) instead of full
authProviderBootstrapSchema — clientSecret is not needed for OIDC
discovery and was not being sent by the frontend handler
- POST /api/admin/auth-provider/test already uses omit() correctly;
no change needed
- apps/api/src/routes/admin/authProvider.ts: added trailing newline
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* feat(web): add auth provider section to settings page (GRO-391)
Add Authentication Provider section to /admin/settings for super users.
Implements: provider ID, display name, issuer URL, internal base URL
(optional, collapsed), client ID, client secret (masked, only sent on
change), scopes fields; Test Connection button; Save and Reset to
Environment Defaults with confirmation dialog; warning banner about
service restart; env config info banner when no DB config is set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(api): move needsSetup guard before Zod parsing in setup endpoints
POST /api/setup/auth-provider and POST /api/setup/auth-provider/test
were returning 400 (Zod validation) instead of 403 when needsSetup
was false, because zValidator middleware ran before the route handler
body. Now manually parse the body after the needsSetup guard so 403
fires immediately for post-setup requests.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix(api): replace c.req.valid("json") with await c.req.json()
Replace zValidator-orphaned c.req.valid("json") calls with await c.req.json()
in the auth provider bootstrap and test endpoints per CTO review.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
---------
Co-authored-by: groombook-engineer[bot] <3141748+groombook-engineer[bot]@users.noreply.github.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Barkley Trimsworth <noreply@groombook>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace zValidator-orphaned c.req.valid("json") calls with await c.req.json()
in the auth provider bootstrap and test endpoints per CTO review.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
POST /api/setup/auth-provider and POST /api/setup/auth-provider/test
were returning 400 (Zod validation) instead of 403 when needsSetup
was false, because zValidator middleware ran before the route handler
body. Now manually parse the body after the needsSetup guard so 403
fires immediately for post-setup requests.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Add Authentication Provider section to /admin/settings for super users.
Implements: provider ID, display name, issuer URL, internal base URL
(optional, collapsed), client ID, client secret (masked, only sent on
change), scopes fields; Test Connection button; Save and Reset to
Environment Defaults with confirmation dialog; warning banner about
service restart; env config info banner when no DB config is set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix CTO review comments on GRO-392:
- POST /api/setup/auth-provider/test now uses authProviderTestSchema
(only issuerUrl + internalBaseUrl) instead of full
authProviderBootstrapSchema — clientSecret is not needed for OIDC
discovery and was not being sent by the frontend handler
- POST /api/admin/auth-provider/test already uses omit() correctly;
no change needed
- apps/api/src/routes/admin/authProvider.ts: added trailing newline
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Rebase introduced duplicate import from ./routes/admin/authProvider.js
and duplicate route registration. Removed duplicates since the correct
import is from ./routes/authProvider.js.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The rebase introduced incompatible test code from the pre-merge GRO-388
commit. Replaced with the canonical test file from main to ensure tests
pass and reflect the actual router implementation.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Fix bug where super users granted via Staff UI were blocked from
admin routes because requireRole("manager") checked role before
isSuperUser. Changed to requireRoleOrSuperUser("manager") so
super users bypass the manager-role check.
Also adds 7 unit tests for requireRoleOrSuperUser middleware
covering: manager access, super user bypass, non-super-user
blocking, and multi-role scenarios.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Implement admin API endpoints for managing auth provider configuration:
- GET /api/admin/auth-provider — get current config (secret redacted)
- PUT /api/admin/auth-provider — create or update provider config
- POST /api/admin/auth-provider/test — validate via OIDC discovery endpoint
- DELETE /api/admin/auth-provider — remove DB config (falls back to env vars)
All endpoints are gated by requireSuperUser(). The clientSecret is
AES-256-GCM encrypted before DB write and always redacted on return.
Test-connection fetches /.well-known/openid-configuration and returns
metadata on success or error detail on failure.
Includes 16 unit tests covering all endpoints and error paths.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Add POST /api/setup/auth-provider/test endpoint for OOBE test connection
- Guard with same !superUser check as bootstrap endpoint
- Update SetupWizard to call /api/setup/auth-provider/test instead of
/api/admin/auth-provider/test (which requires auth session)
- Add trailing newline at EOF in setup.ts
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Backend:
- GET /api/setup/status now returns showAuthProviderStep, authConfigExists,
and authEnvVarsSet to inform the frontend whether to show the step
- POST /api/setup/auth-provider: unauthenticated endpoint for first-time
auth provider configuration during OOBE; guarded by needsSetup check
(returns 403 after setup completes); encrypts clientSecret before storing
Frontend:
- SetupWizard fetches /api/setup/status on mount to determine if the
auth provider step is needed (fresh install with no DB config and no
OIDC env vars)
- When needed, inserts the Auth Provider step after Welcome, before
Business Name; includes full form with Test Connection button
- Endpoint is POST /api/admin/auth-provider/test for connection testing
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Syntax error: `))` was closing the arrow function body prematurely.
Change `)),` to `}),` to properly close the values-returning object.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Implements admin API endpoints for managing auth provider configuration.
All gated by requireSuperUser().
Endpoints:
- GET /api/admin/auth-provider - returns config with clientSecret=redacted
- PUT /api/admin/auth-provider - encrypts clientSecret before DB write
- POST /api/admin/auth-provider/test - validates OIDC discovery endpoint
- DELETE /api/admin/auth-provider - removes DB config
Fixes CTO review findings:
- PUT uses db.transaction() for atomic upsert (was non-atomic delete+insert)
- Rebased on latest main (drops stale GRO-404/406 commits)
- Added EOF newlines to authProvider.ts and authProvider.test.ts
Unit tests with 9 passing test cases covering all endpoints and RBAC.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Merges the services toggle switch (GRO-404) and devFetch early-return guard fix (GRO-406). Admin merge due to known infra deploy issue (GRO-311) — all code checks pass, CTO + QA approved.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Refactor auth initialization to support three config states:
1. DB config (auth_provider_config table) — primary source
2. OIDC_* env vars — fallback when DB config absent
3. Unconfigured — graceful handling when neither source available
Changes:
- auth.ts: Add initAuth() async factory, getAuth() getter, getAuthPromise()
- index.ts: Call initAuth() at startup before serve()
- middleware/auth.ts: Use getAuth() instead of direct auth import
- Add auth.test.ts covering all three config states
Preserves AUTH_DISABLED=true behavior and original hairpin NAT pattern.
Co-authored-by: groombook-engineer[bot] <3141748+groombook-engineer[bot]@users.noreply.github.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
The if (!getDevUser()) return at install time prevented the interceptor
from installing on app startup before any dev user was selected. Since
the per-call check already handles the no-dev-user case correctly,
the early-return guard is unnecessary and breaks the interceptor install
in deployed dev builds.
Co-Authored-By: Paperclip <noreply@paperclip.ing>