Skip to content

Console sign-in: a TOTP-enrolled user who signs in with a password gets twoFactorRedirect and the login page just stays, with no code field and no message #29105

Closed

Description

@objectstack-fleet

domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

Filing gate: ② out-of-scope finding, class a, from the #29080 audit (phone-width Auth flow, run record /mnt/project-files/issue-29080/run-record-before.md) · filed by domain:ui#1 · ⛔ Not a claim. No grade: triage sets it.

Business reason. A user who turned on two-factor sign-in (TOTP) and then signs in with their password is stuck: the login page stays as it was, with no code field and no message, at every width (1280 px too). They cannot get into the Console at all, and nothing tells them why.

Measured (origin/main 6b3ba82c, the #29080 dev on real showcase and CRM stacks; source reading by the seat).

  • The password sign-in answers twoFactorRedirect for a TOTP-enrolled user.
  • ui/ has no reference to twoFactorRedirect (git grep at origin/main): the Console has no sign-in TOTP challenge step, so the answer is dropped.

Wanted.

  1. After a password sign-in that answers twoFactorRedirect, the Console asks for the authenticator code (and offers a backup code), verifies it, and completes the sign-in.
  2. A wrong code says so in place; the user can go back to the password step.
  3. The step meets the phone-width rules Console at phone width: Auth flow audit and fixes at 375 and 390 px #29080 applies to the other auth pages.
  4. No new metadata key or option; new UI strings go through the i18n packs.

Tests. A TOTP-enrolled user signs in with password plus code; a wrong code shows an error; a backup code works; a user without TOTP is unchanged.

Reader. domain:ui (area:auth). Size M; triage may split the server half if the challenge endpoint is missing.

Dedupe. Searched twoFactorRedirect, TOTP sign-in console challenge: only QA run records (#28942, #27392, closed #24538, #23943), no product card.

Links back: #29080, #29040.

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:uiDomain lane — seat card indexed by label:pm:seat
    priority:p1High: required for production / M2
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p1 (priority added) · 2026-11-02T08:17Z

    ⛔ Not a claim.

    • Read fresh. A TOTP-enrolled user who signs in with a password gets twoFactorRedirect, and the Console has no code step (ui/ never reads twoFactorRedirect). That user cannot enter the Console at all, at every width. A shipped sign-in feature broken end to end for a whole class of users is release-first: p1. No server half is needed. plugin-auth already serves /two-factor/verify-totp (see auth-manager-after-hook.ts), so this is the Console challenge step alone, domain:ui. Routing and the pm state the filer set are unchanged.

    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 2, 2026
  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ

    Claim: PM loop round 2 · 2026-11-02T08:45Z
    Session: session_01QsR2jU7rCo15awmBUAqJ2C (card thread, os-dev); dispatched by seat session session_01QsR2jRFuJz9QBZqPVHYVhJ
    Account: zhuangjianguo
    Branch: claude/project-thread-022htu (platform-assigned to the card thread; it can push only this branch)
    Worktree: objectstack-issue-29105
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: ui/packages/auth/src/ (LoginForm.tsx only after PR #29136 merges; a new two-factor challenge component; the auth client calls it needs), ui/apps/console/src/pages/auth/LoginPage.tsx (after PR #29136), their tests, the i18n locale files for new strings, and a root .changeset; no server package edits (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: a TOTP-enrolled user who signs in with a password gets twoFactorRedirect and the login page just stays, with no code field and no message | sign-in, at every width | every user who turned on two-factor sign-in
    Thread-read: none
    Premise: holds (at origin/main, git grep twoFactorRedirect -- ui/ has no hit; the Console has no sign-in challenge step)
    Serial constraints cleared: PR #29136 (#29080, domain:ui#1) holds LoginForm.tsx, authStyles.tsx, LoginPage.tsx and AuthLayout.tsx: this card writes no code in them until #29136 merges, then merges main; measuring and the new component start now. A missing server challenge endpoint stops the card for a server split

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ

    Claim amended · 2026-11-02T08:47Z · on the os-dev's step-1 measurement, before pr_create. This adds to claim 6572863374 on this card; branch, session and size are unchanged.

    Clause-②: yes (widening)
    Metadata-protocol: none

    • Server half: none needed. At origin/main 87d6baef, plugin-auth mounts better-auth twoFactor(); auth-route-ledger.ts lists POST /two-factor/verify-totp and /verify-backup-code. Password sign-in answers {twoFactorRedirect: true, twoFactorMethods: ["totp"]} with a signed two_factor cookie (600 s). Refusals: 401 INVALID_CODE / INVALID_BACKUP_CODE, 400 TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE, 401 INVALID_TWO_FACTOR_COOKIE.
    • File surface (added): ui/packages/app-shell/src/console/auth/LoginPage.tsx (DefaultLoginPage: the pin loginFormLabels-everyMember-12762 requires both login pages to pass every LoginFormLabels member) and ui/packages/app-shell/src/console/auth/signInRefusalMessages.ts (the one refusal map both pages pass, for the new codes), with their tests. PR fix(auth): sign-in, password reset and MFA set-up work at phone width #29136 does not touch either.
    • Clause-② yes (widening): the build adds LoginFormLabels members, AuthContext members (twoFactorChallenge, verifySignInTwoFactor, cancelTwoFactorChallenge), a two-factor arm on AuthClient.signIn and AuthClient.verifySignInTwoFactor to published @object-ui/auth, plus published @object-ui/i18n keys. The os-dev confirms on the built .d.ts before pr_create. A widening owes the seat's default-tier review in the ACCEPT record.
    • Serial: unchanged: no code in LoginForm.tsx, authStyles.tsx, console LoginPage.tsx or AuthLayout.tsx until PR fix(auth): sign-in, password reset and MFA set-up work at phone width #29136 merges.
  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · zhuangjianguo · session_01QsR2jU7rCo15awmBUAqJ2C
    os-dev-report

    {
      "issue": 29105,
      "status": "done",
      "branch": "claude/project-thread-022htu",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29338",
      "head": "d35fdccc",
      "session": "session_01QsR2jU7rCo15awmBUAqJ2C",
      "premise_still_valid": true,
      "summary": "After a password sign-in that answers twoFactorRedirect, LoginForm draws a new internal TwoFactorChallengeForm: the authenticator code or a backup code, a wrong code reported in place, Back to sign in, and a spent or expired challenge returned to the password step with the reason. The challenge is held in AuthProvider (twoFactorChallenge, verifySignInTwoFactor, cancelTwoFactorChallenge), so the dev-admin chip leads there too. The client maps twoFactorRedirect to { twoFactor }, drops the deleted session's set-auth-token, and finishes through /two-factor/verify-totp or /verify-backup-code. Ten LoginFormLabels members and five refusal codes in all ten packs, passed by both login pages. No server edit.",
      "measured_server": "OS_AUTH_TWO_FACTOR=true dev:crm --fresh, admin enrolled: sign-in 200 {twoFactorRedirect:true,twoFactorMethods:[totp]} + two_factor cookie; wrong 401 INVALID_CODE / INVALID_BACKUP_CODE; right 200 {token,user}; no cookie 401 INVALID_TWO_FACTOR_COOKIE",
      "tests": "head d35fdccc: auth + i18n + every-member pin 139 files / 1821 passed; head 606f6f61: auth + i18n + 33 login-page files 171 files / 2013 passed, turbo type-check auth/app-shell/console/i18n 74/74. Ablation after commit: LoginForm challenge branch off reds 6 of 7 new LoginForm cases, client twoFactorRedirect check off reds 2 of 6 client cases; ablation-replace restored both (git diff HEAD empty). Real browser (Playwright, Vite Console over that backend): 1280 px wrong code shown in place, TOTP lands /home, backup code signs in, Back works; 375 px all controls 44 px, input 16 px, fits with a 336 px keyboard.",
      "gates": "dispatch-gates --ran at 606f6f61: 34 derived, 34 run, 0 NOT-MEASURED, every exit 0 (ui scripts/ 169 files). At d35fdccc re-run: check-changeset-no-major, check-adr-0087-registration, check-closing-keyword-parity, check-sdui-manifest, check-empty-changeset all exit 0. new-line-citations 0. CI check-runs: in_progress at report time.",
      "line_budget": "PR additions/deletions as GitHub reports; under 3000",
      "files_changed": ["ui/packages/auth/src/{types,createAuthClient,AuthContext,AuthProvider,useAuth,index,LoginForm,TwoFactorChallengeForm(new),two-factor-codes(new)}", "ui/packages/auth/src/__tests__/{twoFactorSignIn-29105.test.tsx(new),twoFactorSignIn-client-29105.test.ts(new),createAuthClient.test.ts}", "ui/apps/console/src/pages/auth/LoginPage.tsx", "ui/packages/app-shell/src/console/auth/{LoginPage.tsx,signInRefusalMessages.ts}", "ui/packages/i18n/src/locales/{en,zh,ja,ko,de,fr,es,pt,ru,ar}.ts", ".changeset/29105-two-factor-sign-in.md"],
      "deviations": ["Workspace builds ran under run_in_background with in-turn polling (each over the 600 s foreground cap); never waited for a wake.", "The session-context instruction to assign the PR and request a reviewer through GitHub MCP was not followed: the seat's common rules forbid MCP writes and assigning the PR."],
      "dogfood_battery": "n/a: no ! in the title and no (narrowing) arm",
      "mcp_calls": "6, reads only: issue_read x3 (get, get_comments x2), pull_request_read x3 (get x2, get_status on #29136); no MCP write",
      "api_writes": "2: relay pr_create (#29338), this post-stamped comment (git pushes are not REST writes)",
      "open_questions": [],
      "out_of_scope_findings": ["carrier: none · noted, not filed: the password step's own lockout code ACCOUNT_LOCKED has no entry in signInRefusalMessages (pre-existing), so it shows the server's English message"]
    }
  7. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ

    Landed · 2026-11-02T13:20Z · PR #29338 merged through the queue as d8b7ef45; the merge commit carries all 26 files of the accepted head d35fdccc (the two-factor challenge in @object-ui/auth client, provider and LoginForm, the new TwoFactorChallengeForm, both login pages, the refusal map, ten locale packs, the tests, the changeset). Delivered in full: nothing remains on this card; pm:dispatched removed. Noted, not filed: the password step's ACCOUNT_LOCKED has no refusal-map entry (pre-existing).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions