Skip to content

Feature ETP-4958: Route SSO login through caller-owned continuation - #137

Open
sebastianbarrozo wants to merge 1 commit into
epic/ETP-3504from
feature/ETP-4958
Open

Feature ETP-4958: Route SSO login through caller-owned continuation#137
sebastianbarrozo wants to merge 1 commit into
epic/ETP-3504from
feature/ETP-4958

Conversation

@sebastianbarrozo

Copy link
Copy Markdown
Collaborator

ETP-4958 — SSO login on the invitation page never resumes the invitation flow

https://etendoproject.atlassian.net/browse/ETP-4958

Symptom

Accepting a company invitation as an existing account via SSO (Google) authenticated the user but left the login form on screen — nothing appeared to happen and the invitation was never consumed. Email + password on the very same screen worked correctly.

Root cause

LoginStep is a shared authentication surface with two independent authentication branches — handleLogin (password) and handleSsoProviderLogin (SSO) — and each carried its own copy of the post-authentication continuation.

ETP-4894 introduced the caller-owned onAuthenticated prop so a consumer can take over what happens after login instead of the default environment routing. InviteAcceptancePage passes it and depends on it to flip existingAuthenticated and render the acceptance action. Only the password copy was updated; the SSO branch kept the original call:

handleAuthSuccess(data.token, data.account, { authMethod: 'sso' });

So an SSO login persisted the token to localStorage and then stopped. existingAuthenticated stayed false, the page re-rendered the same LoginStep, and the user was stranded with a valid session and an unconsumed invitation token.

The invitation page passes no routeByEnvironments, so the failure surfaced as a silent dead end rather than a wrong redirect. A caller passing both props would additionally have been navigated out of its own flow.

The underlying defect is the duplicated contract: two copies that must be hand-synchronised, where the second copy is where the bug landed.

Changes

File Change
src/onboarding/postAuth.js New. completeAuthentication() — the single post-auth continuation: persist auth state → route only when the caller has not taken ownership → hand over to the caller.
src/onboarding/steps/LoginStep.jsx Both branches now call the helper, so they can no longer diverge. Added the missing onAuthenticated entry to the handleSsoProviderLogin dependency array — it otherwise captured a stale callback for any caller that memoizes config.
test/postAuthContinuation.test.js New. 7 behavioural tests for the contract.
test/onboardingOwnership.test.js Removed the source-text assert.match() regression test (see below).
docs/feedback.md Entry corrected to the real root cause and symptom.

On the previous regression test

The original protection for this bug asserted on the literal source text of LoginStep.jsx:

assert.match(loginStep, /handleAuthSuccess\(data\.token, data\.account, \{\s*route: !onAuthenticated, /s);

That is a change detector, not a behavioural test. It passes even if the code path is unreachable, breaks on harmless reformatting or a variable rename, and verifies neither ordering nor arguments. It is replaced with real tests of the extracted contract: routing suppression, persistence-before-handover ordering, forwarded token/account, error propagation, and the no-continuation default.

Verification

  • npm test in packages/etendo-go-core: 188 passed / 0 failed.
  • RED/GREEN proven: with postAuth.js reverted to the pre-fix behaviour (always route, never call the continuation), 6 of the 7 new tests fail; restored, all 7 pass.
  • Pre-push gate green — conflict check, data-testid check, SonarQube analysis.

Known coverage gaps (not closed here)

Recorded on the Jira issue as follow-ups; each needs work outside this fix's scope:

  1. No component-level wiring test. The helper's contract is covered, but nothing asserts that LoginStep's two branches actually route through it. packages/etendo-go-core runs plain node --test with no JSX/jsdom runner, so the component cannot be rendered here. packages/app-shell-core already has vitest + Testing Library configured and is the template if we want to add one.
  2. The consumer test mocks the component away. tools/app-shell/src/pages/__tests__/InviteAcceptancePage.vitest.jsx (functional repo) replaces LoginStep with a stub that always calls onAuthenticated, so the real SSO branch is rendered in neither repo — this is why the bug reached a user.
  3. No E2E covers invitation + SSO. user-invitation.mocked.spec.js and user-invitation.email.integration.spec.js contain zero SSO references.

Downstream

Core-only change. The functional repo consumes @etendosoftware/etendo-go-core at a pinned preview version, so it needs a pin bump to a build containing this commit before the fix reaches the app.

LoginStep carried two independent copies of the post-authentication
continuation, one per authentication branch. When the caller-owned
onAuthenticated prop was introduced only the password copy was updated, so
an SSO login on the invitation page persisted the session and then stopped:
the caller never got its callback and the user was stranded on the login
form with an unconsumed invitation token.

Extract the continuation into a single exported helper that both branches
call, so the two paths can no longer diverge. Add the missing onAuthenticated
entry to the handleSsoProviderLogin dependency array, which otherwise
captured a stale callback for any caller that memoizes config.

Replace the previous source-text assert.match regression test with
behavioural unit tests covering routing suppression, persistence-before-
handover ordering, forwarded arguments, error propagation and the
no-continuation default.
@sonarscanetendo

Copy link
Copy Markdown

Passed Quality Gate passed

Issues

Measures

Project ID: etendosoftware_schema_forge_core_976a1e0b-4b24-4757-a2f4-8afa9a5e0289

View in SonarQube

@github-actions

Copy link
Copy Markdown
Contributor

📦 Preview packages published — dist-tag alpha

0.3.36-preview.feature-ETP-4958.20260821105932.27b99cc

To exercise the published-package path (no LOCAL_CORE), bump all
core pins to this preview in the functional repo and reinstall:

make bump-core-version VERSION=0.3.36-preview.feature-ETP-4958.20260821105932.27b99cc

Re-posted on each push to this branch; supersedes older previews.

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.

1 participant