|
| 1 | +--- |
| 2 | +"@objectstack/platform-objects": patch |
| 3 | +--- |
| 4 | + |
| 5 | +fix(platform-objects): drop the dead Setup › Advanced › Signing Keys (JWKS) nav entry (#7544) |
| 6 | + |
| 7 | +`Setup › Advanced › Signing Keys (JWKS)` could never load, for **any** persona. |
| 8 | +`sys_jwks` declares `enable.apiEnabled: false` / `apiMethods: []`, so the list |
| 9 | +request answers `OBJECT_API_DISABLED` (404) — and the console masked that as a |
| 10 | +generic "No identity records" empty state, so the surface read as *"you have no |
| 11 | +signing keys"* rather than *"this page cannot work"*. |
| 12 | + |
| 13 | +**Why the gate it carried could not help.** The entry was contributed with |
| 14 | +`requiredPermissions: ['manage_platform_settings']`, and an in-code comment |
| 15 | +claimed a non-admin's list "403s server-side" — which reads as though an admin |
| 16 | +could list the keys. None could. `apiAccessDenialFromEnable` (`rest-server.ts`) |
| 17 | +is a **pure function of the object's `enable` block**: it takes no user, no |
| 18 | +permissions and no context, so the 404 is identical for every persona, platform |
| 19 | +admin included. A permission gate on the entry and an API-disabled object are |
| 20 | +independent conditions, and no combination of the first prunes the second. |
| 21 | + |
| 22 | +**The repair is the entry, not the object.** `sys_jwks` rows are the |
| 23 | +environment's JWT signing keys (`private_key` — private key material); opening a |
| 24 | +read path onto them over the generic data API would be a credential disclosure. |
| 25 | +`enable` is unchanged, and a test now pins that it stays `apiEnabled: false` / |
| 26 | +`apiMethods: []` (fails CLOSED since #3391) and `access: { default: 'private' }` |
| 27 | +(ADR-0066 ④). better-auth continues to read the keys through its adapter under a |
| 28 | +system context, so token signing and verification are unaffected. |
| 29 | + |
| 30 | +This matches how the same class is already handled two lines below in |
| 31 | +`setup-nav.contributions.ts`: `sys_verification` and `sys_device_code` omit |
| 32 | +`list` and therefore get no browse entry. `sys_jwks` was the only one of the |
| 33 | +repo's seven API-disabled objects that still had a nav entry — the six |
| 34 | +`sys_oauth_*` token/consent stores never had one. |
| 35 | + |
| 36 | +Also landed with the removal: |
| 37 | + |
| 38 | +- The four `apps.setup.navigation.nav_jwks` labels move into the |
| 39 | + `DEAD_SETUP_NAV_IDS` tombstone (`setup-nav-dead-key-tombstone.test.ts`), which |
| 40 | + refuses a label with no declaring nav item and states the order for re-adding |
| 41 | + one. The `sys_jwks` **object** labels in the generated bundles are untouched — |
| 42 | + the object still exists. |
| 43 | +- A new invariant in `platform-objects.test.ts`: every contributed |
| 44 | + `type: 'object'` Setup entry must target an object that can actually serve a |
| 45 | + `list`, judged through the same single derivation source the REST gate uses |
| 46 | + (`resolveEffectiveApiMethods` / `isApiOperationAllowed`, #3391). It asserts the |
| 47 | + control too — `nav_api_keys` → `sys_api_key` still lists, so a fix that pruned |
| 48 | + both would fail. |
| 49 | + |
| 50 | +**Not addressed here** (reported on #7544 instead): nav gating has no declaration |
| 51 | +that can express "prune when the destination cannot serve". `filterAppForUser` |
| 52 | +gates `requiredPermissions` and `requiresService` server-side and deliberately |
| 53 | +leaves `requiresObject` to the client, and nothing anywhere consults |
| 54 | +`enable.apiEnabled` — so re-pointing this entry at a `requiresObject` gate would |
| 55 | +not have pruned it either. Closing that gap is a contract-face change and belongs |
| 56 | +in its own card. |
0 commit comments