Skip to content

feat(auth): support tenant shared-session SSO - #1493

Open
Zetazzz wants to merge 4 commits into
feat/oauth-reorgfrom
feat/tenant-shared-session-sso
Open

feat(auth): support tenant shared-session SSO#1493
Zetazzz wants to merge 4 commits into
feat/oauth-reorgfrom
feat/tenant-shared-session-sso

Conversation

@Zetazzz

@Zetazzz Zetazzz commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

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 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.

Stack and compatibility

  • Parent OAuth SHA: c46d4c062b68a326f533f12cd72d1373c2876eb6
  • Tested constructive-db SHA: 58d6c3503d4b08a616992f4b0a954f962a56d0d6

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.

@Zetazzz
Zetazzz marked this pull request as ready for review July 28, 2026 16:44
@Zetazzz

Zetazzz commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

CI verification complete for head 9a07509cb305df66d8e81fc961e822d0dfb57673: the repository’s official CI tests workflow passed all 13 jobs: https://github.com/constructive-io/constructive/actions/runs/30379742801

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.

@Zetazzz

Zetazzz commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

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.

Official CI tests workflow passed all 13 jobs on the new head: https://github.com/constructive-io/constructive/actions/runs/30506390854

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants