Skip to content

Epic: Single Sign On#4751

Open
doc-han wants to merge 38 commits into
mainfrom
4621-full-sso-experience
Open

Epic: Single Sign On#4751
doc-han wants to merge 38 commits into
mainfrom
4621-full-sso-experience

Conversation

@doc-han

@doc-han doc-han commented May 14, 2026

Copy link
Copy Markdown
Contributor

Description

This PR Implements the Full SSO Experience epic.

  • Github & Google sign-in/sign-up (Microsoft deferred)
  • SSO sign-ups go through the same AccountHook as password sign-ups. ***
Login page with SSO buttons Register page with SSO buttons
image image
Signup confirmation page (SSO signup) Email collision flash on login
image image
SSO-only user tries password login Profile page. link/unlink provider
image image

Closes #4621

Validation steps

Setup

  • Configure OAuth clients/app on a provider(Google or Github).
  • Set the redirect URL to <host>/authenticate/<provider>/callback where is google or github for now.
  • Set your CLIENT_ID and CLIENT_SECRET as env variables. eg. GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET.

Validation

Sign up via SSO

# Step Expected
1 New User → "Sign up with GitHub" Confirmation screen appears
2 Click Cancel No user created
3 Click Confirm Account created & Logged in, email has no password
4 Repeat with Google Same result

Sign in via SSO

# Step Expected
1 Existing SSO user → "Sign in with GitHub" Straight in, no confirmation screen
2 MFA user → SSO sign-in Routes to MFA validation page

Email collision

# Step Expected
1 Existing password account at you@domain.com + SSO sign-up with same email Redirected to login with "an account already exists, link from profile" message
2 Check DB (if you can) No user_identities row created

Link from /profile

# Step Expected
1 Click Link next to an unlinked provider → authorise Flash confirms link
2 Log out, sign in via that provider Succeeds

Unlink from /profile

# Step Expected
1 Account with password + SSO → click Unlink Identity removed
2 SSO-only account, unlink last identity Refused with "set a password first"
3 Forgot-password → set password → Unlink Succeeds

Forgot password (SSO-only)

# Step Expected
1 SSO-only account tries password login Flash "This account uses single sign-on"
2 Forgot password → set password Both password and SSO sign-in work

AI Usage

Please disclose whether you've used AI anywhere in this PR (it's cool, we just
want to know!):

  • I have used Claude Code
  • I have used another model
  • I have not used AI

You can read more details in our
Responsible AI Policy

Pre-submission checklist

  • I have performed an AI review of my code (we recommend using /review
    with Claude Code)
  • I have implemented and tested all related authorization policies.
    (e.g., :owner, :admin, :editor, :viewer)
  • I have updated the changelog.
  • I have ticked a box in "AI usage" in this PR

@doc-han doc-han linked an issue May 14, 2026 that may be closed by this pull request
@github-project-automation github-project-automation Bot moved this to New Issues in Core May 14, 2026
@codecov

codecov Bot commented May 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.42690% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.5%. Comparing base (7c23fff) to head (c5f9be4).
⚠️ Report is 33 commits behind head on main.

Files with missing lines Patch % Lines
lib/lightning_web/controllers/oidc_controller.ex 88.4% 13 Missing ⚠️
lib/lightning/config/bootstrap.ex 46.2% 7 Missing ⚠️
lib/lightning/auth_providers/cache_warmer.ex 60.0% 6 Missing ⚠️
...ning_web/live/profile_live/identities_component.ex 88.9% 5 Missing ⚠️
.../lightning_web/live/profile_live/form_component.ex 81.2% 3 Missing ⚠️
lib/lightning/accounts.ex 95.7% 2 Missing ⚠️
lib/lightning_web/components/sso_icons.ex 66.7% 2 Missing ⚠️
lib/lightning/auth_providers/github_handler.ex 85.7% 1 Missing ⚠️
lib/lightning/auth_providers/google_handler.ex 85.7% 1 Missing ⚠️
lib/lightning/auth_providers/handler.ex 96.8% 1 Missing ⚠️
... and 2 more
Additional details and impacted files
@@           Coverage Diff           @@
##            main   #4751     +/-   ##
=======================================
+ Coverage   90.3%   90.5%   +0.2%     
=======================================
  Files        442     444      +2     
  Lines      22540   22765    +225     
=======================================
+ Hits       20353   20605    +252     
+ Misses      2187    2160     -27     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@doc-han doc-han marked this pull request as ready for review May 25, 2026 08:51
@github-actions

Copy link
Copy Markdown

Now I have enough information to perform the security review. This PR adds SSO functionality (GitHub/Google OAuth providers, identity linking/unlinking, signup confirmation). Let me assess each check:

S0 (project scoping): The PR touches only user accounts, identities, sessions, and OAuth flows — none of which are project-scoped resources (no work with workflows, runs, dataclips, work orders, collections, project credentials, triggers, edges, or jobs). New queries (get_user_by_identity, list_user_identities) operate on instance-wide user data, which is correct.

S1 (authorization): New web entrypoints check authorization appropriately:

  • OidcController.link/2 (lib/lightning_web/controllers/oidc_controller.ex:39) gates on conn.assigns.current_user, redirecting unauthenticated users
  • IdentitiesComponent.handle_event("unlink-identity", ...) (lib/lightning_web/live/profile_live/identities_component.ex:18) operates only on socket.assigns.user (the current user from profile), so users cannot affect other accounts' identities
  • handle_sso_login (lib/lightning_web/controllers/oidc_controller.ex:204) explicitly refuses to auto-link to pre-existing email accounts, preventing identity-based account takeover
  • handle_sso_link (lib/lightning_web/controllers/oidc_controller.ex:174) refuses to link an identity already attached to a different account

S2 (audit trail): Per the agent guidance, S2 applies to project/instance configuration changes. This PR modifies user-level account state (registration, identity linking) — analogous to existing account operations (signup, password change) which do not write to Lightning.Auditing.Audit. SSO provider config is loaded from env vars at bootstrap, not mutated through the app, so no config-resource write occurs. No existing lib/lightning/accounts/audit.ex module exists for this domain.

Security Review ✅

  • S0 (project scoping): N/A — PR scope is user accounts, SSO identities, and OAuth flows; no project-scoped resources (workflows, runs, dataclips, collections, etc.) are touched.
  • S1 (authorization): New link/unlink/login paths gate on current_user, refuse to auto-link existing-email accounts (oidc_controller.ex:209), and reject identities already bound to another user (oidc_controller.ex:182), preventing identity-hijack/account-takeover.
  • S2 (audit trail): N/A — SSO provider configuration is env-driven (bootstrap.ex:506), and user/identity mutations are account-level operations, not project or instance configuration writes.

@theroinaochieng theroinaochieng moved this from New Issues to In review in Core May 26, 2026

@midigofrank midigofrank left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review — Epic: Single Sign On

Really nice work on this. A few change requests below before merge:

1. GitHub /user endpoint often returns no email — many GitHub logins will fail (high)

GithubHandler points userinfo_endpoint at https://api.github.com/user. That endpoint's email field is the user's public profile email, which is null for the majority of GitHub users (those who haven't set a public email) — even though the user:email scope is granted. extract_email/1 then returns nil{:error, :no_email} → "Could not retrieve your email."

To reliably obtain a (verified) email, GitHub requires a second call to https://api.github.com/user/emails and selecting the primary && verified entry. As written, GitHub SSO will silently fail for a large fraction of real users. (Confirmed reproducible against a test account with no public email set.)

2. Deployment documentation & env-var disambiguation (high)

DEPLOYMENT.md has no instructions for the new SSO env vars, and the variable names collide/overlap with existing ones — this will confuse operators:

  • GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET (new, SSO login) vs GITHUB_APP_ID / GITHUB_APP_CLIENT_ID / GITHUB_APP_CLIENT_SECRET (existing, DEPLOYMENT.md L97-101). The latter is the GitHub App used for repo/version-control sync — a completely different feature with confusingly similar names. The docs must clearly separate "GitHub App (project version control)" from "GitHub SSO (sign-in)".
  • GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET are already documented (DEPLOYMENT.md L363-377, "Google Oauth2") for the existing DB-backed auth-provider path, whose callback is /authenticate/callback. The new GoogleHandler reuses the same env var names but with a different callback (/authenticate/google/callback). Please clarify whether these are intentionally shared or a collision, and document the redirect URI difference.

Please add a dedicated "Single Sign-On (SSO)" section to DEPLOYMENT.md covering: which provider vars enable SSO, the exact redirect/callback URLs (/authenticate/<provider>/callback), and an explicit note distinguishing these from the GitHub App and the older Google OAuth2 settings. The .env.example comments are a good start but operators rely on DEPLOYMENT.md.

3. Migration is irreversible — mix ecto.rollback will raise (medium)

20260515133933_allow_null_hashed_password.exs:

def change do
  alter table(:users) do
    modify :hashed_password, :string, null: true
  end
end

modify/3 inside change/0 is only reversible when the :from option is given. Without it, a rollback raises Ecto.MigrationError. Use modify :hashed_password, :string, null: true, from: {:string, null: false} (or split into up/down).

4. Provider emails are trusted without a verification check (medium)

New-account provisioning trusts userinfo["email"] as a proven-owned address. Google's userinfo includes an email_verified claim; GitHub's /user/emails includes verified. Neither is checked. An attacker controlling a provider account with an unverified email could provision a Lightning account under an address they don't own. (The collision path protects existing accounts, so this is limited to new-account creation, but it's a cheap, standard hardening step.)

5. SSO button click target is too small — only the inner link is clickable (medium)

On both the login and register pages, the SSO button is a <.button> wrapping an inner <a href={provider.url}>. Only the anchor (which wraps the inline-flex icon+text) is clickable — clicking the button's padding does nothing, so the user has to aim at the link text in the middle. The full button should be the click target. Make the link itself the styled element (e.g. <.button_link href={provider.url}> / <.link> styled as a button) rather than nesting an <a> inside <.button>. Note IdentitiesComponent already uses <.button_link> for its "Link" action — the same pattern would fix this.

6. Email-existence oracle on the login form (low / product decision)

The {:error, :sso_account} flash ("This account uses single sign-on...") and the collision message ("An account already exists for {email}...") both confirm to an unauthenticated visitor whether a given email is registered, and how. This is a deliberate UX tradeoff and common, but flagging it as a behavior change from the previous generic "Invalid email or password".

7. on_conflict: :nothing masks cross-account identity collisions (low)

Accounts.link_user_identity/3 and the duplicate AccountHook.link_identity/3 insert with on_conflict: :nothing, conflict_target: [:provider, :uid]. If (provider, uid) already belongs to a different user, the insert silently no-ops and returns {:ok, identity} with a nil id, so the caller believes the link succeeded. The controller's pre-checks make this a narrow race, but :nothing is hiding a real conflict — consider distinguishing "already linked to me" from "claimed by someone else" at the data layer.

8. Minor

  • defp display_name/1 is defined identically in OidcController and IdentitiesComponent; link_identity in AccountHook duplicates Accounts.link_user_identity/3. Consider consolidating.
  • CacheWarmer.execute/1's try/rescue _ -> [] is very broad and will swallow genuine config errors silently.
  • GithubHandler has no test file (GoogleHandler does) — given #1, a GitHub-specific userinfo test would be valuable.
  • Changelog not updated (PR checklist acknowledges this).

@theroinaochieng theroinaochieng requested review from lmac-1 and removed request for brandonjackson and theroinaochieng June 16, 2026 09:09
doc-han and others added 5 commits June 16, 2026 18:21
Replace invalid button-wrapping-anchor pattern with button_link, and
swap inline-flex/inline-block/align-middle mix for flex items-center
justify-center to fix icon and text baseline alignment.
Add explicit display_name/1 clauses for github and google so all
provider name display sites show GitHub/Google rather than relying
on String.capitalize. Route confirm_signup template through
display_name/1 to match the rest.

@lmac-1 lmac-1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Finished my UX review, looking good! I directly made a couple of changes to improve the following:

  1. Improved horizontal alignment of the "Sign in with X" buttons
  2. Handle capitalisation logic directly in display_name function in lib/lightning/auth_providers. capitalize is used there and we explicitly define "GitHub" so that it isn't displayed as "Github"

A slight nitpick with the following UX, but shouldn't block the PR as it's an existing issue:

  1. User has already logged in with SSO with GitHub or Google (or email..)
  2. User tries to register with the same email
  3. They see an error that says "This value should be unique"
    It would be better to have a more informational message here, such as "An account already exists with this email address". But this is a product decision and should be picked up in a separate issue.

Comment thread lib/lightning_web/controllers/user_registration_html/new.html.heex
Comment thread lib/lightning_web/controllers/user_session_html/new.html.heex Outdated
Comment thread lib/lightning_web/controllers/user_session_html/new.html.heex
Comment thread lib/lightning_web/controllers/user_registration_html/new.html.heex
Comment thread lib/lightning/auth_providers.ex
Comment thread lib/lightning_web/controllers/oidc_html/confirm_signup.html.heex
Comment thread lib/lightning_web/controllers/oidc_html/confirm_signup.html.heex

@elias-ba elias-ba 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.

Hey @doc-han, morning! Went through this one properly and honestly it's really solid work, the SSO flow is well thought out. The way you handle the login state is genuinely better than what I usually see, and I like that you check the email is verified before creating any account, and that a matching email doesn't get silently linked. Nice.

I spent a couple of focused hours on it, mostly checking whether any of this touches our OAuth credentials, the Google Sheets and Salesforce connections that jobs use, since that was my main worry going in. Good news, it doesn't. Those connections use the OAuth clients we set up in the UI and a different callback URL, and none of that is touched here. I also checked the modules you removed and confirmed they were already dead code with no live references on main, so that's a clean cleanup.

Couple of things I'd like us to sort before this goes live though, mostly so we start on the strongest footing:

  1. The Google section in DEPLOYMENT.md is a bit misleading. It says GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are also used by the older Google setup and tells people to register both callback URLs on the same client. But I couldn't find anywhere in the code on main that actually reads those two env vars, so that section looks like it's documenting something that isn't wired up anymore. On top of that the doc never really separates the credential connections from SSO, so someone who has a Google app for Sheets might read this and think they need to go change it, when they really don't. Could you either fix or drop that stale section, and make it clear that the credential connections are set up in the UI and aren't affected, and that the only new callback to add is the SSO one, and only if you turn SSO on?

  2. There's a behavior change for anyone already using SSO that needs a note in the changelog. Login used to match people by their email, now it needs a linked identity record, and there's no migration to backfill those. So anyone who used to sign in through their provider won't get logged in straight through anymore, they'll have to re-link from their profile once. If that's intended, and I think it's the right call for security, let's just call it out so self hosted folks aren't caught off guard.

Two smaller ones, not blockers:

  1. When you unlink a provider, the check for "do they still have another way to log in" and the actual delete aren't done as one operation. In theory a password-less user clicking unlink twice at once could remove both and lock themselves out. Probably worth tightening that up.

  2. In the email resolution code, when GitHub gives you back an email you mark it as verified without checking. It's fine because GitHub only exposes verified emails publicly, but a one line comment saying that would save the next person a double take.

Happy to jump on a call if any of it's unclear.

@doc-han

doc-han commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Hey @doc-han, morning! Went through this one properly and honestly it's really solid work, the SSO flow is well thought out. The way you handle the login state is genuinely better than what I usually see, and I like that you check the email is verified before creating any account, and that a matching email doesn't get silently linked. Nice.

I spent a couple of focused hours on it, mostly checking whether any of this touches our OAuth credentials, the Google Sheets and Salesforce connections that jobs use, since that was my main worry going in. Good news, it doesn't. Those connections use the OAuth clients we set up in the UI and a different callback URL, and none of that is touched here. I also checked the modules you removed and confirmed they were already dead code with no live references on main, so that's a clean cleanup.

Couple of things I'd like us to sort before this goes live though, mostly so we start on the strongest footing:

  1. The Google section in DEPLOYMENT.md is a bit misleading. It says GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are also used by the older Google setup and tells people to register both callback URLs on the same client. But I couldn't find anywhere in the code on main that actually reads those two env vars, so that section looks like it's documenting something that isn't wired up anymore. On top of that the doc never really separates the credential connections from SSO, so someone who has a Google app for Sheets might read this and think they need to go change it, when they really don't. Could you either fix or drop that stale section, and make it clear that the credential connections are set up in the UI and aren't affected, and that the only new callback to add is the SSO one, and only if you turn SSO on?
  2. There's a behavior change for anyone already using SSO that needs a note in the changelog. Login used to match people by their email, now it needs a linked identity record, and there's no migration to backfill those. So anyone who used to sign in through their provider won't get logged in straight through anymore, they'll have to re-link from their profile once. If that's intended, and I think it's the right call for security, let's just call it out so self hosted folks aren't caught off guard.

Two smaller ones, not blockers:

  1. When you unlink a provider, the check for "do they still have another way to log in" and the actual delete aren't done as one operation. In theory a password-less user clicking unlink twice at once could remove both and lock themselves out. Probably worth tightening that up.
  2. In the email resolution code, when GitHub gives you back an email you mark it as verified without checking. It's fine because GitHub only exposes verified emails publicly, but a one line comment saying that would save the next person a double take.

Happy to jump on a call if any of it's unclear.

Thanks @elias-ba,

  1. the SSO vars are now SSO_ prefixed (e.g. SSO_GOOGLE_CLIENT_ID) so there's no collision.
  2. I don't think there's any backfilling needed at this point. The old SSO was configured from inside the app without env vars. and there was no table being kept for their identities because we were doing a simple email match. if a user(which we approx. have none using this method of sign-in) is still using the old method, their simple email match should still work. our identity stuff only takes place when SSO envs are set.
  3. I don't know how practical this is but I've added a db lock so that concurrent unlinks wouldn't lock a user out.
  4. GitHub email. I actually do not just take the email that will be attached to the user profile, we actually make a request to github to the verified email attached to the profile (this was already flagged by @midigofrank and already taken care of)

1 similar comment
@doc-han

doc-han commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Hey @doc-han, morning! Went through this one properly and honestly it's really solid work, the SSO flow is well thought out. The way you handle the login state is genuinely better than what I usually see, and I like that you check the email is verified before creating any account, and that a matching email doesn't get silently linked. Nice.

I spent a couple of focused hours on it, mostly checking whether any of this touches our OAuth credentials, the Google Sheets and Salesforce connections that jobs use, since that was my main worry going in. Good news, it doesn't. Those connections use the OAuth clients we set up in the UI and a different callback URL, and none of that is touched here. I also checked the modules you removed and confirmed they were already dead code with no live references on main, so that's a clean cleanup.

Couple of things I'd like us to sort before this goes live though, mostly so we start on the strongest footing:

  1. The Google section in DEPLOYMENT.md is a bit misleading. It says GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are also used by the older Google setup and tells people to register both callback URLs on the same client. But I couldn't find anywhere in the code on main that actually reads those two env vars, so that section looks like it's documenting something that isn't wired up anymore. On top of that the doc never really separates the credential connections from SSO, so someone who has a Google app for Sheets might read this and think they need to go change it, when they really don't. Could you either fix or drop that stale section, and make it clear that the credential connections are set up in the UI and aren't affected, and that the only new callback to add is the SSO one, and only if you turn SSO on?
  2. There's a behavior change for anyone already using SSO that needs a note in the changelog. Login used to match people by their email, now it needs a linked identity record, and there's no migration to backfill those. So anyone who used to sign in through their provider won't get logged in straight through anymore, they'll have to re-link from their profile once. If that's intended, and I think it's the right call for security, let's just call it out so self hosted folks aren't caught off guard.

Two smaller ones, not blockers:

  1. When you unlink a provider, the check for "do they still have another way to log in" and the actual delete aren't done as one operation. In theory a password-less user clicking unlink twice at once could remove both and lock themselves out. Probably worth tightening that up.
  2. In the email resolution code, when GitHub gives you back an email you mark it as verified without checking. It's fine because GitHub only exposes verified emails publicly, but a one line comment saying that would save the next person a double take.

Happy to jump on a call if any of it's unclear.

Thanks @elias-ba,

  1. the SSO vars are now SSO_ prefixed (e.g. SSO_GOOGLE_CLIENT_ID) so there's no collision.
  2. I don't think there's any backfilling needed at this point. The old SSO was configured from inside the app without env vars. and there was no table being kept for their identities because we were doing a simple email match. if a user(which we approx. have none using this method of sign-in) is still using the old method, their simple email match should still work. our identity stuff only takes place when SSO envs are set.
  3. I don't know how practical this is but I've added a db lock so that concurrent unlinks wouldn't lock a user out.
  4. GitHub email. I actually do not just take the email that will be attached to the user profile, we actually make a request to github to the verified email attached to the profile (this was already flagged by @midigofrank and already taken care of)

@doc-han doc-han requested review from elias-ba and midigofrank June 22, 2026 15:31
@doc-han

doc-han commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

This PR adds Single Sign-On sign-in to Lightning, letting users authenticate with GitHub or Google instead of (or alongside) an email and password. It also lets users link and unlink providers from their profile. SSO sign-ups flow through the same account-creation path as password sign-ups, so provisioning stays consistent across both methods.

User-facing features

  1. Sign in with GitHub / Google options on both the login and register pages.
  2. Link/unlink - providers a new identities panel in profile settings where a user can attach or remove providers on their account.
  3. Guided account handling - if someone signs in with a provider whose email already belongs to an existing account, they're told to sign in with their existing method and link the provider, rather than being silently merged.
  4. Password-less accounts - users created through SSO can exist without a password.

Design

  • We now have one generic handler plus some small Github and Google config builders.
  • Providers are enabled by setting SSO_GITHUB_CLIENT_ID / SSO_GITHUB_CLIENT_SECRET and SSO_GOOGLE_CLIENT_ID / SSO_GOOGLE_CLIENT_SECRET and redirect/callback URLs are derived automatically from the host configuration.
  • Github email resolution. because GitHub's user endpoint frequently returns no public email, the primary verified address is fetched from the dedicated emails endpoint.

DB Changes

  • A new user_identities table (provider, uid, user) with a unique index on provider + uid.
  • The user password column made nullable, to support SSO-only users. (important)
  • A unique constraint enforcing at most one identity per provider per user.

Security

  • CSRF protection on the OAuth flow using a single-use nonce.
  • Ensuring verified emails. account creation only proceeds with a verified email from the provider
  • No automatic account linking. no more email matching accounting linking
  • Lockout protection. a password-less user cannot remove their last remaining identity

cc: @stuartc

@elias-ba elias-ba 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.

Hey @doc-han really huge work here. Thanks for your responses on my last review items. I have tested this and it works well for me. Approving now and really amazing work here

doc-han and others added 11 commits June 23, 2026 14:59
The SSO callback fetched the provider email before branching on
login-vs-link, so a GitHub user with a private email was blocked from
linking even though the link path never uses the email. Move email
resolution into the login path and assert linking succeeds without one.
get_userinfo/2 used the bang OAuth2.Client.get!, so a provider-side HTTP
error during the userinfo step raised out of the SSO callback and became
a 500. Switch to the non-bang get/2 and return a tagged tuple, matching
get_token/2, so the controller redirects with "Authentication failed".
maybe_resolve_email stamped email_verified: true whenever userinfo
carried an email, asserting a verification status the userinfo endpoint
never provides. Consult the authoritative /user/emails endpoint instead
and set email_verified from the address's actual verified flag, failing
closed when the endpoint can't be reached.
A Repo.delete failure during unlink rolled back as :not_linked, so the
user was told the identity wasn't linked when it was — the deletion just
failed. Roll back with :delete_failed so it surfaces the accurate
"could not unlink" message instead.
can_remove_identity? counted all other identities just to test for at
least one. Repo.exists? short-circuits with a LIMIT 1 and matches the
idiom used elsewhere in the module.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

Full SSO Experience

6 participants