Skip to content

feat(auth): allow portal users to sign in via SSO#264

Merged
mortondev merged 3 commits into
mainfrom
feat/portal-user-sso
Jun 19, 2026
Merged

feat(auth): allow portal users to sign in via SSO#264
mortondev merged 3 commits into
mainfrom
feat/portal-user-sso

Conversation

@mortondev

@mortondev mortondev commented Jun 18, 2026

Copy link
Copy Markdown
Member

What

Let Portal Users (role: user) sign in through the workspace SSO connection, gated on a verified domain. Team roles (admin/member) — reached only via bootstrap or a deliberate invite — keep SSO unconditionally.

Why

Closes #263. A workspace run as a private/internal tool authenticates everyone through one IdP (e.g. Microsoft Entra) but keeps most staff as restricted portal users. Those users were offered "Continue with SSO" (verified-domain routing is role-blind) yet rejected at the callback, because portal SSO was gated on a portalConfig.oauth.sso flag no UI ever set — a sign-in loop.

How

  • isAuthMethodAllowed treats SSO as an enabled method for every role; role governs authorization, not whether the method exists.
  • The eligibility gate lives at the OAuth callback (handleCallbackPolicyCleanup), where the IdP email is known: a portal user (role: 'user') signing in via SSO must be at a verified domain (DNS-proven sso_verified_domain), else the session is revoked. Team roles are exempt — they're only granted via bootstrap or invitation, so their SSO stays unconditional (the first-admin bootstrap and invited cross-domain members both depend on this).
  • Aligns the policy with the surfacing (lookupAuthMethodsFn only offers SSO on a verified-domain match) and closes the direct /sign-in/oauth2 start that bypasses that routing.

Safety

  • No privilege change: allowing the SSO method for a role doesn't change the role; assignment stays gated (invite / verified-domain provisioning).
  • A non-verified IdP identity can only reach a portal session via SSO, and that path is now blocked; the first-admin bootstrap (no admin, no verified domain yet) still works.
  • Account linking unchanged (SSO is a trusted provider).

Review

All Codex findings addressed; threads resolved.

SSO is allowed for every role at the policy layer; verified-domain
surfacing and provider registration gate who is offered it, while role
governs authorization rather than authentication. This unblocks
verified-domain portal users on a private or internal workspace who were
offered SSO but rejected at the callback.

Refs #263

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 371ce92130

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/lib/server/auth/auth-restrictions.ts
A direct /sign-in/oauth2 start bypasses the verified-domain routing the
login UI applies, and the OAuth-start path has no email for the
pre-check to gate on. The callback cleanup is therefore the gate: a
portal user (role 'user') signing in via SSO must be at a verified
domain. Team SSO stays unconditional (those roles are assigned
deliberately). Addresses review feedback on #264.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f231ba93ae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/lib/server/auth/hooks.ts Outdated
mortondev added a commit that referenced this pull request Jun 18, 2026
handleSsoCallbackAfter (bootstrap-admin promotion + lastSsoSignInAt stamp)
now skips SSO callbacks whose email is not at a verified domain, matching
the gate handleAutoProvisionAfter already applies. Without it, a
non-verified-domain user in an adminless workspace was promoted to admin
before the verified-domain rejection in handleCallbackPolicyCleanup — which
revokes only the session and leaves the role write behind, so a later
password/magic-link login would be admin.

Addresses Codex P1 review feedback on #264.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a7dbb6a6c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/lib/server/auth/hooks.ts Outdated
@mortondev

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: a7dbb6a6c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@mortondev mortondev force-pushed the feat/portal-user-sso branch from a7dbb6a to 5f44a4b Compare June 18, 2026 21:05
Extract the inline portal-SSO verified-domain check into a reusable isSsoBlockedForRole predicate (sibling of isHardBound) and dedupe the three callback reject branches behind a shared blockSignIn helper. Reuse the shared isTeamMember role helper instead of an inline role check.

Move the SSO gate above the principal-row guard so the email-driven policy fails closed when no principal row exists.

Add tests for portal SSO at an enforced (Require SSO) domain and for the no-principal-row case.

Claude-Session: https://claude.ai/code/session_01J4VVqUDnPRSkaxLeFEaHpZ
@mortondev mortondev merged commit 5e39fc3 into main Jun 19, 2026
7 checks passed
@mortondev mortondev deleted the feat/portal-user-sso branch June 19, 2026 07:41
@mortondev mortondev mentioned this pull request Jun 19, 2026
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.

Bug: Auth Method Not Allowed

1 participant