Skip to content

feat(sso): OSS gated surface for enterprise SSO (OIDC) (#32) - #1303

Merged
dolho merged 6 commits into
devfrom
feature/sso-oidc
Jul 3, 2026
Merged

feat(sso): OSS gated surface for enterprise SSO (OIDC) (#32)#1303
dolho merged 6 commits into
devfrom
feature/sso-oidc

Conversation

@dolho

@dolho dolho commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What

OSS-side, entitlement-gated surface for enterprise SSO via OIDC. All SSO logic lives in the private submodule (companion: trinity-enterprise#36); OSS carries only the gated UI + the submodule bump.

Related to trinity-enterprise#32.

Changes

  • Login.vue — "Sign in with <IdP>" buttons, shown only when the sso feature is entitled and a provider is enabled; handles the OIDC callback fragment (/login#sso=ok|mfa|error) and reuses the existing 2FA challenge UI when the IdP login still needs a local second factor.
  • stores/auth.jscompleteSsoLogin() (reuses _finalizeLogin / _setMfaChallenge) + fetchSsoProviders() (empty in OSS-only builds — endpoint 404s).
  • Settings.vue — admin-gated SSO tab → new SsoPanel.vue (provider CRUD + connectivity test + policy). Gated by enterpriseStore.isEntitled('sso'), same pattern as the 2FA tab.
  • Submodule bump to the SSO module commit.
  • docs — architecture enterprise-modules row + requirements §40.

Why no backend/dep changes

python-jose + httpx are already in the backend image, and the mint (create_access_token), whitelist/default_role (#314), and mfa_gate seams already exist — so OSS needs no Python or Dockerfile change.

Notes

  • Stacked on feat/5-2fa-totp (reuses the OSS mfa_gate + 2FA challenge surface) — base is the 2FA branch so the diff is SSO-only. Retarget to dev once 2FA lands; the submodule pointer also settles once both 2FA branches merge.
  • Frontend build/e2e validated in CI (ui label).

🤖 Generated with Claude Code

dolho and others added 4 commits June 19, 2026 10:24
OSS side of two-factor auth (enterprise issue #5). All IP (TOTP verify,
secret store, recovery codes, policy) lives in the private trinity-enterprise
submodule; this PR adds only the edition-agnostic seam + the entitlement-gated
Vue surface, consistent with the open-core pattern (#847).

Backend seam:
- services/mfa_gate.py — single hook the enterprise provider registers into.
  No provider (OSS-only build) → gate_login() returns None → login unchanged.
  Fail-open on provider error (never lock everyone out of login).
- dependencies.py — create_mfa_challenge_token / decode_mfa_challenge; a
  challenge-scoped token is rejected as a session token in get_current_user
  and decode_token.
- routers/auth.py — /token and /api/auth/email/verify consult mfa_gate after
  the first factor; when a second factor is required they return a short-lived
  challenge token instead of an access token (audited as mfa_challenge_issued).
- models.py — Token gains optional mfa_required / challenge_token fields.
- Dockerfile — pyotp (used by the enterprise module running in this image).

Frontend (gated by `2fa` in GET /api/settings/feature-flags):
- Settings → Security tab (TwoFactorPanel): self-service enroll/confirm/
  disable/recovery; admin-only org policy.
- Login.vue: second-factor step (verify + forced-enroll) after the password/
  email factor; QrCode.vue renders the otpauth URI (graceful manual-key
  fallback). auth store carries the challenge through to the real token.

OSS-only builds: no Security tab, no login step, /api/enterprise/2fa/* → 404.

Related to Abilityai/trinity-enterprise#5

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
frontend-build npm ci failed — package.json added qrcode@^1.5.4
but package-lock.json was not regenerated, leaving it out of sync.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Companion to trinity-enterprise#36. OSS carries only the entitlement-gated
surface; all SSO logic lives in the private submodule.

- Login.vue: "Sign in with <IdP>" buttons (shown only when the `sso` feature is
  entitled and a provider is enabled), plus OIDC callback-fragment handling
  (`/login#sso=ok|mfa|error`) — reuses the existing 2FA challenge UI when the
  IdP login still requires a local second factor.
- stores/auth.js: completeSsoLogin() (reuses _finalizeLogin / _setMfaChallenge)
  + fetchSsoProviders() (empty in OSS-only builds — endpoint 404s).
- Settings.vue: admin-gated "SSO" tab → SsoPanel.vue (provider CRUD + test +
  policy). Gated by enterpriseStore.isEntitled('sso'), same as the 2FA tab.
- Bump enterprise submodule to the SSO module commit.
- docs: architecture enterprise-modules row + requirements §40 (SSO/OIDC).

No new backend dependency (python-jose + httpx already in the image) and no
OSS Python changes — the mint/whitelist/mfa seams already exist.

Stacked on feat/5-2fa-totp (reuses the OSS mfa_gate + 2FA challenge surface).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pulls in the email_verified / issuer-pinning / login-CSRF fixes
(trinity-enterprise 87c8f97). OSS gated surface unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- enterprise gitlink: keep dev's pointer (f1c8caee) — the SSO module (ent#36)
  already reached enterprise main inside the 2FA squash; the branch's stale
  pointer (87c8f975, side branch) must not roll dev back
- dependencies.py: parent-squash echo; PR doesn't touch it -> dev's version
- Login.vue / Settings.vue / auth.js: dev's versions + the SSO-only delta
- architecture.md / requirements.md: dev's versions; the PR's module-catalog
  row and §40 are dropped — enterprise module designs moved to the private
  repo (ent#45) and the enterprise-docs-guard now fails on enterprise_sso/SCIM
  tokens; design docs live in trinity-enterprise/docs
@vybe
vybe changed the base branch from feat/5-2fa-totp to dev July 3, 2026 08:48
@vybe
vybe requested a review from AndriiPasternak31 as a code owner July 3, 2026 08:48

@vybe vybe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated via /validate-pr after resolving the dev merge: effective diff is now purely additive frontend (+235: SsoPanel.vue, Login.vue SSO buttons + #sso= callback, auth.js completeSsoLogin/fetchSsoProviders, Settings SSO tab). Key resolution decisions: (1) enterprise gitlink keeps dev's pointer f1c8caee — the SSO module (ent#36) already reached enterprise main via the 2FA squash, and dev's copy includes a newer JIT-provisioning fix (attribution actor); the branch's stale side-branch pointer would have rolled dev back. (2) The PR's architecture.md module-catalog row + requirements §40 are dropped — enterprise module designs relocated to the private repo (ent#45) and enterprise-docs-guard now fails on enterprise_sso/SCIM/two_factor tokens; the design doc belongs in trinity-enterprise/docs. All SFCs compile; security greps clean. Cross-tracker ent#32 needs a manual status-in-dev bump after merge.

@dolho
dolho merged commit 522ef1d into dev Jul 3, 2026
18 checks passed
pull Bot pushed a commit to bryanwills/trinity that referenced this pull request Jul 9, 2026
…bilityai#1447)

test_submodule_registers_audit_not_sso pinned the enterprise submodule's
__init__.py to "no .sso import" — a stale assertion from the Abilityai#910/Abilityai#941 era
when only the removed SSO PoC stub existed. Real SSO-OIDC (Abilityai#32) has since
landed via Abilityai#1303, so the pin failed on every entitled clone with the
submodule mounted (CI is unaffected — it skips when the submodule is absent).

Update the pin to the current contract:
- keep the direct register_module("audit") assertion (UI-gating module)
- replace the two .sso-absence assertions with presence checks on
  `from .sso import register as register_sso` + `register_sso(app)`
  (SSO self-registers its feature_id inside .sso.register, so
  register_module("sso") is intentionally not called in __init__.py)
- rename ..._not_sso -> ..._and_sso and refresh the docstring

Verified against the mounted submodule: passes with it, skips cleanly without.

Fixes Abilityai#1447

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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