You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
console login: with email+password sign-in off and no SSO provider, the card says "Sign in with your organization's single sign-on" but shows no SSO button and no working method #29159
立卡门: ① 用户可见的体验缺陷,具名落点 + 复现(reach: 界面 + HTTP,关闭邮箱密码登录后匿名打开登录页,实测)
业务理由: 管理员在设置里关掉邮箱密码登录、但还没配好 SSO 时,所有用户打开登录页看到「请用组织的单点登录」却没有任何 SSO 按钮,唯一的「改用密码」链接进去的表单又被服务器拒绝,整页没有一条可走的路;用户只能猜是系统坏了。登录页应只展示服务器公布的登录方式。
动手的读者: 拥有 ui/packages/auth 的 ui 车道:LoginForm 只在 features.sso === true 时选用 SSO 专属文案,邮箱密码关闭且无 SSO 时显示「无可用登录方式 / 联系管理员」一类的状态。
查重: login single sign-on email_password_enabled false 0 命中、ssoEnforced emailPassword.enabled false login 0、Use a password instead login SSO not configured 18(含已关);最近邻 #25083(托管的仅 SSO 环境,已关),不是本现象。
Batch-of: release-18.3.0-w4-l2 — 10 cards from QA run #29151 (subject 7fa9873f; the implementer re-checks the landing site on current main).
QA-source: #29151 · identity-auth.auth-method-matrix · observed beside the clauses
What happens
On a stock showcase dev boot, admin PUT /api/settings/auth {"email_password_enabled":false}. GET /api/v1/auth/config then answers emailPassword.enabled:false, features.sso:false, ssoEnforced:false. Opening /_console/login anonymously shows a card reading "Sign in with your organization's single sign-on", a "Use a password instead" link whose form the server refuses (400 EMAIL_PASSWORD_DISABLED), and no SSO button.
Measured at framework 7fa9873f (main, 2026-11-02), console built from the same tree.
Reproduction
Boot the showcase; as admin PUT /api/settings/auth {"email_password_enabled":false}.
In a fresh browser context open /_console/login.
Expected: the card names only the methods the server advertises; with none, a clear "no sign-in method is available, contact your administrator" state, never an SSO instruction while features.sso is false.
Actual: SSO-only copy with no SSO affordance, plus a password link that is refused.
ui/packages/auth/src/LoginForm.tsx:276-279 sets ssoEnforced when config.emailPassword.enabled === false (a "belt-and-suspenders fallback for older servers"), while the SSO button renders only for features.sso === true (:273). The SSO-only copy (:439-450) is chosen from ssoEnforced alone.
Read fresh. The fix lands in ui/packages/auth/src/LoginForm.tsx, which picks the SSO-only copy from ssoEnforced (set whenever email+password is off) while the SSO button needs features.sso. A login page with no working path is a user-visible behaviour defect, so p2. Location and repro are named, so pm:queue.
Claim: PM loop round 1 · 2026-11-02T09:47Z
Session: session_01QsR2jTH6JZayCDwSkR11qj (card thread, os-dev); dispatched by seat session session_01QsR2jQjvWkZ7is6GXbC6DB
Account: zhuangjianguo
Branch: claude/project-thread-ftpl8z (platform-assigned to the card thread; it can push only this branch)
Worktree: objectstack-issue-29159
Domain: domain:ui
Seat: domain:ui#5
File surface: ui/packages/auth/src/LoginForm.tsx and its tests; one new optional key on the exported LoginFormLabels; the two host LoginPage.tsx files (ui/apps/console/src/pages/auth/, ui/packages/app-shell/src/console/auth/) only to pass the new translated label; the auth.login.* key in ui/packages/i18n locales; a changeset (stop on breach; explain in the report)
Container & model: S/M, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate; no Fable per the maintainer's standing rule), effort: high
Clause-②: yes (widening)
Metadata-protocol: none
Responsibility: LoginForm.tsx:276-279 derives ssoEnforced from emailPassword.enabled === false while the SSO button needs features.sso === true | no platform path covers it: the server answers features.sso:false and the card shows SSO-only copy | every visitor of /_console/login on a deployment with email+password off and no SSO; reached by QA run #29151
Thread-read: 6573315205
Premise: holds (at origin/main104cd1a5: LoginForm.tsx:276-279 sets ssoEnforced on emailPassword.enabled === false, :273 SSO button needs features.sso === true, :427 ssoOnlyView picks l.ssoOnlyDescription). Triage 6573315205 graded p2.
Serial constraints cleared: PR #29136 (#29080, seat 1, merge queue) touches LoginForm.tsx: edits after it merges. #29105 (seat 2, p1) declares app-shell console/auth/LoginPage.tsx and signInRefusalMessages.ts: this PR opens after #29105 lands. #29174 (seat 3) touches neither file.
{
"issue": 29159,
"status": "done",
"branch": "claude/project-thread-ftpl8z",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29257",
"head": "e1a2c064",
"session": "session_01QsR2jTH6JZayCDwSkR11qj",
"premise_still_valid": true,
"summary": "LoginForm read emailPassword.enabled === false as SSO enforcement, so a deployment with the password door shut and no SSO showed SSO-only copy, no SSO button and a break-glass link to a form the server refuses (400 EMAIL_PASSWORD_DISABLED). ssoEnforced now comes only from features.ssoEnforced; with the password door shut and no sso, social/OIDC provider, phone+password or phone OTP, the card shows a role=status line 'No sign-in method is available. Contact your administrator.' with no form, link or sign-up. SSO copy only under declared enforcement or features.sso; break-glass only while the revealed form holds an accepted door. New optional LoginFormLabels.noSignInMethodText, auth.login.noSignInMethodText in all ten locales, passed by both login pages.",
"files_changed": [
"ui/packages/auth/src/LoginForm.tsx",
"ui/packages/auth/src/__tests__/LoginForm.test.tsx",
"ui/packages/auth/src/__tests__/noSignInMethod-29159.test.tsx",
"ui/packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts",
"ui/apps/console/src/pages/auth/LoginPage.tsx",
"ui/packages/app-shell/src/console/auth/LoginPage.tsx",
".changeset/29159-login-no-sign-in-method.md"
],
"line_budget": "16 files, +307 / -25 vs origin/main (well under 3000)",
"tests": "Locked, maxWorkers=2. At 8a6b2ec5: vitest packages/auth/ plus 34 console/app-shell/i18n files naming LoginForm, LoginPage or auth.login: 75/75 files, 580/580 tests (first run had one case reading the async provider button with getByRole; fixed with findByRole in 8a6b2ec5, new files re-ran 3x 35/35). Ablation via scripts/ablation-replace.mjs restoring 'ssoEnforced === true || emailPassword.enabled === false': 2/35 red (password off + provider only; password off + phone), anchor 1 to 0, blob 100d93a2 to c77d644f, restored blob == HEAD, git diff HEAD empty. The no-method case is held by noSignInMethod, not by the derivation, so it does not move under that ablation. turbo type-check auth/i18n/app-shell/console 74/74, 0 error TS. ui vitest scripts/ 169 passed, 2 skipped. After merging main 01252a45 (head e1a2c064, no incoming commit touches the changed files): auth plus 35 consumer files 76/76 files, 584/584 tests. Real browser MEASURED: fresh showcase backend, admin PUT /api/settings/auth email_password_enabled false (200), config emailPassword.enabled false, sso false, ssoEnforced false, no providers, sign-in/email 400 EMAIL_PASSWORD_DISABLED; Playwright fresh context at the Console Vite dev server /login (base /), en-US 1280x800 and zh-CN 390x844: no-method status 1, SSO copy absent, break-glass 0, password field 0; control leg with password on: password field 1, status 0.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 8a6b2ec5: 34 families, each exit 0, including node scripts/check-sdui-manifest.mjs and --self-test; --ran reconciliation 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Re-derived at e1a2c064: same list; check-sdui-manifest, check-changeset-no-major, check-empty-changeset, check:nul-bytes re-ran exit 0. PR body gates on the stored body: check-changeset-no-major, check-adr-0087-registration, check-closing-keyword-parity, check-partof-closing-keyword exit 0. PR check-runs: in_progress at report time.",
"dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
"deviations": "none. Declaration lines copied from the claim. The PR opened before #29105 landed on the seat's instruction (seat 2 agreed #29159 lands first). The PR body opens with the harness attribution block, then Fixes #29159.",
"mcp_calls": "3: mcp__github__issue_read x3 (get, get_comments x2); no write tool",
"api_writes": "2: relay pr_create (objectstack-ai/objectstack#29257, read back identical, 7560 bytes) and this post-stamped comment; plus git push x4 (not REST)",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: read from source and a unit render, not measured at a public door · the email-routed Sign in with SSO button renders only inside the password form, so under features.ssoEnforced with features.sso true (and now with password off and sso true) it is reached only through Use a password instead, whose password submit the server refuses when email+password is off · dedupe words: LoginForm SSO button break-glass ssoEnforced email domain · carrier: none, noted in the PR's Acceptance notes, not filed"
]
}
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: identity-auth.auth-method-matrix
Triage:
domain:ui· p2 ·pm:queue· 2026-11-02T09:10Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#5 · zhuangjianguo · session_01QsR2jQjvWkZ7is6GXbC6DB
Claim: PM loop round 1 · 2026-11-02T09:47Z
Session:
session_01QsR2jTH6JZayCDwSkR11qj(card thread, os-dev); dispatched by seat sessionsession_01QsR2jQjvWkZ7is6GXbC6DBAccount:
zhuangjianguoBranch:
claude/project-thread-ftpl8z(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-29159Domain:
domain:uiSeat:
domain:ui#5File surface:
ui/packages/auth/src/LoginForm.tsxand its tests; one new optional key on the exportedLoginFormLabels; the two hostLoginPage.tsxfiles (ui/apps/console/src/pages/auth/,ui/packages/app-shell/src/console/auth/) only to pass the new translated label; theauth.login.*key inui/packages/i18nlocales; a changeset (stop on breach; explain in the report)Container & model: S/M,
mode:thread,model: default tier (dispatch-gates --tier: no path-derived mandate; no Fable per the maintainer's standing rule),effort: highClause-②: yes (widening)
Metadata-protocol: none
Responsibility:
LoginForm.tsx:276-279 derives ssoEnforced from emailPassword.enabled === false while the SSO button needs features.sso === true | no platform path covers it: the server answers features.sso:false and the card shows SSO-only copy | every visitor of /_console/login on a deployment with email+password off and no SSO; reached by QA run #29151Thread-read: 6573315205
Premise: holds (at
origin/main104cd1a5:LoginForm.tsx:276-279sets ssoEnforced onemailPassword.enabled === false,:273SSO button needsfeatures.sso === true,:427ssoOnlyView picksl.ssoOnlyDescription). Triage 6573315205 graded p2.Serial constraints cleared: PR #29136 (#29080, seat 1, merge queue) touches
LoginForm.tsx: edits after it merges. #29105 (seat 2, p1) declaresapp-shell console/auth/LoginPage.tsxandsignInRefusalMessages.ts: this PR opens after #29105 lands. #29174 (seat 3) touches neither file.objectstack-fleet commentedon Nov 2, 2026
domain:ui#5 · zhuangjianguo · session_01QsR2jTH6JZayCDwSkR11qj
os-dev-report
{ "issue": 29159, "status": "done", "branch": "claude/project-thread-ftpl8z", "pr": "https://github.com/objectstack-ai/objectstack/pull/29257", "head": "e1a2c064", "session": "session_01QsR2jTH6JZayCDwSkR11qj", "premise_still_valid": true, "summary": "LoginForm read emailPassword.enabled === false as SSO enforcement, so a deployment with the password door shut and no SSO showed SSO-only copy, no SSO button and a break-glass link to a form the server refuses (400 EMAIL_PASSWORD_DISABLED). ssoEnforced now comes only from features.ssoEnforced; with the password door shut and no sso, social/OIDC provider, phone+password or phone OTP, the card shows a role=status line 'No sign-in method is available. Contact your administrator.' with no form, link or sign-up. SSO copy only under declared enforcement or features.sso; break-glass only while the revealed form holds an accepted door. New optional LoginFormLabels.noSignInMethodText, auth.login.noSignInMethodText in all ten locales, passed by both login pages.", "files_changed": [ "ui/packages/auth/src/LoginForm.tsx", "ui/packages/auth/src/__tests__/LoginForm.test.tsx", "ui/packages/auth/src/__tests__/noSignInMethod-29159.test.tsx", "ui/packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts", "ui/apps/console/src/pages/auth/LoginPage.tsx", "ui/packages/app-shell/src/console/auth/LoginPage.tsx", ".changeset/29159-login-no-sign-in-method.md" ], "line_budget": "16 files, +307 / -25 vs origin/main (well under 3000)", "tests": "Locked, maxWorkers=2. At 8a6b2ec5: vitest packages/auth/ plus 34 console/app-shell/i18n files naming LoginForm, LoginPage or auth.login: 75/75 files, 580/580 tests (first run had one case reading the async provider button with getByRole; fixed with findByRole in 8a6b2ec5, new files re-ran 3x 35/35). Ablation via scripts/ablation-replace.mjs restoring 'ssoEnforced === true || emailPassword.enabled === false': 2/35 red (password off + provider only; password off + phone), anchor 1 to 0, blob 100d93a2 to c77d644f, restored blob == HEAD, git diff HEAD empty. The no-method case is held by noSignInMethod, not by the derivation, so it does not move under that ablation. turbo type-check auth/i18n/app-shell/console 74/74, 0 error TS. ui vitest scripts/ 169 passed, 2 skipped. After merging main 01252a45 (head e1a2c064, no incoming commit touches the changed files): auth plus 35 consumer files 76/76 files, 584/584 tests. Real browser MEASURED: fresh showcase backend, admin PUT /api/settings/auth email_password_enabled false (200), config emailPassword.enabled false, sso false, ssoEnforced false, no providers, sign-in/email 400 EMAIL_PASSWORD_DISABLED; Playwright fresh context at the Console Vite dev server /login (base /), en-US 1280x800 and zh-CN 390x844: no-method status 1, SSO copy absent, break-glass 0, password field 0; control leg with password on: password field 1, status 0.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 8a6b2ec5: 34 families, each exit 0, including node scripts/check-sdui-manifest.mjs and --self-test; --ran reconciliation 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Re-derived at e1a2c064: same list; check-sdui-manifest, check-changeset-no-major, check-empty-changeset, check:nul-bytes re-ran exit 0. PR body gates on the stored body: check-changeset-no-major, check-adr-0087-registration, check-closing-keyword-parity, check-partof-closing-keyword exit 0. PR check-runs: in_progress at report time.", "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm", "deviations": "none. Declaration lines copied from the claim. The PR opened before #29105 landed on the seat's instruction (seat 2 agreed #29159 lands first). The PR body opens with the harness attribution block, then Fixes #29159.", "mcp_calls": "3: mcp__github__issue_read x3 (get, get_comments x2); no write tool", "api_writes": "2: relay pr_create (objectstack-ai/objectstack#29257, read back identical, 7560 bytes) and this post-stamped comment; plus git push x4 (not REST)", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: read from source and a unit render, not measured at a public door · the email-routed Sign in with SSO button renders only inside the password form, so under features.ssoEnforced with features.sso true (and now with password off and sso true) it is reached only through Use a password instead, whose password submit the server refuses when email+password is off · dedupe words: LoginForm SSO button break-glass ssoEnforced email domain · carrier: none, noted in the PR's Acceptance notes, not filed" ] }