Repository navigation
Commit c898bd8
feat(platform-objects): Create Key offers the key's expiry and names the Authentication policy that bounds it (#25371)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6Jao61ZBid2jvapvCexubDuEuE)_
Fixes #25339
Clause-②: yes
Metadata-protocol: none
## What changed
Setup → API Keys → Create Key asked only for a name, so every key
silently got the default life the Authentication API key policy sets.
Ruling carried: objectstack-ai/hotcrm#2194 record `6354968262`, 「25251 B
hotcrm B」 (adopts T2-U21).
- **`sys_api_key` `create_api_key`**
(`packages/platform-objects/src/identity/sys-api-key.object.ts`) now
declares an optional, field-backed `expires_at` param (the column is
`datetime`). Its help text names the policy: left empty, the key gets
the *Default key lifetime* under Setup → Authentication → API keys (90
days unless changed); a date later than the *Maximum key lifetime* there
is refused. The param has **no `defaultValue` on purpose**: an action
param default is a literal submitted verbatim
(`ActionParamSchema.defaultValue`, and objectui `ActionParamDialog`
seeds it untouched), so any fixed date there would override the
deployment's policy instead of following it.
- **The reveal dialog** shows the expiry the key actually got
(`expires_at`, plain text) beside the masked key. The route already
echoes it in its answer.
- **The route's ceiling refusal**
(`packages/runtime/src/domains/keys.ts`) now names the setting that
decided it: "the Authentication API key policy's Maximum key lifetime is
N days", next to the latest date allowed. Code and status unchanged:
`400 API_KEY_EXPIRY_TOO_LONG`, never a clamp.
- Translation bundles regenerated (`node scripts/check-i18n-bundles.mjs
--write`); the zh-CN / ja-JP / es-ES values are translated by hand,
reusing each locale's existing labels for the column and the two policy
settings.
## Where it landed, and why not plugin-auth
The order expected the refusal in `packages/plugins/plugin-auth`.
Measured on `origin/main` `303aef069`: the mint route is
`@objectstack/runtime` `domains/keys.ts` (the one mint path, `POST
/api/v1/keys`), and it already reads the policy through the auth
service's `resolveApiKeyLifecyclePolicy` and refuses past the ceiling
since #23367 (pinned in
`packages/runtime/src/api-key-lifecycle.pin.test.ts`). plugin-auth is
only the policy's source and is untouched here. The one runtime change
is the refusal text.
## Pins
- New
`packages/platform-objects/src/identity/sys-api-key-create-expiry.test.ts`:
params are exactly `name` (required) and `expires_at` (optional); no
literal default on `expires_at`; its help text names both policy
settings; the param resolves to a `datetime` column; the reveal lists
`key` then `expires_at`.
- `setup-list-empty-states.test.ts`: the reveal pin now expects the
masked key plus the plain-text expiry.
- `api-key-lifecycle.pin.test.ts`: the refusal (status + `error.code`
already asserted) also names the policy setting. The empty-field default
and the "a changed setting changes the answer" pins already exist there
and are unchanged.
## Verification (head `9bfcaebce8`)
- `pnpm --filter @objectstack/platform-objects test`: 89 files,
1322/1322 passed; `typecheck` green.
- `pnpm --filter @objectstack/runtime exec vitest run
src/api-key-lifecycle.pin.test.ts src/domains`: 138 files, 3807/3807
passed; `typecheck` green.
- Ablation (route): restoring `domains/keys.ts` from `303aef069` turns
`a day past the ceiling is refused …` red (1 failed / 13 passed);
restored with `git checkout HEAD --`, `git diff HEAD` empty and the blob
hash equal to HEAD's.
- `dispatch-gates --commands`: 69 derived; 68 run, exit 0; 1
NOT-MEASURED: `pnpm check:dual-build-cjs-loads` exits 3 (PREREQUISITE
NOT MET, it reads every package's `dist/` and this worktree built only
the diff's closure). CI's Lint & Repo Gates runs it on a full build.
`--ran` reconciliation: 0 UNRUN.
## Acceptance notes
- **The dialog states the default; it does not prefill a policy-derived
date.** The card asks for the expiry "defaulted from the policy". An
action param can only carry a literal default, and the policy is
per-deployment configuration, so a prefilled date would need a dynamic
default surface on `ActionParamSchema` (or a renderer that reads the
policy). That is a spec/renderer contract change outside this card's S
size. What ships: the field says what empty means and names the setting,
and the reveal shows the exact date the key got. If a prefilled date is
still wanted, it is a separate decision. Carrier: the engine seat.
- Security floor holds: nothing here lengthens a lifetime. The route
still refuses (never clamps) a date past the maximum, and an empty field
still gets the policy default, never a permanent key.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_012ZpkdTkteFWsMN7p2QMzLq
---
_Generated by [Claude
Code](https://claude.ai/code/session_012ZpkdTkteFWsMN7p2QMzLq)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 97aa34e commit c898bd8
10 files changed
Lines changed: 127 additions & 7 deletions
File tree
- .changeset
- packages
- platform-objects/src
- apps/translations
- identity
- runtime/src
- domains
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1123 | 1123 | | |
1124 | 1124 | | |
1125 | 1125 | | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
| 1129 | + | |
| 1130 | + | |
1126 | 1131 | | |
1127 | 1132 | | |
1128 | 1133 | | |
1129 | 1134 | | |
1130 | 1135 | | |
1131 | | - | |
| 1136 | + | |
| 1137 | + | |
1132 | 1138 | | |
1133 | 1139 | | |
1134 | 1140 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1123 | 1123 | | |
1124 | 1124 | | |
1125 | 1125 | | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
| 1129 | + | |
| 1130 | + | |
1126 | 1131 | | |
1127 | 1132 | | |
1128 | 1133 | | |
1129 | 1134 | | |
1130 | 1135 | | |
1131 | | - | |
| 1136 | + | |
| 1137 | + | |
1132 | 1138 | | |
1133 | 1139 | | |
1134 | 1140 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1123 | 1123 | | |
1124 | 1124 | | |
1125 | 1125 | | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
| 1129 | + | |
| 1130 | + | |
1126 | 1131 | | |
1127 | 1132 | | |
1128 | 1133 | | |
1129 | 1134 | | |
1130 | 1135 | | |
1131 | | - | |
| 1136 | + | |
| 1137 | + | |
1132 | 1138 | | |
1133 | 1139 | | |
1134 | 1140 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1123 | 1123 | | |
1124 | 1124 | | |
1125 | 1125 | | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
| 1129 | + | |
| 1130 | + | |
1126 | 1131 | | |
1127 | 1132 | | |
1128 | 1133 | | |
1129 | 1134 | | |
1130 | 1135 | | |
1131 | | - | |
| 1136 | + | |
| 1137 | + | |
1132 | 1138 | | |
1133 | 1139 | | |
1134 | 1140 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
| 148 | + | |
| 149 | + | |
148 | 150 | | |
149 | 151 | | |
| 152 | + | |
150 | 153 | | |
151 | 154 | | |
152 | 155 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
112 | 112 | | |
113 | 113 | | |
114 | 114 | | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
115 | 125 | | |
116 | 126 | | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
117 | 131 | | |
118 | 132 | | |
119 | 133 | | |
120 | 134 | | |
121 | 135 | | |
122 | 136 | | |
123 | 137 | | |
| 138 | + | |
124 | 139 | | |
125 | 140 | | |
126 | 141 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
112 | 112 | | |
113 | 113 | | |
114 | 114 | | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
115 | 119 | | |
116 | 120 | | |
117 | 121 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
240 | 240 | | |
241 | 241 | | |
242 | 242 | | |
243 | | - | |
244 | | - | |
245 | | - | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
246 | 247 | | |
247 | 248 | | |
248 | 249 | | |
| |||
0 commit comments