Skip to content

Commit d36d4ad

Browse files
feat(plugin-auth): a SCIM Credentials Setup entry while SCIM is wired (#31222)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XYauubomxzQiKW9cdVCunsZ)_ Fixes #31123 Clause-②: yes (widening) Metadata-protocol: none ## 维护者速读 - **改了什么**:开启 SCIM 的部署里,Setup → 访问控制 下多出一个「SCIM Credentials」菜单项,打开 SCIM 连接凭据列表(上面就是已上线的「签发凭据 / 吊销凭据」按钮)。没开 SCIM 时不出现。 - **为什么改**:签发和吊销按钮已经上线,但菜单里找不到入口,管理员只能手敲 URL 才能配置 Okta / Entra ID 的 SCIM 对接。 - **风险与回滚**:只加一个菜单项和六种语言的标签,权限沿用 SSO 菜单项(平台管理员),签发/吊销接口自身的权限不变;回滚即 revert 本 PR。 - **先看**:`packages/plugins/plugin-auth/src/auth-plugin.ts`(菜单项与开关,复用签发接口的同一个 SCIM 判定);`packages/plugins/plugin-auth/src/scim-credentials-setup-nav.test.ts`;`packages/platform-objects/src/apps/translations/en.ts`。 - **两分钟验证**:`pnpm --filter @objectstack/plugin-auth exec vitest run src/scim-credentials-setup-nav.test.ts`,预期 6 个用例全绿(开 SCIM 有入口、关 SCIM 无入口)。 - **需要你做的**:无,席位落地。 ## What changes - **plugin-auth** builds its Setup > Access Control items from the two capabilities it owns. "SSO Providers" is unchanged (still only while `isSsoWired()`). A new item `nav_scim_credentials`, "SCIM Credentials", opens `sys_scim_connection_credential` (the object PR #31119's Mint / Revoke Credential actions live on), with icon `key-round` and the SSO entry's gate, `requiredPermissions: ['manage_platform_settings']`. It is contributed only while `this.authManager.isScimWired()` is true. That is the method the `/admin/scim/mint-credential` door's `isScimEnabled` dep calls before it refuses 503, so there is one spelling of "SCIM wired" and the menu cannot show an entry whose Mint button can only fail. The routes keep their own platform-admin gate, so the entry widens nothing. - The contribution object is still spread in only when at least one item is wired, so a deployment with neither SSO nor SCIM registers no `navigationContributions` from plugin-auth, as before. Both items share the one existing contribution (group `group_access_control`, priority 250); SCIM sorts after SSO. - **platform-objects**: the label `apps.setup.navigation.nav_scim_credentials` in all six Setup bundles (en "SCIM Credentials", zh-CN "SCIM 凭据", ja-JP "SCIM 資格情報", es-ES "Credenciales SCIM", zh-Hant "SCIM 憑證", ar "بيانات اعتماد SCIM"), each the short form of the object's own translated `pluralLabel`. The five non-English locales record the en source hash (`2d94a32c33ca2957`) so a later source edit marks them stale. The id joins `CONDITIONAL_SETUP_NAV_IDS` in `setup-nav-dead-key-tombstone.test.ts`, because `check:app-nav-i18n` boots one composition and never merges a gated-off entry (the same reason `nav_sso_providers` is listed there). - Clause-② measured: the bundle ships in platform-objects' `dist` (`files: ["dist", …]`) and the key is new, so `yes (widening)`; the claim's seat reading `no` is re-declared as the order asked. Changeset `.changeset/31123-scim-credentials-setup-entry.md`, patch for both packages. ## Tests - New `packages/plugins/plugin-auth/src/scim-credentials-setup-nav.test.ts` reads the manifest `init()` registers: the entry with SCIM on via config, with SCIM on via `OS_SCIM_ENABLED`, absent with SCIM off (and no other entry in its place), absent when the manager's `isScimWired()` answers false although the config says on, the SSO + SCIM order, and SSO alone unchanged. - Red first on `origin/main` `658d84f5`: 4 failed, 2 passed (the two absence cases already hold on main). - Ablation of the negative pins: replacing `this.authManager.isScimWired()` with an always-true marker turned 3 cases red ("is absent when SCIM is not wired…", "asks the manager the same question…", "SSO alone…"), restored with blob == HEAD and `git diff HEAD` empty. Removing the ja-JP label turned the tombstone pin red (`expected [ 'nav_scim_credentials' ] to deeply equal []`), restored the same way. ## Acceptance notes - Wording "SCIM Credentials" is the card's. The other locales shorten the object's `pluralLabel` the same way English does. - Comments outside this claim's file surface still say plugin-auth's "only nav contribution (`nav_sso_providers`)" is conditional: `packages/cli/scripts/check-app-nav-i18n.mjs` header, `packages/cli/test/platform-page-i18n-parity.test.ts`, `packages/qa/dogfood/test/platform-app-object-entry-views.test.ts`. Their reasoning still holds (every plugin-auth nav entry is conditional); only the count is now two. Not edited here. - `check:app-nav-i18n` is not in the derived list and has no reverse direction, so the new bundle key draws no orphan verdict; not run locally. ## Verification All local readings are at head `bb431ed4`. - `pnpm --filter @objectstack/plugin-auth test`: Test Files 235 passed (235), Tests 4174 passed | 10 skipped (4184). - `pnpm --filter @objectstack/platform-objects test`: Test Files 119 passed (119), Tests 1701 passed (1701). - `pnpm --filter @objectstack/plugin-auth typecheck` (tsc, examples tsconfig, `check:test-typecheck`) and `pnpm --filter @objectstack/platform-objects typecheck`: exit 0. - `eslint --no-inline-config packages/plugins/plugin-auth/src packages/platform-objects/src`: exit 0. - Derived gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` gives 68 families; all 68 ran with exit 0. `--ran` reconciliation: "68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN". <details><summary>Three gates that first answered exit 3 (prerequisite not met), and their re-runs</summary> - `node scripts/check-plugin-teardown-shape.mjs --self-test` needed the shallow clone deepened (`git fetch --depth=1 origin 621a487`); re-run exit 0, "48 cases pass". - `pnpm check:dual-build-cjs-loads` and `pnpm check:i18n` needed a full build (`turbo run build`, 120/120 tasks); re-run exit 0: "116 published require entry point(s) across 71 package(s) load" and "check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys)". </details> Not run locally, left to CI: `test:repo`, the dogfood battery (no `!` in the title and no `(narrowing)` arm), and `check:app-nav-i18n` (not derived; see Acceptance notes). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01FEkQ5kkU448zCPf5uHdBMT --- _Generated by [Claude Code](https://claude.ai/code/session_01FEkQ5kkU448zCPf5uHdBMT)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent df22adc commit d36d4ad

15 files changed

Lines changed: 209 additions & 18 deletions
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/plugin-auth': patch
3+
'@objectstack/platform-objects': patch
4+
---
5+
6+
feat(plugin-auth): a "SCIM Credentials" entry in Setup > Access Control while SCIM is wired
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
- **`@objectstack/plugin-auth`** contributes a Setup navigation entry `nav_scim_credentials` ("SCIM Credentials") that opens `sys_scim_connection_credential`, the object the console's Mint Credential and Revoke Credential actions live on. It sits in the Access Control group after SSO Providers and carries the same gate (`manage_platform_settings`). It is contributed only while SCIM is wired, decided by `isScimWired()`, the same answer the `/admin/scim/mint-credential` door reads before it refuses 503, so the entry never opens a list whose Mint button can only fail. The mint and revoke doors keep their own platform-admin gate; the entry widens nothing. The SSO Providers entry is unchanged, and a deployment with neither SSO nor SCIM wired gets no plugin-auth Setup entry, as before.
12+
- **`@objectstack/platform-objects`** adds the label `apps.setup.navigation.nav_scim_credentials` to the Setup app's translation bundle in en, zh-CN, ja-JP, es-ES, zh-Hant and ar (a new published key, hence the widening arm).

‎packages/platform-objects/src/apps/translations/ar.source-hashes.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export const arSourceHashes: Readonly<Record<string, string>> = {
100100
'apps.setup.navigation.nav_queue_members.label': '46715e86f5231603',
101101
'apps.setup.navigation.nav_assignment_rules.label': '1779fe384b23954f',
102102
'apps.setup.navigation.nav_sso_providers.label': '7b06b6a6abf53ba4',
103+
'apps.setup.navigation.nav_scim_credentials.label': '2d94a32c33ca2957',
103104
'apps.setup.navigation.nav_api_keys.label': '8cda9851248b0490',
104105
'apps.setup.navigation.nav_approvals_inbox.label': '0a9269b20d0266c9',
105106
'apps.setup.navigation.nav_approval_requests.label': 'aafd0f7c6a376f55',

‎packages/platform-objects/src/apps/translations/ar.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ export const ar: TranslationData = {
7171
// the rationale in `en.ts` (#6659). Wording matches this locale's
7272
// `sys_sso_provider.pluralLabel`.
7373
nav_sso_providers: { label: 'مزوّدو SSO' },
74+
// Conditionally contributed by plugin-auth (only while SCIM is wired),
75+
// the same case as the entry above; see `en.ts` (#31123).
76+
nav_scim_credentials: { label: 'بيانات اعتماد SCIM' },
7477
nav_api_keys: { label: 'مفاتيح API' },
7578

7679
// صندوق الموافقات = the Approvals Inbox component (#7234) — the surface

‎packages/platform-objects/src/apps/translations/en.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ export const en: TranslationData = {
109109
// object's own `pluralLabel` per locale (`sys_sso_provider`), since the
110110
// entry opens that object's list view.
111111
nav_sso_providers: { label: 'SSO Providers' },
112+
// `nav_scim_credentials` is the same case (#31123): plugin-auth
113+
// contributes it only while SCIM is wired (`isScimWired()`, the answer
114+
// the SCIM credential mint door reads), so no boot of
115+
// `check:app-nav-i18n` merges it and the tombstone file's conditional
116+
// list keeps it honest. It opens `sys_scim_connection_credential`'s
117+
// list; the label is the short form of that object's `pluralLabel`.
118+
nav_scim_credentials: { label: 'SCIM Credentials' },
112119
nav_api_keys: { label: 'API Keys' },
113120

114121
// Approvals. `nav_approvals_inbox` is the working surface (#7234); the

‎packages/platform-objects/src/apps/translations/es-ES.source-hashes.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export const esESSourceHashes: Readonly<Record<string, string>> = {
9595
'apps.setup.navigation.nav_sharing_rules.label': 'ceaa66fa5f1df4ad',
9696
'apps.setup.navigation.nav_record_shares.label': 'e261d340ceb25b9a',
9797
'apps.setup.navigation.nav_sso_providers.label': '7b06b6a6abf53ba4',
98+
'apps.setup.navigation.nav_scim_credentials.label': '2d94a32c33ca2957',
9899
'apps.setup.navigation.nav_api_keys.label': '8cda9851248b0490',
99100
'apps.setup.navigation.nav_connect_agent.label': 'a4542422fb5e8a68',
100101
'apps.setup.navigation.nav_approvals_inbox.label': '0a9269b20d0266c9',

‎packages/platform-objects/src/apps/translations/es-ES.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ export const esES: TranslationData = {
7171
// the rationale in `en.ts` (#6659). Wording matches this locale's
7272
// `sys_sso_provider.pluralLabel`.
7373
nav_sso_providers: { label: 'Proveedores SSO' },
74+
// Conditionally contributed by plugin-auth (only while SCIM is wired),
75+
// the same case as the entry above; see `en.ts` (#31123).
76+
nav_scim_credentials: { label: 'Credenciales SCIM' },
7477
nav_api_keys: { label: 'Claves API' },
7578

7679
nav_approvals_inbox: { label: 'Centro de aprobaciones' },

‎packages/platform-objects/src/apps/translations/ja-JP.source-hashes.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export const jaJPSourceHashes: Readonly<Record<string, string>> = {
9595
'apps.setup.navigation.nav_sharing_rules.label': 'ceaa66fa5f1df4ad',
9696
'apps.setup.navigation.nav_record_shares.label': 'e261d340ceb25b9a',
9797
'apps.setup.navigation.nav_sso_providers.label': '7b06b6a6abf53ba4',
98+
'apps.setup.navigation.nav_scim_credentials.label': '2d94a32c33ca2957',
9899
'apps.setup.navigation.nav_api_keys.label': '8cda9851248b0490',
99100
'apps.setup.navigation.nav_connect_agent.label': 'a4542422fb5e8a68',
100101
'apps.setup.navigation.nav_approvals_inbox.label': '0a9269b20d0266c9',

‎packages/platform-objects/src/apps/translations/ja-JP.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ export const jaJP: TranslationData = {
7171
// the rationale in `en.ts` (#6659). Wording matches this locale's
7272
// `sys_sso_provider.pluralLabel`.
7373
nav_sso_providers: { label: 'SSO プロバイダー' },
74+
// Conditionally contributed by plugin-auth (only while SCIM is wired),
75+
// the same case as the entry above; see `en.ts` (#31123).
76+
nav_scim_credentials: { label: 'SCIM 資格情報' },
7477
nav_api_keys: { label: 'API キー' },
7578

7679
nav_approvals_inbox: { label: '承認センター' },

‎packages/platform-objects/src/apps/translations/setup-nav-dead-key-tombstone.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,9 @@ describe('removed Setup nav ids stay removed (#6660)', () => {
190190
// bugs. If the CONTRIBUTION is ever retired, this list loses its entry in the
191191
// same commit that removes the nav item, and the id moves up to
192192
// `DEAD_SETUP_NAV_IDS` — the two lists are the two halves of one ledger.
193-
const CONDITIONAL_SETUP_NAV_IDS = ['nav_sso_providers'] as const;
193+
// `nav_scim_credentials` (#31123) is the same shape: plugin-auth contributes it
194+
// only while SCIM is wired (`authManager.isScimWired()`).
195+
const CONDITIONAL_SETUP_NAV_IDS = ['nav_sso_providers', 'nav_scim_credentials'] as const;
194196

195197
describe('conditionally-contributed Setup nav ids stay labelled (#6659)', () => {
196198
for (const [locale, data] of Object.entries(LOCALES)) {

‎packages/platform-objects/src/apps/translations/zh-CN.source-hashes.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export const zhCNSourceHashes: Readonly<Record<string, string>> = {
9595
'apps.setup.navigation.nav_sharing_rules.label': 'ceaa66fa5f1df4ad',
9696
'apps.setup.navigation.nav_record_shares.label': 'e261d340ceb25b9a',
9797
'apps.setup.navigation.nav_sso_providers.label': '7b06b6a6abf53ba4',
98+
'apps.setup.navigation.nav_scim_credentials.label': '2d94a32c33ca2957',
9899
'apps.setup.navigation.nav_api_keys.label': '8cda9851248b0490',
99100
'apps.setup.navigation.nav_connect_agent.label': 'a4542422fb5e8a68',
100101
'apps.setup.navigation.nav_approvals_inbox.label': '0a9269b20d0266c9',

0 commit comments

Comments
 (0)