Repository navigation
fix(plugin-auth)!: clearing an authentication setting, or resetting the namespace, takes effect at once (#30312) - #30447
Conversation
…ared value in the same pass (WIP) Co-Authored-By: Claude <[email protected]> Claude-Session: https://claude.ai/code/session_01Utp6CZPLJKSBMtARXMHc4W
…ared toggle at its declaration, pins Co-Authored-By: Claude <[email protected]> Claude-Session: https://claude.ai/code/session_01Utp6CZPLJKSBMtARXMHc4W
…geset Co-Authored-By: Claude <[email protected]> Claude-Session: https://claude.ai/code/session_01Utp6CZPLJKSBMtARXMHc4W
Co-Authored-By: Claude <[email protected]> Claude-Session: https://claude.ai/code/session_01Utp6CZPLJKSBMtARXMHc4W
📓 Docs Drift CheckThis PR changes 1 package(s): 21 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1bcbb13d7278e37b8f048d5381f01c837b5a5b85 && git checkout 1bcbb13d7278e37b8f048d5381f01c837b5a5b85
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f506b79d85790a8f74a762ac4031090c7fbdae25 c22645ebea8b55d8c6e6592c8546d731657720cb && git checkout -B drift-repro f506b79d85790a8f74a762ac4031090c7fbdae25 && git merge --no-ff c22645ebea8b55d8c6e6592c8546d731657720cb
node scripts/docs-audit/affected-docs.mjs --json f506b79d85790a8f74a762ac4031090c7fbdae25
|
|
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL Contract reviewServed-tier: Face: declared narrowing ( ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS Must-fix: none. Owed before queue (seat): dogfood battery pinned to the landing head (③). Non-blocking: ① 9 (rate-limit half-clear residual, filing recommended), ④ rig duplication note, ② wording note on the reset reading. |
Co-Authored-By: Claude <[email protected]> Claude-Session: https://claude.ai/code/session_01Utp6CZPLJKSBMtARXMHc4W # Conflicts: # packages/plugins/plugin-auth/src/auth-plugin.ts
|
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL Contract review — deltaServed-tier: Shape of the delta. ① Derived judgments
③ Boundary flags
Implemented-by: VERDICT: PASS Must-fix: none. Seat action before queue: update the PR body's Verification section from |
|
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL 维护者速读
席位核对 — ACCEPT · domain:server#2 · head
|
Requested by Bill · project thread
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
维护者速读
auth:configure,没声明就回内置默认),不再要重启。覆盖全部 33 个认证设置:锁定阈值、每人最大会话、密码策略、会话时长、强制 MFA、API 密钥期限、限流、Google、受众策略等;IP 白名单(Allowed IP ranges: an administrator can exclude their own address unwarned, and clearing the field does not take effect until a restart #30203)并入同一机制。yes (narrowing)):部署声明"邮箱密码登录关"、在 Setup 里打开、再关掉了 Google,此时清空"邮箱密码登录"会被拒存(400SETTINGS_VALIDATION),因为清空现在真的会把它关掉、导致无人能登录。其余清空只是开始生效。环境变量锁定的设置不受影响。回滚即撤本 PR。packages/plugins/plugin-auth/src/auth-settings-declared.ts(回落表,每个键走哪一片);auth-plugin.ts的applySettings新增回落段与受众回落;sign-in-method-floor.ts清空按声明值判的一行。pnpm --filter @objectstack/plugin-auth exec vitest run src/auth-settings-clear.pin.test.ts,预期 17/17 通过,其中第一组断言锁定阈值清空后当场回到声明的 7。Fixes #30312
Clause-②: yes (narrowing) — one save an administrator could make before is now refused at the auth settings save door with the existing
SETTINGS_VALIDATION/invalid_valuefield error atemail_password_enabled: clearing that toggle on a deployment that itself declared email and password off, when nothing else signs anyone in after the save. Before, that clear was saved and changed nothing at runtime; now a clear applies, and that one would lock everyone out. No error code, settings key or export is added or removed.Metadata-protocol: none
Skills-docs: none — no file under
skills/**teaches the auth settings namespace, its clear or the sign-in floor.Why
PUT /api/settings/authwith a key set tonull(the console's clear), or the namespace reset, answers saved, and the key resolvessource: 'default'. Everyif (isExplicit(key))branch inapplySettingsskipped such a key, so the running value kept the last stored one until a restart. Re-measured first ondbf53efe, realAuthPlugin+SettingsService:lockout_threshold3 andmax_concurrent_sessions_per_user2 stayed 3 / 2 aftersetMany(…, null); a reset leftlockout_threshold4. Onlyallowed_ip_rangeshad a reset arm (#30203, PR #30309).What changes
elsearms.auth-settings-declared.tsholds two pieces of data.captureDeclaredAuthOptionstakes, once ininit()afterauth:configureand before the manager is built, the deployment's own value of every option anauth.*key drives.AUTH_SETTING_RESTOREnames, for each of the 33 keys of the shipped auth manifest, the piece its declared value goes back through.applySettings. The pass keeps the set of keys the running config last took from the store. A key in that set that now resolvessource: 'default'is restored in the same pass, through the same piece a save of it rides, so the manager judges a restore as it judges a save:allowed_ip_ranges) ride the plain patch;emailAndPasswordfields (email_password_enabled,signup_enabled,require_email_verification,password_min_length,password_max_length) are each their own refusable piece; the verification flag restores as the deployment's declaration (plugin-auth: under theopenaudience posture, honour an explicitrequireEmailVerification: false(email_domainstays forced) #20389), as at boot;password_reject_breachedis its ownpluginspiece;mfa_requiredrestores as a save of the declared value would apply: a declared on is one piece withplugins.twoFactor, a declared off clears the requirement and leaves the enrollment path standing, exactly as an explicit off does;session_expiry_days/session_refresh_daysmerge over the declaredsessionblock (a stored field overrides its declared twin; before, a stored field replaced the whole block);audience_postureis not stored, through the same merged-result validation (a sibling still stored without a posture is IGNORED, as before).source: 'env'and are unaffected.allowed_ip_rangesmoves onto the same table;declaredAllowedIpRanges/allowedIpRangesFromSettingsare gone. Its Allowed IP ranges: an administrator can exclude their own address unwarned, and clearing the field does not take effect until a restart #30203 pins pass unchanged (an undeclared list restores toundefined, which the gate reads as open, as[]was).sign-in-method-floor.ts, file surface widened by the seat). The floor read a clearedemail_password_enabledas on (the manifest default). With clears now applying, a deployment that declared email and password off could clear the toggle into a lockout. Measured before the floor change: declared off + Google, Setup sets email/password on, then Google off, then clears the toggle → 200, and the running config endsemailPassword.enabled: falsewith no provider. The floor now takes an optionaldeclaredEmailPasswordEnabled()reader (frombindAuthSettings) and judges a cleared toggle at the declaration; the same clear is refused 400 and nothing changes. With another way in standing, the clear saves and applies.Tests
packages/plugins/plugin-auth/src/auth-settings-clear.pin.test.ts, 17 cases, on #30203's composition (realAuthPlugin, realSettingsServicewith the shipped auth manifest, real settings routes for the save-door cases):lockoutThreshold: 7back to 7; undeclared max sessions back toundefined); password classes + API-key max expiry; a pass about one stored key applies only that key's option (applyConfigPatchspy).emailAndPasswordfields; breached-password flag;mfa_requiredto a declared off (enrollment stays) and to a declared on (withtwoFactor); the floor refuses the clear with no other way in (code, field,invalid_value, runtime unchanged) and lets it through with Google standing.OS_AUTH_LOCKOUT_THRESHOLDsurvives a reset.Ablations, each through
scripts/ablation-replace.mjs(anchor 1 → 0, restored to the HEAD blob,git diff HEADempty; tests importsrc/, no dist in the path):emailAndPasswordarmpluginsarmmfa_requireddeclared-off armmfa_requireddeclared-on armAcceptance notes
rateLimit; unchanged from before.mfaRequired: truewhileOS_AUTH_TWO_FACTOR=falserestores to its own contradictory boot state on a clear; the gate's once-per-process warning (Organization require-MFA can be turned on with two-factor off, and after the grace period everyone is locked out with no way to enroll #30201) names it. Not filed: boot already lands there.Verification
Readings under
scripts/pm/os-verify-lock.sh, shared box. Landing headc22645eb:8b9f159fplus a merge oforigin/mainatf506b79d(29 commits, among them PR #30449 for #30300). One hand resolution, inauth-plugin.ts: #30449's one-lineregisterSignInMethodFloor(…, this.signInMethodFloorDeps(…), …)taken, and this PR'sdeclaredEmailPasswordEnabledreader moved into that shared constructor (read only by the save floor's batch fallback; the SSO-provider delete floor and the boot report judge the running config). Delta contract review PASS, comment 6659465418.On
c22645eb, one run under the verify lock (VERDICT command-exit 0, every step exit 0): closure build ofdogfood^...andservice-settings^...; full dogfood Test Files 419 passed, 3 skipped (422), Tests 3492 passed, 36 skipped (3528);@objectstack/plugin-authtest 225/225 files, 3991 passed, 10 skipped (4001); plugin-auth typecheck exit 0 (check:test-typecheckOK);@objectstack/service-settingstest 68/68 files, 1156/1156; service-settings typecheck exit 0.Earlier readings, kept for the record: code head
f58be8f5;8b9f159fwas that plus a merge oforigin/main(23 commits, none touchingauth-plugin.ts,sign-in-method-floor.tsorservice-settings).Suites and typecheck
8b9f159f:pnpm --filter '@objectstack/plugin-auth^...' build && pnpm --filter @objectstack/plugin-auth test && pnpm --filter @objectstack/plugin-auth run typecheck:VERDICT command-exit 0; Test Files 225 passed (225), Tests 3971 passed, 10 skipped (3981);check:test-typecheck: OK. (A first run after the merge, before rebuilding the closure, read a stale@objectstack/specdist and failedpublic-feature-registry.test.tson main's newemailDeliveryflag; the rebuilt run above is green.)f58be8f5: the same suite 225/225 files, 3966 passed;pnpm lint: exit 0.Full dogfood battery (owed by the narrowing arm)
On
f58be8f5:pnpm --filter '@objectstack/dogfood^...' build && pnpm --filter @objectstack/dogfood test(no shard, no path):VERDICT command-exit 0, held the lock 40m35s; Test Files 419 passed, 3 skipped (422); Tests 3492 passed, 36 skipped (3528); no failure.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 64 commands onf58be8f5; each run with its exit code recorded before any pipe.--ran: 64 derived, 63 run, 1 NOT-MEASURED, 0 UNRUN. NOT MEASURED:pnpm check:dual-build-cjs-loads(exit 3, PREREQUISITE NOT MET: it needs a whole-workspace build; CI runs it).check-plugin-teardown-shape --self-testfirst exited 3 (shallow clone, pinned fixture commit), then 0 after fetching that commit. Every other command exited 0.Generated by Claude Code