Repository navigation
feat(plugin-auth): the platform OP emits the standard locale claim at userinfo - #26012
Conversation
Claude-Session: https://claude.ai/code/session_01XCJ2r3w7jCsheYguravx4H Co-authored-by: Claude <noreply@anthropic.com>
… OIDC discovery Claude-Session: https://claude.ai/code/session_01XCJ2r3w7jCsheYguravx4H Co-authored-by: Claude <noreply@anthropic.com>
… userinfo Claude-Session: https://claude.ai/code/session_01XCJ2r3w7jCsheYguravx4H Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 21 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 213dd7d067813bf3f441b6dcac2bcfd624a18017 && git checkout 213dd7d067813bf3f441b6dcac2bcfd624a18017
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3518664cb63a56903dcb69414e377d04b97cae57 630e39c241aafdaf16e276229aa28eb187c6955e && git checkout -B drift-repro 3518664cb63a56903dcb69414e377d04b97cae57 && git merge --no-ff 630e39c241aafdaf16e276229aa28eb187c6955e
node scripts/docs-audit/affected-docs.mjs --json 3518664cb63a56903dcb69414e377d04b97cae57
|
席位核对 · ACCEPT ·
|
| item | reading |
|---|---|
| Shape | draft, base main, Fixes #25529, Clause-②: yes, Metadata-protocol: none, patch on @objectstack/plugin-auth. |
| Files | 4 files, +443/−0: oidc-userinfo-locale-claim.ts, its wiring in the OP plugin list, one suite, one changeset. No host key, no column, no additionalField. Not governed. |
| Fix | the platform OP's userinfo carries locale under the profile scope when the user's sys_user.locale is a non-empty BCP-47 tag, read through the data engine in system context; omitted otherwise; userinfo only. |
| Evidence | red first 3/16 failed; ablation reds the same 3; plugin-auth 3737 green; one real OP → RP round trip stores zh-CN; 68/68 derived gates. |
| Carried | objectstack-ai/cloud#3159 resumes as a pin bump once this ships. |
| Landing | 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready + auto-merge through the relay once every check on this head is green or an expected skip. |
Generated by Claude Code
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 40300684735 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
席位核对 · queue note ·
|
Requested by Leehom · project thread
Fixes #25529
Clause-②: yes
Metadata-protocol: none
What
The OIDC provider plugin-auth composes (
oauthProvider({...})inauth-manager-plugin-list.ts, where the composition now lives after theauth-manager.tssplit) gainscustomUserInfoClaims. It returns{ locale }only when the access token's scopes includeprofileand the user'ssys_user.locale, read through the data engine in system context, is a tag the platform's one column reader accepts (normalizeRecipientLocale: non-empty, BCP-47-shaped, never the stringified-nothing literals). Otherwise it returns nothing. The rule lives in the newoidc-userinfo-locale-claim.ts.additionalField: every ObjectStack OP emits the claim.locale.warnnaming the user (functional degradation, AGENTS.md degradation log levels).Measured premises (tree
05f7758f05)@better-auth/oauth-providerare both1.7.12onmain.customUserInfoClaims(info: { user, scopes, jwt, requestedClaims })exists there (oauth-DXiqbRzK.d.mts:1761), is called only when scopes are non-empty, and its result is merged after the provider's own claims withsubre-pinned (introspect-DeWu79D4.mjsuserinfo).auth-manager.ts:4252on the filing tree is nowauth-manager-plugin-list.ts:500. Still plugin-auth, inside the claimed file surface.Tests
packages/plugins/plugin-auth/src/oidc-userinfo-locale-claim.test.tsdrives a REALAuthManagerwith its OIDC provider on, over the shared in-memory engine, through its HTTP surface (sign-up, create-client, authorize with PKCE, consent, token, userinfo):profile: userinfolocale: "zh-CN", ID token withoutlocale;localekey;"undefined"literal: nolocalekey;profilenot granted: nolocale, and the column is never read;sub/name, nolocale, exactly onewarncarrying the user id;AuthManageras the platform RP (genericOAuth, providerobjectstack-cloud, configured by OIDC discovery) signs in through this OP over afetchrouted to the OP's handler (the discovery document throughoauthProviderOpenIdConfigMetadata, asregisterOidcDiscoveryRoutesmounts it). The RP's JIT path (idp-locale-claim.ts) storeszh-CNon the RP's ownsys_user.Red first, on
mainplus the test only (b6c90526): 3 failed / 13 passed — the zh-CN leg (expected undefined to be 'zh-CN'), the read-failure leg (warncalled 0 times) and the round trip (RP user's localeundefined). The other three legs assert absence and are green onmainby construction.Green with the fix (
630e39c2): 16/16 in the file (the count includes the imported shared engine module's own cases).Ablation (
node scripts/ablation-replace.mjs, anchorcustomUserInfoClaims: ({ user, scopes }) =>renamed so the provider never reads it; the test imports./auth-managerfrom source, nodistin the path): anchor 1 → 0, blobdade996dd516→2de5c1541934; the same 3 legs red (3 failed / 13 passed); restored to the HEAD blobdade996dd516,git diff HEADempty.pnpm --filter @objectstack/plugin-auth typecheck: exit 0 (incl.check:test-typecheck: no new test-layer errors).pnpm --filter @objectstack/plugin-auth exec vitest run: 196 files / 3737 passed / 10 skipped, exit 0.eslint --no-inline-config packages/plugins/plugin-auth: exit 0.Gates (head
630e39c2)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon this diff: 68 commands, every exit code captured before any pipe;--ranreconciliation: 68 derived, 68 run, 0 NOT-MEASURED, exit 0. Three first runs exited 3 (PREREQUISITE NOT MET, nothing measured) and were re-run green after the prerequisite:check-plugin-teardown-shape.mjs --self-test(shallow clone, positive-control commit fetched),check:dual-build-cjs-loads(needs a full build;turbo run build120/120),check:type-check-debt(lock budget spent behind that build). Every CI family is read off this PR's check-runs, not re-run here.Acceptance notes
claims_supporteddoes not listlocale. The provider computes that list itself from its standard-claim table and overwrites anyclaimsoption; onlyadvertisedMetadata.claims_supportedwould replace it wholesale. OIDC Discovery makes the list RECOMMENDED and non-exhaustive, the platform RP never reads it, and aclaims.userinforequest naminglocaleis filtered by it, so a client asking forlocaleindividually withoutprofilegets nothing, which matches the ruled scope rule. Noted, not filed.sys_user.localeexists besidestoredRecipientLocale(email/SMS). It shares the column constant and the normalizer; it does not reuse that function because that function swallows a read failure silently by contract, and this order asks the failure to be logged.Generated by Claude Code