fix(device): surface SSO errors on /device and fix CLI null-account crash on external-SSO login#36781
Merged
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #36781 +/- ##
=======================================
Coverage 85.75% 85.75%
=======================================
Files 4546 4546
Lines 222191 222198 +7
Branches 40927 40931 +4
=======================================
+ Hits 190535 190545 +10
+ Misses 27988 27985 -3
Partials 3668 3668
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
The prior fix set errMsg(ssoError) and router.replace(pathname) in the same effect run. router.replace stripped the param and re-rendered, wiping the just-set state and resetting view to code_entry — so the SSO error never showed and the page silently bounced back to code entry. Drive sso_error from a dedicated terminal error_sso view (matching the existing error_* views), leave the non-sensitive param in the URL, and map the code to friendly copy via a dispatch table. Add regression tests, including one asserting router.replace is not called on mount.
Replace the standalone error_sso terminal view with an inline banner derived directly from the sso_error query param on the code-entry screen. The banner is pure-rendered from the URL (no effect, no extra state), so it survives re-render/remount and the error is shown on the main page instead of a separate view.
The poll-success contract sends account: null for the external_sso subject type. login guarded only !== undefined and then read .id on null, crashing the device-flow SSO login. Normalize null/undefined to one path so the external_subject branch is taken.
Replace per-consumer 'const account = s.account ?? undefined' normalization with direct truthy checks on s.account. Handles both null and undefined in one guard, drops duplicated coercion across renderLoggedIn/bundleFromSuccess. Fixes external_subject being skipped on SSO logins (account: null), which caused the token to be stored under the 'default' key instead of the SSO email. Pin via store-key assertion in the sso test.
wylswz
approved these changes
May 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two related fixes for the device-authorization (RFC 8628) SSO path.
Web — surface SSO errors on
/deviceWhen the SSO callback (SAML ACS / OIDC / OAuth2) rejects a login, the enterprise backend redirects the browser to
/device?sso_error=<code>. The/devicepage readsso_verifiedfrom the URL but notsso_error, so the failure was silent — the user landed back on a blank code-entry form with no feedback.The page now maps the
sso_errorcode to friendly copy (ssoErrorCopy()dispatch table) and shows it as an inline banner on the code-entry page. The banner is derived directly from the URL param (no effect, no extra state), so it survives re-render/remount and shows the error on the main page rather than a separate full-screen view. The param is intentionally not scrubbed (non-sensitive error code; keeps the banner stable instead of being wiped byrouter.replace).CLI — fix crash on external-SSO device login
The poll-success contract sends
account: nullfor theexternal_ssosubject type (external SSO has no console account).difyctl auth loginguarded only!== undefinedand then read.idon the result, crashing the SSO login withnull is not an object (evaluating 'account.id'). Normalizednull/undefinedto a single path so theexternal_subjectbranch is taken, and widened thePollSuccess.accounttype toPollAccount | nullto match the contract. Updated the device-flow mock's SSO scenario to sendaccount: nullso the test reproduces the real server response (the gap that hid the bug).Tests
page-terminal.spec.tsx— banner shows with friendly copy, code-entry stays visible, raw backend code not surfaced, no scrub-on-mount regression. 11/11 pass.login.test.ts— SSO case reproduces the crash pre-fix, passes post-fix;whoami/statusalready cover both subject types. Full CLI suite 817/817 pass.Checklist
make lint && make type-check(backend) andcd web && pnpm exec vp staged(frontend) to appease the lint gods