Repository navigation
fix(auth): treat PASSWORD_RESET_REQUIRED as a remediation gate (objectui#12365) - #12368
objectstack-fleet[bot] merged 2 commits into
Conversation
…tui#12365) The backend answers an administrator-forced password change with 403 PASSWORD_RESET_REQUIRED (objectstack#23287 ruling C'). detectAuthGate now recognises it, and RemediationOverlay routes it to the change-password form under its own title, since "your password has expired" is false for a forced reset. Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs read: card #12365 body, the triage grade ( ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
1 similar comment
Contract reviewServed-tier: Inputs read: card #12365 body, the triage grade ( ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #12365
Clause-②: yes
What
The backend answers an administrator-forced password change with
403 PASSWORD_RESET_REQUIRED, its own code, distinct fromPASSWORD_EXPIRED(objectstack-ai/objectstack#23287 ruling C′, carried by objectstack-ai/objectstack#23440). Before this PR the client ignored it, so a force-reset user would get raw 403s and no overlay.packages/auth/src/auth-gate-events.ts:detectAuthGateacceptsPASSWORD_RESET_REQUIREDbesideMFA_REQUIREDandPASSWORD_EXPIRED. The three codes now sit in one module-private set.AuthGateInfo.codenames the new literal.packages/app-shell/src/console/RemediationOverlay.tsx: the overlay routesPASSWORD_RESET_REQUIREDto the change-password form, the same formPASSWORD_EXPIREDgets (renamedExpiredPasswordFormtoChangePasswordForm).auth.remediation.password.resetTitle("You must change your password"), added to all ten built-in packs.PASSWORD_EXPIREDkeeps its title. The fallback message ("Please set a new password to continue.") is true for both and is unchanged.Harmless before the backend emits the code: nothing else raises it.
Spec derivation (mechanism assumption A2): measured, hand-written literal
The installed
@objectstack/spec18.0.0-next.0 has noAuthGateCodeSchemaand noPASSWORD_RESET_REQUIREDanywhere in itsdist. objectstackorigin/maindoes not have them either:git grepforAuthGateCodeSchemaorPASSWORD_RESET_REQUIREDunderpackagesreturns nothing. They exist only on the branch of objectstack-ai/objectstack#23440 (packages/spec/src/kernel/execution-context.zod.ts:AuthGateCodeSchema = z.enum(['PASSWORD_EXPIRED', 'PASSWORD_RESET_REQUIRED', 'MFA_REQUIRED'])). So the literal is hand-written, and a comment beside the set says to derive it from the spec export once the spec this package builds against has it. The hand-written set has the same three members as that enum.Published surface (Clause-②)
createAuthenticatedFetchnow emits the gate for one more code, soAuthProvidersetsremediationRequiredfor it.exportschanges.detectAuthGateandAuthGateInfoare not exported from@object-ui/auth's entry (src/index.tshas noauth-gate-eventsexport).remediationRequiredstays typed{ code: string; message: string }. The only type text that changes isAuthGateInfo.codeindist/auth-gate-events.d.ts. That file ships underfiles: ["dist"], but noexportspath reaches it..changeset/12365-password-reset-required-gate.md:minorfor@object-ui/auth,@object-ui/app-shelland@object-ui/i18n(new pack key).Tests
New pins (on HEAD
edc7f7a;7af6488only adds the changeset):packages/auth/src/__tests__/authGatePasswordResetRequired-12365.test.tsx: a 403PASSWORD_RESET_REQUIREDresponse throughcreateAuthenticatedFetchraises the event with the server's code and message.detectAuthGateaccepts all three codes, and refusesPERMISSION_DENIEDand a non-403.packages/app-shell/src/console/__tests__/RemediationOverlay.passwordResetRequired-12365.test.tsx: forPASSWORD_RESET_REQUIREDthe overlay shows the change-password form (current, new and confirm fields) under the reset title, not the expiry title and not the MFA form.PASSWORD_EXPIREDkeeps the expiry title.pnpm exec vitest runon the two pins plusauthRemediation-locale-parity.test.tsandcreateAuthenticatedFetch.test.tsx:Test Files 4 passed (4),Tests 25 passed (25).Ablation (one-time, on HEAD
7af6488; mutated withablation-replace.mjs, which checks the anchor count on disk and restores to HEAD): both test files resolve@object-ui/*tosrcthrough the root vitest aliases, so no build sits between the mutation and the run.PASSWORD_RESET_REQUIREDfrom the accepted set: both auth pins go red (expected [] to deeply equal,expected null to deeply equal). Restored: the blob equals HEAD andgit diff HEADis empty.PASSWORD_EXPIREDto the form: the forced-reset overlay pin goes red and the expiry pin stays green. Restored the same way.Gates (all run on HEAD
7af6488, every exit code recorded):turbo run build --filter='@object-ui/app-shell^...'exited 0:type-checkexits 0 for@object-ui/auth,@object-ui/i18nand@object-ui/app-shell.check:phantom-deps,check:self-import,check:unreferenced-sources,check:doc-example-readers,check:handler-key-reads,check:side-effects-array,check:element-data-source-declaration,check:spec-symbols,check:action-forward-parity,check:designer-field-key-parity,check:icon-record-names,check:i18n-keys,check:i18n-drift,check:i18n-designer-parity,check-changeset-fixed.mjsandcheck-changeset-presence.mjs.check:spec-floorsexits 1 on main for an unrelated reason (objectui#12334): "The scan collapsed: 0 package(s) declare @objectstack/spec in a consumer-facing field". Recorded, not fixed here.eslint --no-inline-config --format jsonover the 14 changed.ts/.tsxfiles reports 14 files, 0 errors and 0 warnings.eslint.config.jssets noparserOptions.projectand noprojectService, so the linting is not type-aware and this diff cannot change the verdict on any file it does not touch. The fullpnpm lintis left to CI.Acceptance notes
detectAuthGate"published". Measured: it is shipped indistbut not exported from the package entry. The published face that widens is the behaviour ofcreateAuthenticatedFetchandAuthProvider.Generated by Claude Code