fix(auth): link bunyip_sub onto an existing verified-email account on SSO (LC-588) #551

Merged
nrupard merged 1 commit from fix/LC-588-sso-email-link into main 2026-07-16 19:58:20 +02:00
Owner

Summary

Fixes LC-588 (origin BUNYIP-378): bunyip SSO sign-in failed with sso_error=internal for anyone who already had a Let's Chat account on the same email.

Root cause (pinned from the server log)

ERROR bunyip_sso: resolve_or_provision_user failed error=... (code: 2067) UNIQUE constraint failed: users.email

The OIDC handshake succeeds. resolve_or_provision_user (routes/bunyip_sso.rs) looks the user up by bunyip_sub, misses (a pre-SSO password account has bunyip_sub = ''), then create_user_from_bunyip INSERTs a new row with the same email → UNIQUE(users.email) violation → internal error.

Fix

Adopt the existing account by linking, instead of duplicate-inserting:

  • New db::auth::link_bunyip_sub - UPDATE users SET bunyip_sub = ? ... WHERE id = ? AND (bunyip_sub IS NULL OR bunyip_sub = '') AND COALESCE(is_bot, 0) = 0, returning whether a row was linked. The guards mean it never overwrites a row already linked to another subject, and never links a bot.
  • In resolve_or_provision_user, after the bunyip_sub miss and before auto-provision: if userinfo.email_verified == Some(true) and a non-empty email is present, find_user_id_by_email (already excludes banned) → on a hit, link_bunyip_sub → on success, sign in as that user.

Security

Linking by email is gated on email_verified (bunyip issues it from the user's real verified status) so a bunyip account cannot claim an unverified address to hijack a local account. Banned/bot/already-linked rows are excluded. A verified-email collision already linked to a different subject is NOT auto-merged - it falls through (a genuine identity conflict).

Testing

just check green (server default + saas + desktop + clippy -D warnings + fmt). just test passes except 2 pre-existing failures in server/tests/last_visited.rs (home-dashboard rendering) that are unrelated to this change and already fail on main - confirmed by re-running them with this change stashed. Flagging separately; not introduced here.

## Summary Fixes LC-588 (origin BUNYIP-378): bunyip SSO sign-in failed with `sso_error=internal` for anyone who already had a Let's Chat account on the same email. ## Root cause (pinned from the server log) ``` ERROR bunyip_sso: resolve_or_provision_user failed error=... (code: 2067) UNIQUE constraint failed: users.email ``` The OIDC handshake succeeds. `resolve_or_provision_user` (`routes/bunyip_sso.rs`) looks the user up by `bunyip_sub`, misses (a pre-SSO password account has `bunyip_sub = ''`), then `create_user_from_bunyip` INSERTs a new row with the same email → `UNIQUE(users.email)` violation → internal error. ## Fix Adopt the existing account by linking, instead of duplicate-inserting: - New `db::auth::link_bunyip_sub` - `UPDATE users SET bunyip_sub = ? ... WHERE id = ? AND (bunyip_sub IS NULL OR bunyip_sub = '') AND COALESCE(is_bot, 0) = 0`, returning whether a row was linked. The guards mean it never overwrites a row already linked to another subject, and never links a bot. - In `resolve_or_provision_user`, after the `bunyip_sub` miss and before auto-provision: if `userinfo.email_verified == Some(true)` and a non-empty email is present, `find_user_id_by_email` (already excludes banned) → on a hit, `link_bunyip_sub` → on success, sign in as that user. ## Security Linking by email is gated on `email_verified` (bunyip issues it from the user's real verified status) so a bunyip account cannot claim an unverified address to hijack a local account. Banned/bot/already-linked rows are excluded. A verified-email collision already linked to a different subject is NOT auto-merged - it falls through (a genuine identity conflict). ## Testing `just check` green (server default + saas + desktop + clippy `-D warnings` + fmt). `just test` passes except **2 pre-existing failures in `server/tests/last_visited.rs`** (home-dashboard rendering) that are unrelated to this change and already fail on `main` - confirmed by re-running them with this change stashed. Flagging separately; not introduced here.
fix(auth): link bunyip_sub onto an existing verified-email account on SSO (LC-588)
All checks were successful
check-secrets / Nosey parker (push) Successful in 3s
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Kingfisher (push) Successful in 9s
check-secrets / Nosey parker (pull_request) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 6s
check-secrets / Kingfisher (pull_request) Successful in 9s
Check / clippy + fmt + tests (pull_request) Successful in 3m44s
Create release / Create release from merged PR (pull_request) Has been skipped
27b1df2972
Signing into Let's Chat via bunyip SSO failed with sso_error=internal for a user who already had a local account on the same email: resolve_or_provision_user missed the bunyip_sub lookup (a legacy password account has bunyip_sub=''), then create_user_from_bunyip INSERTed a duplicate-email row, tripping UNIQUE(users.email).

Adopt the existing account instead: when the bunyip_sub lookup misses but a local account has the same VERIFIED email, link the bunyip_sub to it and sign in as that user, rather than creating a duplicate. Gated on email_verified (bunyip sends it from the user's real verified status) so a bunyip account cannot claim an unverified address to hijack a local account. find_user_id_by_email already excludes banned rows; link_bunyip_sub only links a non-bot row not already linked to another subject.

Adds db::auth::link_bunyip_sub (a guarded UPDATE returning whether a row was linked) and the email-link branch in resolve_or_provision_user.

check green (default + saas + clippy + fmt). Note: server/tests/last_visited.rs has 2 pre-existing failures on main (home-dashboard, unrelated to this change; confirmed they still fail with this change stashed).

#LC-588

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard scheduled this pull request to auto merge when all checks succeed 2026-07-16 19:58:19 +02:00
nrupard deleted branch fix/LC-588-sso-email-link 2026-07-16 19:58:20 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!551
No description provided.