Skip to content

fix(app-shell,auth,i18n): the organization form says requiring MFA needs two-factor on, and the enrollment screen names a two-factor-off deployment in the user's language (#30295) - #30369

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-ccinqe
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-ccinqe

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

domain:ui#3 · os-bill · session_01Utp6CbPN5UYMnBaNJPJWfG

维护者速读

  • 改了什么:两步验证关着时,Setup 里编辑组织的表单在标题下直接说明「强制多因素认证」需要先开两步验证、勾上保存会被拒、去哪里开;两步验证开着时,保存成功把它打开后,提示里多一句:哪些成员要去绑定身份验证器、从什么时候起算宽限期(默认 7 天)。注册身份验证器的页面遇到 404(两步验证没开)时,显示本地化的「此部署未开启双重验证,请联系管理员在设置 → 认证中开启」,不再显示 Two-factor enable failed (404)。
  • 为什么改:服务端半边 fix(plugin-auth)!: an organization cannot require MFA while two-factor enrollment is off (#30201) #30293 已经拒存并解除锁死,但界面上管理员事先看不到任何说明,被拒后只有一段英文;成员若被引到注册页,看到的是一句未翻译的 404。
  • 风险与回滚:只改界面文案与两个认证调用的报错字段(多带 status、code,原 message 不变),新增 3 个翻译键、十个语言包齐全。没有服务端、元数据或权限改动。回滚即撤本 PR。
  • 先看:ui/packages/app-shell/src/console/organizationMfaNotices.ts(何时提示、何时不提示);ui/packages/app-shell/src/console/AppContent.tsx 的组织表单挂接与保存成功 toast;RemediationOverlay.tsx 的 enrollRefusalMessage。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/console/__tests__/organizationMfaNotices-30295.test.tsx packages/app-shell/src/console/__tests__/RemediationOverlay.enrollUnavailable-30295.test.tsx,预期 11/11 通过。
  • 需要你做的:无,席位落地。

Fixes #30295

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

What changes

The three Wanted items of the card, on a zh-CN console against a local app-showcase backend carrying #30293:

  • Before saving (item 1, first half). While GET /api/v1/auth/config answers features.twoFactor: false, the sys_organization edit form (the Console's global record-form modal in AppContent.tsx; the object declares no editMode: 'page') shows a notice as the dialog's description: Require Multi-Factor Auth needs two-factor, a save with it ticked is refused, and how to turn two-factor on (Require multi-factor authentication in Setup → Authentication, which applies to every organization, or OS_AUTH_TWO_FACTOR=true). The notice is judged by organizationMfaFormNotice in the new organizationMfaNotices.ts: only that object, only a form that offers require_mfa, and only when the flag reads false (an unread config says nothing).
  • The refused save (item 1, second half): already held, no change. Measured on the before tree: the generic form shows the server's 400 VALIDATION_FAILED text on the require_mfa field, not a generic failure (the fields[] entry fix(plugin-auth)!: an organization cannot require MFA while two-factor enrollment is off (#30201) #30293 sends). See Acceptance notes for its language.
  • The accepted save (item 2). A save that turns require_mfa on for a row that stored it off adds a line to the saved toast (the existing notice slot of toastRecordSave): every member without an authenticator app must set one up, and is blocked from data once the MFA grace period ends (7 days unless changed in Setup → Authentication), counted from their first use after the change; members already enrolled are not affected. The stored value comes from one findOne when the organization form opens (useStoredRequireMfa, keyed on the object, the record id and the open flag); a create counts as off, an unread value as off, a row already on says nothing.
  • The enrollment screen (item 3). enrollTotp / verifyTotp in @object-ui/auth now reject with the HTTP status and the server's code beside the unchanged message (twoFactorEnrollError). The authenticator step of RemediationOverlay reads 404 (the /two-factor/* endpoints are mounted only while two-factor is on) as auth.remediation.mfa.unavailable, saying enrollment is off and that an administrator turns it on in Setup → Authentication, and INVALID_PASSWORD as the password screen's existing sentence. Any other refusal keeps the server's message.
  • i18n (the widening). Three new keys in all ten packs: auth.remediation.mfa.unavailable, console.organizationMfa.needsTwoFactor, console.organizationMfa.nowRequired.
  • Changeset .changeset/30295-org-mfa-notice-and-enroll-refusal.md (patch for @object-ui/app-shell, @object-ui/auth, @object-ui/i18n).

Why

#30293 made the server refuse require_mfa = true while two-factor is off and stopped the gate from holding anyone it cannot enroll. Nothing in the console said so: the form offered the checkbox with the object's help ("When true, every member … must enroll"), the administrator learned the dependency only from the refusal, a successful save said nothing about who is now held or when, and a member sent to enroll on a deployment without two-factor read Two-factor enable failed (404).

Tests

  • ui/packages/app-shell/src/console/__tests__/organizationMfaNotices-30295.test.tsx (new, 7): the notice's three conditions (two-factor off; on or unread; another object or no field), the saved line's (turned on; already on, left off, another object; unread counts as off), and the stored-value hook (one read per open edit, create is off, closed form or another object reads nothing, a failed read stays unread).
  • ui/packages/app-shell/src/console/__tests__/RemediationOverlay.enrollUnavailable-30295.test.tsx (new, 4): zh and en 404 sentences (and the English failed (404) gone), zh INVALID_PASSWORD, and a control where a 429 keeps the server's message. The other RemediationOverlay files each carry their own useAuth mock and no shared harness exists, so this is its own file.
  • ui/packages/auth/src/__tests__/createAuthClient.test.ts: a new top-level describe in the existing harness, three pins through the real better-auth client and a mocked fetch (404 on enable and on verify carries status, a wrong password carries code and status, messages unchanged).
  • Ablations with scripts/ablation-replace.mjs, each restore proven blob == HEAD (these suites import source, so no build sits between mutation and run). A, C and D ran on c4ec9241; B ran on c4ec9241 and again on 9fb2f487, after its pins moved into the harness file (3 red of 37):
    • A, the overlay's 404 branch deleted: 2 red (both 404 sentences).
    • B, status no longer copied onto the error: 3 red (expected undefined to be 404).
    • C, the notice's two-factor check deleted: 1 red (expected 'console.organizationMfa.needsTwoFactor' to be undefined).
    • D, the already-on check deleted: 1 red (expected 'console.organizationMfa.nowRequired' to be undefined).

Acceptance notes

  • The refusal under the field is the server's English. In a zh-CN session the field shows fix(plugin-auth)!: an organization cannot require MFA while two-factor enrollment is off (#30201) #30293's English message (it names OS_AUTH_TWO_FACTOR and auth.mfa_required). It is server-authored text; this PR adds the localized notice above it rather than replacing it. Localizing that refusal belongs to the server's message.
  • The grace period's number is the default. The console has no read of auth.mfa_grace_period_days (/api/v1/auth/config does not publish it, @objectstack/client has no settings read, and the settings page is admin-only), so the saved line says "7 days unless changed in Setup → Authentication". The server knows the configured value; a write notice from the server would carry it.
  • The page-mode host is not wired. RecordFormPage would show neither line for an organization; sys_organization declares no editMode: 'page', so the modal is the only host a user reaches today.
  • The object's field help (packages/platform-objects, server metadata) still says only "When true, every member … must enroll"; this PR leaves it alone (out of this card's file surface).
  • Two names for two switches: the field is 强制多因素认证 (sys_organization.require_mfa, every member of one organization) and the setting is 要求多因素认证 (auth.mfa_required, every organization, and it turns two-factor on). The notice names both in each pack's own spelling.

Verification

Suites, type-check and gates ran on 30cfab99 (main 9afcd6a6, which carries #30293, plus this branch); the final head fc268ce2 merges main again (no ui/ file moved) and re-ran the scoped suites.

Browser check (zh-CN, local app-showcase backend, Playwright)

The screenshots are in the project files (ui-seat3/30295/); no image host is reachable from this session. What each shows:

  • before-enroll.png / after-enroll.png: the MFA overlay raised by answering one data read with the server's 403 MFA_REQUIRED (two-factor is off, so the real gate no longer holds anyone), then the password step against the real backend, whose POST /two-factor/enable answered 404. Before: Two-factor enable failed (404). After: 此部署未开启双重验证,暂时无法绑定身份验证器。请联系管理员在“设置 → 认证”中开启后再试。
  • before-form.png / after-form.png: the 编辑组织 dialog with two-factor off. Before: no notice. After: the notice under the title.
  • before-saved.png / after-saved.png: the box ticked and 更新 pressed with two-factor off. Both show the server's refusal on the field; the row stays require_mfa: false (read back over REST).
  • after-2fa-saved.png: backend restarted with OS_AUTH_TWO_FACTOR=true, the box ticked and saved: the toast reads 组织“Default Organization”已更新 plus 本组织所有尚未绑定身份验证器的成员现在都必须绑定。每位成员从此次变更后第一次使用起开始计算 MFA 宽限期(默认 7 天,可在“设置 → 认证”中修改)… The row reads require_mfa: true.
Suites and type-check (under scripts/pm/os-verify-lock.sh)
  • pnpm --dir ui exec vitest run over the three changed test files plus the files importing the changed modules' behaviour (the five other RemediationOverlay.* files, changePassword-code-27869, and the i18n parity files all-locales-key-parity, authRemediation-locale-parity, en-zh-key-parity, console-namespace-3546): 14 files, 158 tests passed (the auth pins then moved into createAuthClient.test.ts: 37/37).
  • pnpm exec turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/auth --filter=@object-ui/i18n --concurrency=2: Tasks 70 successful, 70 total. VERDICT command-exit 0.
Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 30cfab99: 35 derived, each run with its exit code captured before any pipe: 35 exit 0 (check:type-check-debt under the lock). node scripts/check-sdui-manifest.mjs exit 0. --ran over the record (COMMAND :: exit N): 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN, exit 0 ("a DERIVED zero — all 35 recorded an exit code and none of them is 3"). The merge of main (fc268ce2) brought no file under ui/ or .changeset/; the derivation on the merged head is byte-identical, and the scoped suites re-ran green there (13 files, 158 tests). pnpm lint is CI's run.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic (packages/spec not touched).
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint is CI's run.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 block is Chinese by the Communication rule).

🤖 Generated with Claude Code


Generated by Claude Code

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Nov 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CbPN5UYMnBaNJPJWfG

Known red, not this PR's · Lint UI (job 127131167427). Same signature as main-red anchor #30367: @objectstack/spec check:liveness reports query/searchFields → packages/objectql/src/engine.ts (the citation lost its key when PR #30341 moved engine.ts docblocks). This diff touches only ui/packages/app-shell, ui/packages/auth, ui/packages/i18n and a changeset. The fix lands on main through #30367; this PR merges origin/main after it, then the seat re-reads CI.

…zation MFA notice

console.organizationMfa.needsTwoFactor quotes the Setup switch it names
(„Mehrstufige Authentifizierung verlangen“), one more correctly paired
span: 103 -> 104 in both pinned counts.

Claude-Session: https://claude.ai/code/session_01Utp6CYeUM6wKr7VK5xLHXn
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CbPN5UYMnBaNJPJWfG

维护者速读

席位核对 — ACCEPT · head 8031747

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 10:13
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 10ecafd Nov 5, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ccinqe branch November 5, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Setup: organization 强制多因素认证 says nothing when two-factor is off, and the enrollment screen's refusal is an untranslated 404

1 participant