Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

auth LoginForm under SSO enforcement: the config-pending spinner is captioned "Enter your email and password to continue", and an "or continue with email" divider renders with no email form under it #12682

Closed

Description

@objectstack-fleet

Filing gate ①: a user-visible UX defect with a named landing point (packages/auth/src/LoginForm.tsx, packages/auth/src/SocialSignInButtons.tsx). Reach: the console login page on an SSO-enforced environment, measured on a live boot. It reproduces on every fresh load (3/3) and was checked against objectui main @ dcd1041, where the code paths below are unchanged.

Reader who acts: the objectui packages/auth execution seat. Both fixes are in LoginForm and need no new spec contract:

  • don't caption the pending state with the password-form description;
  • don't render the email divider when the password form is hidden.

What happens

The server advertises features.ssoEnforced: true on GET /api/v1/auth/config (OS_AUTH_SSO_ONLY=true plus one oidcProviders[] entry). On that environment the login page:

  1. While the auth config is still resolving (the spinner that fix(auth): env 登录页首次加载缺 SSO 按钮 — auth config race 让 SSO-only 用户卡死在密码墙 #2625 introduced): the header reads "Enter your email and password to continue". ssoEnforced is still false at that moment, so ssoOnlyView is false and the header falls through to the password-form description default. On the one deployment class with no password path, the first words a user reads tell them to use a password. The 3 s delayed-config load made this easy to see.
  2. After the config resolves: the SSO button renders, followed by an "OR CONTINUE WITH EMAIL" divider with nothing under it. SocialSignInButtons always renders its own divider (dividerText, default 'or continue with email'). LoginForm relies on that divider for the password form, but under enforcement passwordFormVisible is false until the break-glass link is clicked, so the divider points at a form that isn't there.

The security property holds: no password input renders in any state, and the "Use a password instead" break-glass link works. This card is only about the misleading copy.

Reproduction

On the objectstack framework at 2034975a with the vendored console at objectui daeb77fc:

  1. Boot with an SSO-only environment: OS_AUTH_SSO_ONLY=true plus a configured OIDC provider. The QA run used a localhost OIDC fixture registered through auth.applyConfigPatch({ oidcProviders }).
  2. Confirm GET /api/v1/auth/config → features.ssoEnforced: true.
  3. Open /_console/login in a fresh browser context. Optionally delay the /auth/config response by about 3 s to widen the pending window.
  4. While the spinner shows, the header subtitle reads "Enter your email and password to continue".
  5. After the config resolves, the provider button is followed by an "OR CONTINUE WITH EMAIL" divider, and nothing renders below it until "Use a password instead" is clicked.

Expected: neutral, or SSO-appropriate, pending copy, and no email divider while the password form is collapsed.
Actual: password copy while pending, and a dangling divider.

Mechanism (objectui main @ dcd1041)

  • packages/auth/src/LoginForm.tsx:
    • description = 'Enter your email and password to continue' (about line 201).
    • ssoEnforced initialises to false (about line 246) and configPending to true (about line 260).
    • The header renders description={ssoOnlyView ? l.ssoOnlyDescription : description} (about line 441) during the pending spinner (about line 446).
    • <SocialSignInButtons dividerText={labels.orText} …/> (about line 456) renders whether or not passwordFormVisible is true.
  • packages/auth/src/SocialSignInButtons.tsx: dividerText = 'or continue with email' (about line 122), rendered unconditionally beneath the provider buttons (about line 211).

Dedupe

QA-source: objectstack-ai/objectstack#23943 · identity-auth.sso-enforced-first-paint · acceptance 1

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · domain:ui · area:identity · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T15:02Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Why p2: the login page of every SSO-enforced environment shows an "or continue with email" divider with nothing under it, on every load.
    • Lane: domain:ui. Both fixes are in packages/auth/src/LoginForm.tsx and need no spec change, as the card says.
  2. added
    area:identityLogin and identity — sign-up, sessions, organization membership, SSO
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 16, 2026
  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2
    Session: session_01Vk15bz8xHtKGUfXfCm16h4
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12682-sso-login-pending-copy
    Worktree: objectui-issue-12682
    Domain: domain:ui
    Seat: domain:ui#3
    File surface: packages/auth/src/LoginForm.tsx (the header's description while configPending, and whether the provider block's divider shows while passwordFormVisible is false); packages/auth/src/SocialSignInButtons.tsx (one optional prop that omits its divider, with the default keeping today's rendering for RegisterForm and every other caller); packages/auth/README.md where it documents SocialSignInButtons' props; tests beside each; one .changeset/12682-*.md (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui over the named paths: no path-derived mandate)
    Clause-②: yes
    Clause-② reason: @object-ui/auth exports SocialSignInButtons and SocialSignInButtonsProps (packages/auth/src/index.ts:38), and the divider can be withheld only by a new optional member of that props type. That is a widening of a published UI surface, with no @object-ui/types member and no authoring key. The dev reads the final name on the built dist/index.d.ts. The seat's TIER_DEFAULT review of record comes before ready.
    Responsibility: @object-ui/auth's LoginForm captions the config-pending spinner with the password form's description, and SocialSignInButtons always draws its "or continue with email" divider, including under SSO enforcement where no email form follows | none covers it | every user of an SSO-enforced environment's login page, on every load (measured 3/3 on a live boot by QA, objectstack-ai/objectstack#23943; the lines the card names stand on main 1d86919: LoginForm.tsx:201, :246, :260, :441, :456; SocialSignInButtons.tsx:122, :211)
    Thread-read: 6218331213
    Serial constraints cleared: objectui#12712 (pm:queue + pm:retriage, no PR, the same LoginForm.tsx, field region only if triage re-scopes it) is serial, not fold, by gate ①: a different defect shape. Whichever lands second merges main. objectui#12700 (baozhoutao) may touch packages/auth/src/ only where /auth/me/permissions loads, a different region. No other in-flight domain:ui claim and no open objectui PR names packages/auth/, read across the lane's pm:dispatched claims and all 8 open PRs' file lists 2026-10-16T18:08Z.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12682,
      "status": "done",
      "branch": "claude/issue-12682-sso-login-pending-copy",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12761",
      "session": "session_01Vk15bz8xHtKGUfXfCm16h4 (subagent: the PM seat's id, from the dispatch and the relay envelope)",
      "premise_still_valid": true,
      "summary": "Both symptoms were reproduced live on unchanged main (e219a3d) before any edit. The setup: objectstack 66d456d01 examples/app-crm as the backend, Vite from the worktree, and Playwright routing GET /api/v1/auth/config to the backend's own answer with features.ssoEnforced set and one OIDC provider appended, delayed 3 s. No OIDC provider was configured on the server. (1) The pending caption 'Enter your email and password to continue' shows on app-shell's exported DefaultLoginPage (examples/console-starter /login), which passes description=t('auth.login.description'). It does NOT show on apps/console /login. The PM hypothesis that the console page passes its own description is refuted on today's main: the page passes hideHeader (objectui#12600, landed after the QA build daeb77fc, whose LoginPage still passed title/description), and its card header 'Welcome back / Sign in to continue your work' is neutral. The hideHeader branch renders nothing while pending, which confirms hypothesis 1's second half. (2) The dangling 'OR CONTINUE WITH EMAIL' divider shows on both pages, followed only by 'Use a password instead'. The fix: LoginForm's header carries no description while configPending; once the config resolves, the description is ssoOnlyDescription under enforcement and description otherwise, as before. SocialSignInButtons gains one optional boolean prop, hideDivider (default false, so RegisterForm and external callers are unchanged), and LoginForm passes hideDivider={!passwordFormVisible}. Also updated: the README orText paragraph, the description/orText docblocks, and the changeset (auth minor). After the fix, the live readings at e74debc show no pending password copy, no divider under enforcement, divider plus form after break-glass, and the non-enforced page unchanged. No translation key was added. Commits carry the model-free trailer pair per AGENTS.md, not the harness reminder's model-named Co-Authored-By. Zero label writes: objectui path labels belong to labeler.yml, and the dispatch named none. The worktree was removed cleanly and every server I started was stopped by PID.",
      "tests": "New file packages/auth/src/__tests__/ssoEnforcedLoginCopy-12682.test.tsx, 8 cases. Against unchanged source (test only, no fix): 'Tests 4 failed | 4 passed (8)'. The 4 red are the pins: the pending caption with the default description, the pending caption with a host-passed description, no divider under enforcement until break-glass, and hideDivider omits the divider. The 4 green are controls, green on main by design: hideHeader pending, failed-config description, non-enforced divider+form+description, and the default divider. The PM template said every pin goes red on main; the controls cannot, so this is noted rather than forced. After the fix: 8 passed. At HEAD e74debc: 'pnpm exec vitest run packages/auth/ packages/app-shell/src/console/auth/ apps/console/src/pages/auth/' gave 'Test Files 56 passed (56) / Tests 449 passed (449)', lock VERDICT command-exit 0. RegisterForm keeps its divider through the existing pin 'RegisterForm — social button labels (objectui#10900) › unset labels keep the English defaults'. Ablations, one per pin, after the fix was committed, each through objectstack scripts/ablation-replace.mjs (anchor hit x1, blob changed, restored blob == HEAD, git diff HEAD empty after every leg). Tests import ../LoginForm source directly, so no dist leg was needed. A1b (header description reverted): both pending pins red. A2 (no hideDivider passed): the enforced-divider pin red. A3 (hideDivider ignored): the enforced-divider and hideDivider pins red. A4 (hideDivider={true}): the enforced-divider and non-enforced control red. A5 (default flipped to true): the default-divider control and both RegisterForm 10900 pins red. A6 (hideHeader branch captions pending): the hideHeader control red. A7b (description never shown): the failed-config control, the non-enforced control and both pending pins red. The first A1 and A7 attempts were no-ops: the tool refused them (exit 1, the replacement count could not rise) and no test ran. Both were re-run with marker comments.",
      "gates": "Local, at e74debc, exit codes captured before any pipe. (1) pnpm --workspace-concurrency=2 --filter '@object-ui/auth...' build: exit 0, 'dist completeness: 1 package(s) complete'. '@object-ui/auth^...' matches no workspace package, so the closure is empty. dist/SocialSignInButtons.d.ts reads 'hideDivider?: boolean;' and dist/index.d.ts re-exports 'type SocialSignInButtonsProps'. (2) pnpm --filter @object-ui/auth type-check: exit 0; tsconfig.test.json --listFilesOnly lists the new test (count 1). (3) pnpm --filter '@object-ui/console^...' build (35 packages) then pnpm --filter @object-ui/console type-check: BUILD_EXIT=0 and TC_EXIT=0, each captured in its own log. The lock printed 'VERDICT batch-last-exit 0' because the wrapper string used ';', so the two captured exits are the evidence, not that verdict. (4) pnpm exec eslint on LoginForm.tsx, SocialSignInButtons.tsx and the new test: exit 0, 0 errors, 2 pre-existing warnings on untouched lines (hasSocialProviders unused; onProvidersResolved missing from an effect's deps). (5) node scripts/check-changeset-presence.mjs: exit 0. (6) pnpm check:new-line-citations: exit 0, 'VERDICT new-cross-file-line-citations: 0 new citation(s)'. (7) check:i18n-keys not run: no bundle changed. Control-byte self-scan of all 5 changed files: no hits. objectui has no dispatch-gates.mjs, so the list was hand-derived per the dispatch and --ran reconciliation does not apply. CI on PR #12761 head e74debc, read once at report time. Completed success: Build Docs, Changeset Bump Policy, Changeset Claim Re-read, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Governed Surface Queue Guard, Line Citation Gate, label. Skipped: Test (coverage), Test (coverage shard 8-way), dependabot. in_progress (23): Lint, Type Check, Repo Gates, Build & E2E, Bundle Analysis, Test (dist pins), Test shards 1-16 of 16, Live E2E (informational).",
      "mcp_calls": "0",
      "api_writes": "4 sent by this session, 2 now and 2 at the report: (1) POST /repos/objectstack-ai/objectstack/dispatches (relay request fw-20261016T183228Z-1e0664, run 38948131462 success), which executed POST /repos/objectstack-ai/objectui/pulls (draft PR #12761, read-back 9866 bytes identical) and POST /repos/objectstack-ai/objectui/issues/12761/assignees (os-elon-musk). (2) POST /repos/objectstack-ai/objectstack/dispatches for this os-dev-report comment, executing POST /repos/objectstack-ai/objectui/issues/12682/comments. git push of the branch (empty claim-marker push, then e74debc) is not REST. No label write, no PATCH.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a · reach: apps/console /login in a zh-CN browser context with GET /api/v1/auth/config answering features.ssoEnforced: true (live; objectstack 66d456d01 app-crm backend; Playwright-routed answer plus one OIDC provider) renders the SSO-only line \"Sign in with your organization's single sign-on\" and the break-glass link \"Use a password instead\" in English, while the rest of the card is Chinese (欢迎回来 / 登录后继续你的工作 / 使用 Acme IdP 继续 / 本地开发实例) · evidence: neither apps/console LoginPage's LoginFormCard labels nor app-shell DefaultLoginPage's labels pass LoginFormLabels.ssoOnlyDescription, usePasswordText or backToSsoText, and git grep finds no key for any of the three under packages/i18n; landing points are those two pages' labels plus the ten bundles · family: the labels-that-never-reach-LoginForm class objectui#10900 closed for socialButton/orText · dedupe words: SSO-only login copy untranslated; ssoOnlyDescription usePasswordText backToSsoText not passed; break-glass link English zh-CN"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #12761 comment 6222513545 · domain:ui seat 3 · session_01Vk15bz8xHtKGUfXfCm16h4 · 2026-10-16T18:52Z. The seat lands it through the relay (pr_ready + automerge_enable); on MERGED, Fixes closes this card.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 3 · session_01Vk15bz8xHtKGUfXfCm16h4 · 2026-10-16T19:07Z

    • PR fix(auth): an SSO-enforced login page shows no password copy while its config resolves and no email divider without an email form (objectui#12682) #12761 merged through the merge queue as 9b69e13 on main (2026-10-16T19:06Z). Head 489d640: ACCEPT 6222513545, contract review 6222502129 (TIER_DEFAULT, declaration face, PASS).
    • Read by content on main at 9b69e13:
      • packages/auth/src/LoginForm.tsx withholds the header description while configPending (:450) and passes hideDivider={!passwordFormVisible} (:471);
      • packages/auth/src/SocialSignInButtons.tsx declares hideDivider?: boolean (:105, default false) and omits the divider when it is set.
    • This card closed completed on the PR's Fixes. The seat strips pm:dispatched in the same stroke as this note.
    • Out of scope, as the ACCEPT records: the four SSO labels that no host passes, so an SSO login reads English on a zh-CN console, are objectui#12762. objectui#12712 (pm:retriage, the same LoginForm.tsx) merges main over this if triage re-scopes it.

    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:identityLogin and identity — sign-up, sessions, organization membership, SSObugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions