Skip to content

Commit f6a429b

Browse files
feat(plugin-auth): no user-context restore of an API key revoked because its owner's membership ended (#31435)
<!-- claude-projects-attribution: {"github_login":"os-zhuang"} --> _Requested by **Sam** · [project thread](https://claude.ai/code/project/chan_01GsBogozeZG5sdpAARrN7HP?thread=cmsg_01GsBogozeZG5sdpAARrN7HPXGFhC5rGwDNXu4au6YC5H8)_ Closes #28387 Clause-②: yes (widening) Metadata-protocol: none ## 维护者速读 - 改了什么:`sys_api_key.revoked_reason` 新增一个取值 `organization_membership_ended`(与会话的 `revoke_reason` 同一个词);带这个原因被撤销的 API 密钥,任何用户身份都不能通过 Restore 恢复,平台管理员也不行,服务端直接 403 并说明原因;被撤销密钥调用时的 `API_KEY_REVOKED` 提示也改为说明该密钥不可恢复。 - 为什么改:cloud 在成员被移出组织时会吊销他在各环境的密钥,但环境管理员可以用 Restore 把它恢复,等于把非成员的凭据重新打开。框架原先无法分辨这种吊销原因。 - 风险与回滚:只影响带这个新原因的密钥;`admin_password_reset` 的规则不变,系统上下文写入不受影响。框架自身不写这个原因,cloud 的写入在后续卡里做。回滚即撤销本 PR。 - 先看:`packages/plugins/plugin-auth/src/api-key-restore-guard.ts`(规则 1,在读取管理员身份之前判定)、`packages/core/src/security/api-key.ts`(提示文字的新分支)、`packages/platform-objects/src/identity/sys-api-key.object.ts`(新选项与说明)。 - 两分钟验证:`pnpm --filter @objectstack/plugin-auth exec vitest run src/api-key-restore-guard.test.ts src/forced-reset-revoke-reason-vocabulary.test.ts`,预期 24 个全绿。 - 需要你做的:无,席位落地(落地前席位做契约复核,其中一项偏离待裁定,见 Acceptance notes)。 ## What changes **The value.** `sys_api_key.revoked_reason` (`packages/platform-objects/src/identity/sys-api-key.object.ts`) gains the option `organization_membership_ended` ("Organization membership ended") beside `admin_password_reset`. It is the word `sys_session.revoke_reason` already carries when a membership removal ends a session (`MEMBERSHIP_ENDED_REVOKE_REASON`, plugin-auth `membership-ended-session.ts`), and the guard imports that same constant. The column stays readonly and off the ADR-0092 D2 whitelist. Its help text now names both reasons and who restores each; the six translated locales carry the new option and the new help, and the en bundle is regenerated (`check-i18n-bundles.mjs --write`). The platform never writes the value: a host that removes someone from the organization writes it in the system context, beside `revoked: true`. **The refusal.** plugin-auth `api-key-restore-guard.ts` gains a rule judged before any standing is read: a user-context un-revoke of a row whose stored `revoked_reason` is `organization_membership_ended` is refused `403 PERMISSION_DENIED` whoever the caller is, on the by-id path and per row on a multi-row update. This is the server door the `restore_api_key` row action reaches (`PATCH /api/v1/data/sys_api_key/{id}`, `{ revoked: false }`); the action's `visible` is display only and is unchanged. The refusal says the owner's membership ended and that a member creates a new key (`POST /api/v1/keys`, or the API Keys page). Unchanged: the `admin_password_reset` rule and its message (byte-identical), the clear on every admitted restore, the reason kept on a re-revoke, and a system-context write is never refused, so a host can still re-admit a key and the reason clears. **The key door's text.** `@objectstack/core` `lifecycleRefusalOf` (`packages/core/src/security/api-key.ts`) answered a revoked key with "Its owner or a platform administrator can restore it" for every reason but the forced reset. For a membership-ended key that is now false, so it gets its own arm: revoked because its owner's membership in the organization ended, cannot be restored, a member creates a new key. Code `API_KEY_REVOKED`, status 403 and every carried field are unchanged. Changeset: `patch` for `@objectstack/platform-objects`, `@objectstack/plugin-auth` and `@objectstack/core` (`Metadata-protocol: none`: the column is a system table's data value, not an authorable metadata key). ## Why cloud#3403 (PR cloud#3423) revokes a removed member's keys in every environment, and named the follow-up: an environment administrator can restore such a key. The framework had no way to tell that revocation apart, because `revoked_reason` accepted only `admin_password_reset`. On a `single` deployment no per-request membership check refuses the key (the `organization_membership_ended` admission refusal in `resolve-authz-context.ts` runs only under a wall-enforcing posture), so the `revoked` flag is the only barrier, and anyone who could clear it reopened a non-member's credential. ## Tests - **plugin-auth `api-key-restore-guard.test.ts`**, new top-level describe on the existing rig (real `ObjectQL` over SQLite, real `SecurityPlugin` with the shipped permission sets, the D2 guard, the guard under test, the real key door). Callers: the key's owner; a platform administrator by grant (`admin_full_access`, promoted by `bootstrapPlatformAdmin`); a platform administrator by the `OS_PLATFORM_OWNER_EMAIL` anchor, with no grant row. Each admin case asserts `hasPlatformAdminStanding` first and ends with a control: the same caller restores a reset-revoked key, so the refusal is the reason's and not a caller who cannot reach the row. Also: the host's system write is stored with the reason; re-revoke and a smuggled `revoked_reason: null` keep it; an admin batch restore including such a key is refused whole; a system un-revoke lands and clears; the door text names no restorer. - **plugin-auth `forced-reset-revoke-reason-vocabulary.test.ts`**: the option set is exactly the two values, and the new one equals the session word that `sys_session.revoke_reason` names. - **core `api-key-lifecycle.test.ts`**: the membership-ended arm of the `API_KEY_REVOKED` text. - **platform-objects `action-state-visibility.test.ts`**: Restore stays offered on a membership-ended row (the server decides). - **dogfood `api-key-data-door-session-required.dogfood.test.ts`**, through the real HTTP door: the `restore_api_key` request read off the REGISTERED schema (method, target, `bodyExtra`), sent by the member's session and by the platform administrator's session, each refused 403 `PERMISSION_DENIED`, row unchanged, key still `API_KEY_REVOKED`; control: the administrator's same request restores a reset-revoked key (200, key works). - Reason-only clear (seat review): a user-context write of `{ revoked_reason: null }` with no `revoked` key is refused 403 `PERMISSION_DENIED` to the owner and both platform administrators (by grant and by the `OS_PLATFORM_OWNER_EMAIL` anchor), on the engine and on the data door, so no restore follows it (head `a3727aaa`). The refuser is the ADR-0092 D2 guard; with the whitelist widened, the readonly strip still refuses it. ## Acceptance notes - **Accepted-deviation candidate, for the contract review:** a platform administrator cannot restore a membership-ended key, unlike `admin_password_reset`, where they can. Reason: a non-member's key re-armed by anyone in a user context reopens the hole the revocation closed, and an environment's administrator is exactly the restorer cloud#3423 named. A system-context write still re-admits a key, so a host that re-adds the member can restore it deliberately. The admin-exemption alternative is measured below (it turns exactly the three admin legs red). - Restore stays visible on such a row. The list views do not project `revoked_reason`, so a `visible` predicate on it would be unreliable; the server refusal says why. - The cloud half is a separate card after a pin round: cloud's `environment-identity-revocation-plugin.ts` writes `revoked_reason: 'organization_membership_ended'` beside `revoked: true` for the `membership_ended` reason. Option membership is never enforced on a readonly select (objectql `record-validator.ts`, the open-vocabulary ruling), so that write would be stored even on today's pin; the refusal needs this release of plugin-auth. ## Verification <details><summary>Ablations (each mutation landed on disk, run, restored to the HEAD blob)</summary> - Rule 1 removed (`if (false && …)`), engine level: 5 of 19 red: owner, admin by grant, admin by email anchor, re-revoke, admin batch. Restored, `git diff HEAD` empty. - Admin exemption added (the `admin_password_reset` pattern), engine level: 3 of 19 red: admin by grant, admin by email anchor, admin batch; the owner legs stay green. - Admin exemption, HTTP level (plugin-auth rebuilt, marker proven in `dist/`, then the dogfood file): the platform administrator's leg red, `expected 200 to be 403`, the row restored and the reason cleared. Restore leg: rebuilt, marker proven absent from `dist/`, `git diff HEAD` empty. - Core text arm removed: 1 of 23 red (the membership-ended text pin). Restored. </details> <details><summary>Local runs</summary> All at head `eaefa983d` (objectstack-ai/objectstack), after the last commit. - Package suites, under the verify lock: `@objectstack/plugin-auth` 240 files / 4257 passed, 10 skipped; `@objectstack/core` 136 / 3147 passed; `@objectstack/platform-objects` 120 / 1751 passed. - `typecheck` (each package's own script, test-typecheck included): `core`, `platform-objects`, `plugin-auth`, `dogfood`, all Done. - Dogfood, one file: `api-key-data-door-session-required.dogfood.test.ts` 10 / 10 passed (showcase closure built first). The full dogfood battery is not owed: no `!` in the title and no `(narrowing)` arm. - `check:i18n` for platform-objects: in sync (20 bundles) after `--write`, which rewrote only the en bundle. - `dispatch-gates --commands`: 75 families derived from the merge base `db5023141`; `--ran` reconciliation with recorded exit codes: 74 run with exit 0, 1 NOT MEASURED: `check:dual-build-cjs-loads`, PREREQUISITE NOT MET (exit 3; it reads every package's `dist/`, and only the plugin-auth, dogfood and cli closures were built here). CI's `Lint & Repo Gates` runs it on a full build. - `check-plugin-teardown-shape --self-test` first exited 3 because its positive-control fixture commit was outside this shallow clone; after fetching that one commit it passed (48 cases). - Not run locally, owned by CI: the repo-wide `pnpm lint` and `test:repo`. </details> ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes (CI-owned run). - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: not touched. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- _Generated by [Claude Code](https://claude.ai/code/session_01QUs3BrzAC1gmXvDdmDaVk6)_ <!-- signed: repo:cloud#3 · os-zhuang · session_01QUs3BvRcDHXqRU4TSVE4u6 --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent eb92a48 commit f6a429b

17 files changed

Lines changed: 438 additions & 61 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
'@objectstack/plugin-auth': patch
4+
'@objectstack/core': patch
5+
---
6+
7+
feat(plugin-auth,platform-objects,core): `sys_api_key.revoked_reason` accepts `organization_membership_ended`, and no user-context Restore lifts it
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: none
11+
12+
- **`@objectstack/platform-objects`: `sys_api_key.revoked_reason` gains the option `organization_membership_ended`** ("Organization membership ended"), beside `admin_password_reset`. It is the word `sys_session.revoke_reason` already carries when a membership removal ends a session, so one grep finds every place the platform acts on a membership ending, across sessions and keys. The platform never writes it: a host that removes someone from the organization and revokes their keys writes it in the system context, beside `revoked: true`. The column stays readonly and off the ADR-0092 D2 whitelist, so no data-API write sets or clears it. The field's help text names both reasons and who can restore each; the six translated locales carry the new option and the new help.
13+
- **`@objectstack/plugin-auth`: the restore guard refuses every user-context restore of a key carrying that reason**, a platform administrator's included (by the `admin_full_access` grant or by the `OS_PLATFORM_OWNER_EMAIL` anchor), on the by-id path and per row on a multi-row update: `403 PERMISSION_DENIED`, nothing written, and a message that says the owner's membership ended and that a member creates a new key (`POST /api/v1/keys`, or the API Keys page). This is the `restore_api_key` row action's own door (`PATCH /api/v1/data/sys_api_key/{id}` with `{ revoked: false }`). Re-arming the key would hand a non-member's credential back its access, whoever clicks Restore, and on a `single` deployment no per-request membership check refuses the key, so `revoked` is the only barrier. A system-context un-revoke still lands and clears the reason, as for every reason. `admin_password_reset` keeps its rule unchanged: a platform administrator restores it, its owner does not.
14+
- **`@objectstack/core`: the key door's `403 API_KEY_REVOKED` text for such a key** says it was revoked because its owner's membership in the organization ended and cannot be restored, and names the new-key road. It used to offer "its owner or a platform administrator can restore it", a restore the guard now refuses. Code, status and every carried field are unchanged.

‎packages/core/src/security/api-key-lifecycle.test.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,4 +351,21 @@ describe('[#23944] the revoked refusal agrees with the declared restore action',
351351
expect(resetMessage).toContain('POST /api/v1/keys');
352352
expect(plainMessage).toMatch(/Its owner or a platform administrator can restore it/);
353353
});
354+
355+
it('a key revoked because its owner\'s membership ended names no restorer, only the new-key road', async () => {
356+
// `revoked_reason: organization_membership_ended` is written by the host
357+
// that removed the owner, and plugin-auth's restore door refuses every
358+
// user-context restore on that reason, a platform administrator's
359+
// included. The text follows the door: it offers no restore anyone could
360+
// make. Code, status and every carried field stay those of any revoked key.
361+
const ended = await resolveApiKeyAdmission(
362+
makeQl({ revoked: true, revoked_reason: 'organization_membership_ended' }), headers(), NOW, undefined, undefined, policy(),
363+
);
364+
365+
expect(ended.outcome === 'lifecycle' && ended.refusal).toMatchObject({ code: 'API_KEY_REVOKED', keyId: KEY_ID, userId: OWNER });
366+
const endedMessage = ended.outcome === 'lifecycle' ? ended.refusal.message : '';
367+
expect(endedMessage).toMatch(/membership in the organization ended, and it cannot be restored/);
368+
expect(endedMessage).not.toMatch(/can restore it/);
369+
expect(endedMessage).toContain('POST /api/v1/keys');
370+
});
354371
});

‎packages/core/src/security/api-key.ts‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -315,17 +315,24 @@ function lifecycleRefusalOf(
315315
// `revoked_reason`: a key an administrator's forced password reset
316316
// revoked (`admin_password_reset`) is restored by a platform
317317
// administrator only (plugin-auth `api-key-restore-guard.ts` refuses its
318-
// owner); any other revoked key by its owner or a platform administrator.
319-
// The text names exactly those restorers and never claims the key is
320-
// gone for good. Rotation stays the other road, for a key that should
321-
// not come back.
318+
// owner); a key revoked because its owner's membership in the
319+
// organization ended (`organization_membership_ended`, written by the
320+
// host that removed them) by no one, the same guard refusing every
321+
// user-context restore of it; any other revoked key by its owner or a
322+
// platform administrator. The text names exactly those restorers, and
323+
// says the key cannot come back only where no restorer exists. Rotation
324+
// stays the other road, for a key that should not come back.
322325
message: row.revoked_reason === 'admin_password_reset'
323326
? `${label} was revoked by an administrator's forced password reset. Only a platform administrator can `
324327
+ 'restore it (the Restore API Key action on the API Keys list); otherwise create a new key '
325328
+ '(POST /api/v1/keys, or the API Keys page) and replace this one wherever it is configured.'
326-
: `${label} was revoked. Its owner or a platform administrator can restore it (the Restore API Key action `
327-
+ 'on the API Keys list); otherwise create a new key (POST /api/v1/keys, or the API Keys page) and replace '
328-
+ 'this one wherever it is configured.',
329+
: row.revoked_reason === 'organization_membership_ended'
330+
? `${label} was revoked because its owner's membership in the organization ended, and it cannot be `
331+
+ 'restored. A member of the organization creates a new key (POST /api/v1/keys, or the API Keys page) '
332+
+ 'and replaces this one wherever it is configured.'
333+
: `${label} was revoked. Its owner or a platform administrator can restore it (the Restore API Key action `
334+
+ 'on the API Keys list); otherwise create a new key (POST /api/v1/keys, or the API Keys page) and replace '
335+
+ 'this one wherever it is configured.',
329336
...ids,
330337
};
331338
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,9 +1160,10 @@ export const arObjects: NonNullable<TranslationData['objects']> = {
11601160
},
11611161
revoked_reason: {
11621162
label: "سبب الإلغاء",
1163-
help: "يُعيَّن عندما تُبطل إعادة تعيين كلمة المرور القسرية من قِبل المسؤول هذا المفتاح. لا يستطيع استعادة مثل هذا المفتاح إلا مسؤول المنصة؛ وينشئ مالكه مفتاحًا جديدًا. يُمسح عند استعادة المفتاح. فارغ للمفتاح المُبطَل من قائمة مفاتيح API.",
1163+
help: "سبب إبطال المفتاح، حين يحدد من يستطيع استعادته. إعادة تعيين كلمة المرور من قِبل المسؤول: لا يستطيع استعادة المفتاح إلا مسؤول المنصة. انتهاء العضوية في المؤسسة: غادر المالك المؤسسة، ولا يستطيع أحد استعادة المفتاح؛ وينشئ عضوٌ مفتاحًا جديدًا. يُمسح عند استعادة المفتاح. فارغ للمفتاح المُبطَل من قائمة مفاتيح API.",
11641164
options: {
1165-
admin_password_reset: "إعادة تعيين كلمة المرور من قِبل المسؤول"
1165+
admin_password_reset: "إعادة تعيين كلمة المرور من قِبل المسؤول",
1166+
organization_membership_ended: "انتهاء العضوية في المؤسسة"
11661167
}
11671168
},
11681169
key: {

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,9 +1160,10 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
11601160
},
11611161
revoked_reason: {
11621162
label: "Revoke Reason",
1163-
help: "Set when an administrator's forced password reset revoked this key. Only a platform administrator can restore such a key; its owner creates a new one. Cleared when the key is restored. Empty for a key revoked from the API Keys list.",
1163+
help: "Why the key was revoked, where that decides who can restore it. Administrator password reset: only a platform administrator can restore the key. Organization membership ended: the owner left the organization, and no one can restore the key; a member creates a new one. Cleared when the key is restored. Empty for a key revoked from the API Keys list.",
11641164
options: {
1165-
admin_password_reset: "Administrator password reset"
1165+
admin_password_reset: "Administrator password reset",
1166+
organization_membership_ended: "Organization membership ended"
11661167
}
11671168
},
11681169
key: {

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,9 +1160,10 @@ export const esESObjects: NonNullable<TranslationData['objects']> = {
11601160
},
11611161
revoked_reason: {
11621162
label: "Motivo de la revocación",
1163-
help: "Se registra cuando el restablecimiento forzado de la contraseña por parte de un administrador revocó esta clave. Solo un administrador de la plataforma puede restaurar una clave así; su propietario crea una nueva. Se borra cuando la clave se restaura. Vacío para una clave revocada desde la lista de claves de API.",
1163+
help: "Por qué se revocó la clave, cuando eso decide quién puede restaurarla. Restablecimiento forzado de la contraseña por un administrador: solo un administrador de la plataforma puede restaurar la clave. Fin de la pertenencia a la organización: el propietario dejó la organización y nadie puede restaurar la clave; un miembro crea una nueva. Se borra cuando la clave se restaura. Vacío para una clave revocada desde la lista de claves de API.",
11641164
options: {
1165-
admin_password_reset: "Restablecimiento forzado de la contraseña por un administrador"
1165+
admin_password_reset: "Restablecimiento forzado de la contraseña por un administrador",
1166+
organization_membership_ended: "Fin de la pertenencia a la organización"
11661167
}
11671168
},
11681169
key: {

‎packages/platform-objects/src/apps/translations/fr.objects.generated.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,9 +1160,10 @@ export const frObjects: NonNullable<TranslationData['objects']> = {
11601160
},
11611161
revoked_reason: {
11621162
label: "Motif de la révocation",
1163-
help: "Renseigné lorsque la réinitialisation forcée du mot de passe par un administrateur a révoqué cette clé. Seul un administrateur de la plateforme peut restaurer une telle clé ; son propriétaire en crée une nouvelle. Effacé lorsque la clé est restaurée. Vide pour une clé révoquée depuis la liste Clés API.",
1163+
help: "Motif de la révocation de la clé, lorsqu'il détermine qui peut la restaurer. Réinitialisation du mot de passe par un administrateur : seul un administrateur de la plateforme peut restaurer la clé. Fin de l'appartenance à l'organisation : le propriétaire a quitté l'organisation et personne ne peut restaurer la clé ; un membre en crée une nouvelle. Effacé lorsque la clé est restaurée. Vide pour une clé révoquée depuis la liste Clés API.",
11641164
options: {
1165-
admin_password_reset: "Réinitialisation du mot de passe par un administrateur"
1165+
admin_password_reset: "Réinitialisation du mot de passe par un administrateur",
1166+
organization_membership_ended: "Fin de l'appartenance à l'organisation"
11661167
}
11671168
},
11681169
key: {

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,9 +1160,10 @@ export const jaJPObjects: NonNullable<TranslationData['objects']> = {
11601160
},
11611161
revoked_reason: {
11621162
label: "失効理由",
1163-
help: "管理者によるパスワードの強制リセットでこのキーが失効したときに記録されます。このようなキーを復元できるのはプラットフォーム管理者のみで、所有者は新しいキーを作成します。キーを復元するとクリアされます。API キー一覧から失効させたキーでは空です。",
1163+
help: "キーが失効した理由で、誰が復元できるかを左右する場合に記録されます。管理者によるパスワードの強制リセット:キーを復元できるのはプラットフォーム管理者のみです。組織メンバーシップの終了:所有者が組織を離れたため、誰もキーを復元できません。組織のメンバーが新しいキーを作成します。キーを復元するとクリアされます。API キー一覧から失効させたキーでは空です。",
11641164
options: {
1165-
admin_password_reset: "管理者によるパスワードの強制リセット"
1165+
admin_password_reset: "管理者によるパスワードの強制リセット",
1166+
organization_membership_ended: "組織メンバーシップの終了"
11661167
}
11671168
},
11681169
key: {

‎packages/platform-objects/src/apps/translations/pt-BR.objects.generated.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,9 +1160,10 @@ export const ptBRObjects: NonNullable<TranslationData['objects']> = {
11601160
},
11611161
revoked_reason: {
11621162
label: "Motivo da revogação",
1163-
help: "Definido quando a redefinição forçada de senha por um administrador revogou esta chave. Somente um administrador da plataforma pode restaurar essa chave; o proprietário cria uma nova. Limpo quando a chave é restaurada. Vazio para uma chave revogada na lista de Chaves de API.",
1163+
help: "Por que a chave foi revogada, quando isso decide quem pode restaurá-la. Redefinição de senha pelo administrador: somente um administrador da plataforma pode restaurar a chave. Fim da associação à organização: o proprietário saiu da organização e ninguém pode restaurar a chave; um membro cria uma nova. Limpo quando a chave é restaurada. Vazio para uma chave revogada na lista de Chaves de API.",
11641164
options: {
1165-
admin_password_reset: "Redefinição de senha pelo administrador"
1165+
admin_password_reset: "Redefinição de senha pelo administrador",
1166+
organization_membership_ended: "Fim da associação à organização"
11661167
}
11671168
},
11681169
key: {

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,9 +1160,10 @@ export const zhCNObjects: NonNullable<TranslationData['objects']> = {
11601160
},
11611161
revoked_reason: {
11621162
label: "撤销原因",
1163-
help: "管理员强制重置密码时撤销了该密钥,则记录此原因。此类密钥只能由平台管理员恢复,其所有者应创建新的密钥。密钥恢复后清除。从 API 密钥列表撤销的密钥此项为空。",
1163+
help: "密钥被撤销的原因,仅在它决定谁能恢复密钥时记录。管理员强制重置密码:只有平台管理员能恢复该密钥。组织成员资格已结束:所有者已离开组织,任何人都不能恢复该密钥,由组织成员创建新的密钥。密钥恢复后清除。从 API 密钥列表撤销的密钥此项为空。",
11641164
options: {
1165-
admin_password_reset: "管理员强制重置密码"
1165+
admin_password_reset: "管理员强制重置密码",
1166+
organization_membership_ended: "组织成员资格已结束"
11661167
}
11671168
},
11681169
key: {

0 commit comments

Comments
 (0)