You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is a stacked PR based on #1303 (feat/oauth-reorg).
Review only the commits and diff above that branch.
Do not merge this PR before #1303.
After #1303 merges, this branch must be rebased onto main and the PR base changed to main.
Important
This first SSO implementation supports shared sessions only when strictAuth=false.
It does not change or bypass authenticate_strict.
SSO is limited to APIs resolved by the scoped routing plane to the same CNC databaseId.
The current constructive-hub DB gitlink (ffb2c60dfb69a883f3639ba886cb18372330f122) was inspected first but is too old for the rebased CNC contract: it lacks the current scoped-routing/internal-secrets and sessions/auth-settings shape. 58d6c350… is the already validated compatible DB revision used by the rebased OAuth branch, so this PR does not restore legacy services_public fallbacks or introduce a CNC DB change.
What changed
Allows an OAuth login initiated on an Auth API to redirect to another registered API only when scoped routing resolves it to the same explicit databaseId.
Reuses the configured routing schema, cached routing pool, resolveRoute, and routeToApiStructure; no copied SQL, default-database fallback, or broad origin allowlist.
Binds the normalized redirect URI plus Auth and target database/API/origin scopes into signed OAuth state.
Re-resolves the target during callback and fails with INVALID_STATE if routing changed.
Keeps provider callbacks and error redirects on the Auth API.
Reuses existing auth settings and cookie helpers for the parent-domain constructive_session; OAuth state/PKCE remain host-only under /auth.
Adds production scoped-routing fixtures for Auth/API1/API2 in database A and a rejected API in database B.
Documents the parent-domain trust boundary, cookie migration concerns, CORS/CSRF separation, device-cookie coupling, and unsupported SSO modes.
Trust and security boundary
This is not a parent-domain suffix allowlist. A cross-origin target must be HTTP(S), contain no URL userinfo, use HTTPS in production, resolve as an API through the configured scoped routing plane, and have the same databaseId as the Auth API. Similar or unregistered hosts, non-API routes, missing database IDs, and other tenants are rejected.
A parent-domain session cookie is safe only when every subdomain that can receive it is inside the same security boundary and uses HTTPS. Deployments must avoid simultaneous host-only and domain cookies with the same name during migration.
Validation
Passed locally:
pnpm install --frozen-lockfile --offline
pnpm run build
pnpm --filter @constructive-io/oauth test --runInBand — 57 tests
pnpm --filter @constructive-io/express-context test --runInBand — 17 tests
pnpm --filter @constructive-io/graphql-server test --runInBand — 154 tests
pnpm --filter graphql-server-test build
pnpm --filter graphql-server-test test --runInBand __tests__/oauth-scoped-routing.integration.test.ts — 7 tests against local PostgreSQL/MinIO
Full graphql-server-test suite — 147 tests / 2 snapshots
Legacy-config ESLint over every changed TypeScript file, without Prettier noise
git diff --check
The complete fake-provider browser golden path remains intentionally in constructive-hub. A follow-up Hub PR must pin this SSO head SHA and constructive-db 58d6c3503d4b08a616992f4b0a954f962a56d0d6.
This run was triggered with workflow_dispatch because the workflow’s pull_request.branches filter only watches main and v1; a correctly stacked PR targeting feat/oauth-reorg does not auto-trigger it.
Review-noise cleanup pushed as 449e69131c75d8c02d7a86a8d6ca4bd8ebf8cb8e.
Removed unrelated import reordering, line wrapping, Express import spacing, and the unrelated OAuth catch typing refactor. The aggregate PR diff now preserves the parent branch style and contains only the SSO-related changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Caution
This is a stacked PR based on #1303 (
feat/oauth-reorg).Review only the commits and diff above that branch.
Do not merge this PR before #1303.
After #1303 merges, this branch must be rebased onto
mainand the PR base changed tomain.Important
This first SSO implementation supports shared sessions only when
strictAuth=false.It does not change or bypass
authenticate_strict.SSO is limited to APIs resolved by the scoped routing plane to the same CNC
databaseId.Stack and compatibility
c46d4c062b68a326f533f12cd72d1373c2876eb658d6c3503d4b08a616992f4b0a954f962a56d0d6The current constructive-hub DB gitlink (
ffb2c60dfb69a883f3639ba886cb18372330f122) was inspected first but is too old for the rebased CNC contract: it lacks the current scoped-routing/internal-secrets and sessions/auth-settings shape.58d6c350…is the already validated compatible DB revision used by the rebased OAuth branch, so this PR does not restore legacyservices_publicfallbacks or introduce a CNC DB change.What changed
databaseId.resolveRoute, androuteToApiStructure; no copied SQL, default-database fallback, or broad origin allowlist.INVALID_STATEif routing changed.constructive_session; OAuth state/PKCE remain host-only under/auth.Trust and security boundary
This is not a parent-domain suffix allowlist. A cross-origin target must be HTTP(S), contain no URL userinfo, use HTTPS in production, resolve as an API through the configured scoped routing plane, and have the same
databaseIdas the Auth API. Similar or unregistered hosts, non-API routes, missing database IDs, and other tenants are rejected.A parent-domain session cookie is safe only when every subdomain that can receive it is inside the same security boundary and uses HTTPS. Deployments must avoid simultaneous host-only and domain cookies with the same name during migration.
Validation
Passed locally:
pnpm install --frozen-lockfile --offlinepnpm run buildpnpm --filter @constructive-io/oauth test --runInBand— 57 testspnpm --filter @constructive-io/express-context test --runInBand— 17 testspnpm --filter @constructive-io/graphql-server test --runInBand— 154 testspnpm --filter graphql-server-test buildpnpm --filter graphql-server-test test --runInBand __tests__/oauth-scoped-routing.integration.test.ts— 7 tests against local PostgreSQL/MinIOgraphql-server-testsuite — 147 tests / 2 snapshotsgit diff --checkThe complete fake-provider browser golden path remains intentionally in constructive-hub. A follow-up Hub PR must pin this SSO head SHA and constructive-db
58d6c3503d4b08a616992f4b0a954f962a56d0d6.