feat(sso): SAML/OIDC single sign-on#3911
Conversation
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis PR introduces end-to-end SSO support across the monorepo. A new ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@trigger.dev/build
trigger.dev
@trigger.dev/core
@trigger.dev/python
@trigger.dev/react-hooks
@trigger.dev/redis-worker
@trigger.dev/rsc
@trigger.dev/schema-to-json
@trigger.dev/sdk
commit: |
f0185b2 to
fbf8172
Compare
5a2cf4b to
e5012c1
Compare
fa71a08 to
19289bb
Compare
08a9e68 to
9d79e25
Compare
- vercel.onboarding: already-authenticated users whose domain requires SSO were redirected to /login/sso, which bounces authed users home and dropped the single-use Vercel code. Destroy the session first via authenticator.logout so /login/sso accepts them and the resume URL survives the round-trip (addresses PR #3911 review). - auth.sso.callback: sanitize redirectTo at the exit point (IdP-initiated values come from relay-state and never passed the host sanitizer) and attribute the referral source, matching the other auth callbacks. - ssoAuth: extract private early-return helpers (resolveSsoUserId, attachSsoIdentityBestEffort, provisionJitMembershipBestEffort, runPostAuthentication) and fail closed if the user can't be confirmed post-resolution instead of minting a session.
Vendor-neutral plugin contract plus the host wiring that consumes it. With no SSO plugin installed, everything degrades to a no-op fallback, so OSS deployments are unaffected. - Plugin contract (@trigger.dev/plugins) + lazy loader/fallback in internal-packages/sso: status, portal-link, enforce/JIT config, route-decision, begin/complete authorization, identity resolution, JIT evaluation, and periodic session validation. All methods return neverthrow Results; the fallback is fail-open. - Login: 'Sign in with SSO' entry + dedicated /login/sso flow and /auth/sso(.callback) routes, plus auto-discovery from magic-link/OAuth. - Org settings -> SSO page: plan-tier upsell, connection status, verified-domain list, enforcement + JIT provisioning + default-role configuration, and an admin-portal link dialog. - AuthUser carries an optional signed 'sso' marker; SSO-established sessions are periodically re-validated against the identity provider on a single-flight, throttled, fail-open basis and logged out only on an explicit invalid result. - SSO_ENABLED gate (default off) so the feature ships dark until its backing plugin is available; SSO_SESSION_REVALIDATION_INTERVAL_SECONDS controls the cadence.
ensureOrgMember's create + rbac.setUserRole + compensating delete are not transactional (the RBAC plugin writes on its own connection). The common single-failure case already recovers — a failed setUserRole deletes the row, so the next login retries cleanly. But if the compensating delete ALSO fails, the placeholder MEMBER row is orphaned and the findFirst no-op short-circuits every future login, stranding the user on the wrong role. When an existing membership is found and a JIT role is requested, complete the assignment if (and only if) the RBAC layer shows no role assigned. Gated on 'no role assigned' so it can never demote a deliberately-set role; best-effort so it never throws or rolls back a valid pre-existing membership. Addresses PR #3911 review.
The SSO controller is built with forceFallback: !SSO_ENABLED || SSO_FORCE_FALLBACK, so ssoController.isUsingPlugin() is true only when SSO_ENABLED is on AND a real plugin is loaded — it already encodes 'env var on + plugin available'. The UI gates were leading on isManagedCloud instead, which is neither necessary nor the intended signal (per review). - login button: drop the isManagedCloud host check; gate on isUsingPlugin() AND the hasSso global flag. hasSso is a DB-backed runtime kill switch for the SSO login button (toggleable without a redeploy), so it's kept. isUsingPlugin() is cheap and short-circuits before the flag fetch, so self-hosted/no-plugin pays nothing. - SSO settings page: drop isManagedCloud from both the loader and action gates; key both on isUsingPlugin() so config mutations require an active plugin too. - settings sidebar: drop isManagedCloud from the SSO menu item so it's gated on isSsoUsingPlugin() alone, matching the loader/action and keeping the page discoverable via normal navigation on self-hosted. Addresses PR #3911 review.
SAML/OIDC Single Sign-On
Vendor-neutral SSO feature for Trigger.dev, built as a plugin contract + host wiring. With no SSO plugin installed, everything degrades to a no-op fallback — OSS / self-hosted deployments are completely unaffected.
Plugin contract + fail-open fallback
Login & sign-in flows
Org settings → SSO page
Delivers plan-tier upsell, connection status, verified-domain list, enforcement toggle, JIT provisioning config, default-role selection, and an admin-portal link dialog.
Session lifecycle & enforcement
Data model
A new UserSsoIdentity-style relation (Prisma schema + migration) linking host users to IdP identities, enabling the fast existing-user-by-IdP login path.