Skip to content

feat(FR-2616): handle totp and concurrent-session inline in STokenLoginBoundary#6897

Merged
graphite-app[bot] merged 1 commit into
mainfrom
04-22-feat_fr-2616_handle_totp_and_concurrent-session_inline_in_stokenloginboundary
Apr 24, 2026
Merged

feat(FR-2616): handle totp and concurrent-session inline in STokenLoginBoundary#6897
graphite-app[bot] merged 1 commit into
mainfrom
04-22-feat_fr-2616_handle_totp_and_concurrent-session_inline_in_stokenloginboundary

Conversation

@nowgnuesLee
Copy link
Copy Markdown
Contributor

@nowgnuesLee nowgnuesLee commented Apr 22, 2026

Follow-up feature on top of Epic FR-2616. Jira ticket to be filed.

Summary

Extends STokenLoginBoundary to handle two interactive auth challenges inline, without a modal and without splitting into separate components — the existing DefaultErrorCard layout stays, only its action area swaps per error kind.

UX

Error kind Card status Description Actions
totp-required (new) warning "Enter the 6-digit code from your authenticator app…" Inline OTP <Input> + Submit
concurrent-session (activated) warning "This account is already signed in somewhere else. Do you want to end the other session and continue here?" Copy details · Sign in anyway
all other kinds error (unchanged) Copy details · Retry

On Submit, STokenLoginBoundary folds otp: <entered> into extraParams and replays the auth sequence. A failed retry re-renders the OTP card with an invalid-code hint above the input; the OTP itself is single-use (cleared after every retry).

On Sign in anyway, the boundary sets a sticky forceApprovedRef and replays the sequence with force: true folded in. Sticky matches LoginView's forceLoginApprovedRef — a TOTP challenge that arrives after a force approval keeps the force flag on the subsequent retries.

Why

  • token_login previously had a latent bug: client.token_login returns { fail_reason } on authenticated: false, which is truthy, so the old !loginSuccess guard passed through and connectViaGQL ran against an unauthenticated client. Introducing TokenLoginFailedError fixes this at the helper layer before the classifier inspects it.
  • Classification uses duck-typed field extraction (err.failReason, err.failType) rather than instanceof TokenLoginFailedError so cross-module-instance errors (Jest mocks, HMR reloads) classify identically to direct throws.
  • String-match detection mirrors LoginView.handleLoginError's legacy fallback so both entry points classify the same until the webserver surfaces the probe type through client.token_login (tracked by TODO(user-tunable)).

Changes

  • react/src/helper/loginSessionAuth.ts
    • TokenLoginFailedError class and structured throw from tokenLogin.
    • extraParams type widened to Record<string, string | boolean> so force: true can pass through alongside string URL params.
  • react/src/components/STokenLoginBoundary.tsx
    • STokenLoginError union: totp-required added, concurrent-session wired up end-to-end.
    • pendingOtpRef (single-use) + forceApprovedRef (sticky) retry state.
    • retryWithOtp / retryWithForce handlers; existing retry() preserved for non-interactive kinds.
    • DefaultErrorCard gains onSubmitOtp / onConfirmForce plumbing and kind-branched action area. Card status shifts to warning for the two interactive kinds.
    • New TotpInlineForm subcomponent (kept co-located in the same file, per scope request).
  • resources/i18n/en.json, resources/i18n/ko.json
    • ErrorTotpRequiredTitle / ErrorTotpRequiredDescription / ErrorTotpInvalidHint / TotpPlaceholder / SubmitOtp / ForceLogin.
    • ErrorConcurrentSessionDescription updated to match the new force-confirm UX.

Test plan

  • bash scripts/verify.shALL PASS
  • pnpm --dir react jest src/components/STokenLoginBoundary.test.tsx → 9 passed
  • Manual: navigate /?sToken=<TOTP-required> → card shows OTP input, submit retries with otp=…. Wrong code surfaces invalid hint; right code proceeds.
  • Manual: navigate with an active second-session setup → card offers "Sign in anyway"; confirming retries with force=true.
  • Manual (combined): sToken where the server first issues active-login-session-exists then require-totp-authentication on the force retry → force flag stays on while OTP is submitted.

Follow-ups

  • File a Jira sub-task under Epic FR-2616 for this feature.
  • Swap string-match classifier for strict type equality once client.token_login surfaces the probe type to the caller. TokenLoginFailedError.failType is already reserved.
  • Translations for the 20 non-en/ko locales via the fw:i18n skill.

@github-actions github-actions Bot added area:ux UI / UX issue. area:i18n Localization size:L 100~500 LoC labels Apr 22, 2026
Copy link
Copy Markdown
Contributor Author

nowgnuesLee commented Apr 22, 2026


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • flow:merge-queue - adds this PR to the back of the merge queue
  • flow:hotfix - for urgent changes, fast-track this PR to the front of the merge queue

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has required the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 22, 2026

Coverage report for ./react

St.
Category Percentage Covered / Total
🔴 Statements
9.02% (+0.1% 🔼)
1857/20587
🔴 Branches
8.19% (+0.11% 🔼)
1187/14502
🔴 Functions
5.29% (+0.01% 🔼)
295/5578
🔴 Lines
8.76% (+0.11% 🔼)
1749/19973
Show files with reduced coverage 🔻
St.
File Statements Branches Functions Lines
🟡
... / STokenLoginBoundary.tsx
70.92% (-14.14% 🔻)
60.22% (-31.68% 🔻)
47.62% (-21.61% 🔻)
72.99% (-13.05% 🔻)

Test suite run success

865 tests passing in 40 suites.

Report generated by 🧪jest coverage report action from 7005b4b

@nowgnuesLee nowgnuesLee force-pushed the 04-22-feat_fr-2616_handle_totp_and_concurrent-session_inline_in_stokenloginboundary branch 2 times, most recently from c80fed0 to c9ec6b3 Compare April 23, 2026 04:16
@github-actions github-actions Bot added the area:lib Library and SDK related issue. label Apr 23, 2026
@nowgnuesLee nowgnuesLee force-pushed the 04-22-feat_fr-2616_handle_totp_and_concurrent-session_inline_in_stokenloginboundary branch 2 times, most recently from b66b6dc to 8a99fb1 Compare April 23, 2026 04:57
@github-actions github-actions Bot added size:XL 500~ LoC and removed size:L 100~500 LoC labels Apr 23, 2026
@nowgnuesLee nowgnuesLee force-pushed the 04-22-test_fr-2639_add_e2e_regression_for_stoken_login_boundary_routes branch from a231db5 to 17f7358 Compare April 23, 2026 05:39
@nowgnuesLee nowgnuesLee force-pushed the 04-22-feat_fr-2616_handle_totp_and_concurrent-session_inline_in_stokenloginboundary branch from 8a99fb1 to 8b238b7 Compare April 23, 2026 05:39
@nowgnuesLee nowgnuesLee marked this pull request as ready for review April 23, 2026 06:09
Copilot AI review requested due to automatic review settings April 23, 2026 06:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Extends STokenLoginBoundary to handle interactive auth challenges (TOTP and concurrent-session force login) inline within the existing error card UI, while improving token-login failure signaling and adding supporting i18n/E2E coverage.

Changes:

  • Add structured token-login failure propagation (fail_reason / fail_type) and throw TokenLoginFailedError on authenticated: false.
  • Implement inline TOTP submit flow + concurrent-session “force” retry flow in STokenLoginBoundary, including improved pending-step messaging.
  • Add/adjust translations across multiple locales and expand Playwright E2E coverage for the interactive flows.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/lib/backend.ai-client-esm.ts Surface fail_type alongside fail_reason from token_login for downstream classification.
react/src/helper/loginSessionAuth.ts Introduce TokenLoginFailedError and ensure token-login failures can’t proceed to connectViaGQL.
react/src/components/STokenLoginBoundary.tsx Add inline TOTP + concurrent-session force flows, failure classification, and pending-step messaging.
e2e/auth/stoken-login.spec.ts Add E2E tests mocking /server/token-login to validate OTP/force retry request bodies and UI branching.
resources/i18n/en.json Update/add sTokenLoginBoundary strings for new pending-step + TOTP flows and revised concurrent-session copy.
resources/i18n/ko.json Update/add sTokenLoginBoundary strings for new pending-step + TOTP flows and revised concurrent-session copy.
resources/i18n/de.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/el.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/es.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/fi.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/fr.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/id.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/it.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/ja.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/mn.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/ms.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/pl.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/pt.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/pt-BR.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/ru.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/th.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/tr.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/vi.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/zh-CN.json Add sTokenLoginBoundary strings for pending-step + interactive flows.
resources/i18n/zh-TW.json Add sTokenLoginBoundary strings for pending-step + interactive flows.

Comment thread react/src/components/STokenLoginBoundary.tsx
Comment thread react/src/components/STokenLoginBoundary.tsx
Comment thread react/src/components/STokenLoginBoundary.tsx
Comment thread react/src/components/STokenLoginBoundary.tsx
Comment thread react/src/components/STokenLoginBoundary.tsx
Comment thread e2e/auth/stoken-login.spec.ts
@nowgnuesLee nowgnuesLee force-pushed the 04-22-test_fr-2639_add_e2e_regression_for_stoken_login_boundary_routes branch from 17f7358 to a1a4a7a Compare April 23, 2026 07:57
@nowgnuesLee nowgnuesLee force-pushed the 04-22-feat_fr-2616_handle_totp_and_concurrent-session_inline_in_stokenloginboundary branch from 8b238b7 to 2d4b07a Compare April 23, 2026 07:57
@nowgnuesLee nowgnuesLee force-pushed the 04-22-test_fr-2639_add_e2e_regression_for_stoken_login_boundary_routes branch from a1a4a7a to 6f5b6cd Compare April 23, 2026 08:02
@nowgnuesLee nowgnuesLee force-pushed the 04-22-feat_fr-2616_handle_totp_and_concurrent-session_inline_in_stokenloginboundary branch from 2d4b07a to 57e7a40 Compare April 23, 2026 08:02
@nowgnuesLee nowgnuesLee force-pushed the 04-22-test_fr-2639_add_e2e_regression_for_stoken_login_boundary_routes branch from 6f5b6cd to abeb16c Compare April 23, 2026 08:35
@nowgnuesLee nowgnuesLee force-pushed the 04-22-feat_fr-2616_handle_totp_and_concurrent-session_inline_in_stokenloginboundary branch 2 times, most recently from 8769ded to 33f5bd1 Compare April 23, 2026 09:57
Copy link
Copy Markdown
Member

@yomybaby yomybaby left a comment

Choose a reason for hiding this comment

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

LGTM

@graphite-app
Copy link
Copy Markdown

graphite-app Bot commented Apr 23, 2026

Merge activity

@nowgnuesLee nowgnuesLee force-pushed the 04-22-feat_fr-2616_handle_totp_and_concurrent-session_inline_in_stokenloginboundary branch from 33f5bd1 to aec9ce5 Compare April 24, 2026 07:30
@nowgnuesLee nowgnuesLee force-pushed the 04-22-test_fr-2639_add_e2e_regression_for_stoken_login_boundary_routes branch from abeb16c to d650bff Compare April 24, 2026 07:30
…inBoundary (#6897)

Follow-up feature on top of Epic [FR-2616](https://lablup.atlassian.net/browse/FR-2616). Jira ticket to be filed.

## Summary

Extends `STokenLoginBoundary` to handle two interactive auth challenges inline, without a modal and without splitting into separate components — the existing `DefaultErrorCard` layout stays, only its action area swaps per error kind.

### UX

| Error kind | Card status | Description | Actions |
|---|---|---|---|
| `totp-required` *(new)* | `warning` | "Enter the 6-digit code from your authenticator app…" | Inline OTP `<Input>` + **Submit** |
| `concurrent-session` *(activated)* | `warning` | "This account is already signed in somewhere else. Do you want to end the other session and continue here?" | **Copy details** · **Sign in anyway** |
| all other kinds | `error` | (unchanged) | **Copy details** · **Retry** |

On **Submit**, `STokenLoginBoundary` folds `otp: <entered>` into `extraParams` and replays the auth sequence. A failed retry re-renders the OTP card with an invalid-code hint above the input; the OTP itself is single-use (cleared after every retry).

On **Sign in anyway**, the boundary sets a sticky `forceApprovedRef` and replays the sequence with `force: true` folded in. Sticky matches LoginView's `forceLoginApprovedRef` — a TOTP challenge that arrives *after* a force approval keeps the force flag on the subsequent retries.

### Why

- `token_login` previously had a latent bug: `client.token_login` returns `{ fail_reason }` on `authenticated: false`, which is truthy, so the old `!loginSuccess` guard passed through and `connectViaGQL` ran against an unauthenticated client. Introducing `TokenLoginFailedError` fixes this at the helper layer before the classifier inspects it.
- Classification uses duck-typed field extraction (`err.failReason`, `err.failType`) rather than `instanceof TokenLoginFailedError` so cross-module-instance errors (Jest mocks, HMR reloads) classify identically to direct throws.
- String-match detection mirrors `LoginView.handleLoginError`'s legacy fallback so both entry points classify the same until the webserver surfaces the probe `type` through `client.token_login` (tracked by `TODO(user-tunable)`).

## Changes

- `react/src/helper/loginSessionAuth.ts`
  - `TokenLoginFailedError` class and structured throw from `tokenLogin`.
  - `extraParams` type widened to `Record<string, string | boolean>` so `force: true` can pass through alongside string URL params.
- `react/src/components/STokenLoginBoundary.tsx`
  - `STokenLoginError` union: `totp-required` added, `concurrent-session` wired up end-to-end.
  - `pendingOtpRef` (single-use) + `forceApprovedRef` (sticky) retry state.
  - `retryWithOtp` / `retryWithForce` handlers; existing `retry()` preserved for non-interactive kinds.
  - `DefaultErrorCard` gains `onSubmitOtp` / `onConfirmForce` plumbing and kind-branched action area. Card `status` shifts to `warning` for the two interactive kinds.
  - New `TotpInlineForm` subcomponent (kept co-located in the same file, per scope request).
- `resources/i18n/en.json`, `resources/i18n/ko.json`
  - `ErrorTotpRequiredTitle` / `ErrorTotpRequiredDescription` / `ErrorTotpInvalidHint` / `TotpPlaceholder` / `SubmitOtp` / `ForceLogin`.
  - `ErrorConcurrentSessionDescription` updated to match the new force-confirm UX.

## Test plan

- [x] `bash scripts/verify.sh` → `ALL PASS`
- [x] `pnpm --dir react jest src/components/STokenLoginBoundary.test.tsx` → 9 passed
- [ ] Manual: navigate `/?sToken=<TOTP-required>` → card shows OTP input, submit retries with `otp=…`. Wrong code surfaces invalid hint; right code proceeds.
- [ ] Manual: navigate with an active second-session setup → card offers "Sign in anyway"; confirming retries with `force=true`.
- [ ] Manual (combined): sToken where the server first issues `active-login-session-exists` then `require-totp-authentication` on the force retry → force flag stays on while OTP is submitted.

## Follow-ups

- File a Jira sub-task under Epic FR-2616 for this feature.
- Swap string-match classifier for strict `type` equality once `client.token_login` surfaces the probe `type` to the caller. `TokenLoginFailedError.failType` is already reserved.
- Translations for the 20 non-en/ko locales via the `fw:i18n` skill.

[FR-2616]: https://lablup.atlassian.net/browse/FR-2616?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
@graphite-app graphite-app Bot force-pushed the 04-22-test_fr-2639_add_e2e_regression_for_stoken_login_boundary_routes branch from d650bff to d7fb3b2 Compare April 24, 2026 07:48
@graphite-app graphite-app Bot force-pushed the 04-22-feat_fr-2616_handle_totp_and_concurrent-session_inline_in_stokenloginboundary branch from aec9ce5 to 7005b4b Compare April 24, 2026 07:49
Base automatically changed from 04-22-test_fr-2639_add_e2e_regression_for_stoken_login_boundary_routes to main April 24, 2026 07:50
@graphite-app graphite-app Bot merged commit 7005b4b into main Apr 24, 2026
7 checks passed
@graphite-app graphite-app Bot deleted the 04-22-feat_fr-2616_handle_totp_and_concurrent-session_inline_in_stokenloginboundary branch April 24, 2026 07:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:i18n Localization area:lib Library and SDK related issue. area:ux UI / UX issue. size:XL 500~ LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants