Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/23217-s4-storage-door.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@objectstack/service-storage': patch
---

The storage download door resolves its caller through `@objectstack/core`'s `resolveRequestExecutionContext`, the one request-to-`ExecutionContext` composition

Clause-②: no

- The download authorizer that `StorageServicePlugin` and `mountStorageRoutes` bind for parent-governed files resolves the caller by calling `resolveRequestExecutionContext` with this door's differences as named parameters: `anonymous: 'fail-closed'`, `authGate: 'resolved'`, `credentials: 'api-key-and-session'`, `sessionApi: 'optional'`, `session: 'request'`, its own way to reach the `tenancy` service, `localization: 'withhold'`, no request locale, `accessToken: 'carry'`, and no OAuth provenance or admission channel. The auth-service lookup, the header normalisation, the tenancy wiring, the verdict reads and the fail-closed `catch` stay at the door. The family's ADR-0069 gate reader, which runs before every storage route, is unchanged.
- The answers are the ones the door gave before: an anonymous caller, an unrecognised cookie, a failed session read with no gate active and a refused API key get `401 AUTH_REQUIRED`; a session or an API key owner the authentication policy holds gets `403` with its code and message from the family's gate; an unreadable permission store or tenancy service gets `503 SERVICE_UNAVAILABLE`; and an API key its own lifecycle refuses gets its `403` and code. A one-off probe of 47 fault wirings through the download route read the same answer on both sides of the change.
- The context the door hands to the owner-record read and to a file-access delegate is now the assembled `ExecutionContext` envelope rather than the resolver's raw output: it also states `isSystem: false` and `principalKind: 'human'`, and no longer carries the internal `credential` marker, which no reader of that context consumes.
- Nothing to do for a consumer: no export, route, configuration key or response shape changed.
9 changes: 4 additions & 5 deletions packages/core/src/security/authz-store-unavailable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,13 @@ const TRANSPORT_LEDGER: Record<string, 'guarded' | 'propagates'> = {
// The one request-to-ExecutionContext composition (ruling record 6227928430,
// letter A). It catches nothing around the resolver: every door that calls
// it — the runtime dispatcher's `resolve-execution-context.ts` first, REST's
// `rest-server.ts` second and the settings door's
// `settings-service-plugin.ts` third, each of the last two through a
// fail-closed `catch` that re-raises the outage — receives the outage and
// answers it at its own net.
// `rest-server.ts` second, the settings door's `settings-service-plugin.ts`
// third and the storage download door's `storage-service-plugin.ts` fourth,
// each of the last three through a fail-closed `catch` that re-raises the
// outage — receives the outage and answers it at its own net.
'packages/core/src/security/request-execution-context.ts': 'propagates',
'packages/mcp/src/plugin.ts': 'propagates',
'packages/services/service-datasource/src/admin-routes.ts': 'guarded',
'packages/services/service-storage/src/storage-service-plugin.ts': 'guarded',
'packages/plugins/plugin-sharing/src/sharing-plugin.ts': 'guarded',
'packages/plugins/plugin-hono-server/src/current-user-endpoints.ts': 'guarded',
'packages/cloud-connection/src/marketplace-install-local-plugin.ts': 'guarded',
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/security/request-execution-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@
* | parameter | values, and the door that takes each |
* |:--|:--|
* | `anonymous` | `'guest'`: the runtime / MCP dispatcher. `'fail-closed'`: REST, settings, storage, the `/auth/me` endpoints |
* | `authGate` | `'withhold'`: the dispatcher (its `enforceAuthGate` re-reads the session; a key's answer leaves through `onAdmission`) and the `/auth/me` endpoints (allow-listed for a gated user). `'resolved'`: settings. `'reread-when-active'`: REST and the storage family door |
* | `authGate` | `'withhold'`: the dispatcher (its `enforceAuthGate` re-reads the session; a key's answer leaves through `onAdmission`) and the `/auth/me` endpoints (allow-listed for a gated user). `'resolved'`: settings and the storage download door. `'reread-when-active'`: REST. The storage family's ADR-0069 gate reader, which runs before every storage route, stays hand-written beside this function (#24366) |
* | `credentials` | `'session-only'`: the `/auth/me` endpoints, which never opened an API-key door. `'api-key-and-session'`: every other door |
* | `sessionApi` | `'required'`: REST and the `/auth/me` endpoints, which authenticate nobody without a session API. `'optional'`: the dispatcher, settings and storage, which still admit an API key |
* | `session` | `'request'`: every door. `{ established }` / `'none'`: the dispatcher's `/mcp` face, for a verified / an unverifiable OAuth access token |
* | `tenancy` | the door's own way to reach the `tenancy` service (#16013: the resolution stays at the door), or `undefined` where the door reads no posture (the `/auth/me` endpoints) |
* | `localization` | the door's settings-service lookup (the dispatcher, REST), or `'withhold'` (the `/auth/me` endpoints read the cascade per endpoint) |
* | `requestLocale` | the door's own parse: `Accept-Language` (the dispatcher), the header or `?locale` (REST), none (the `/auth/me` endpoints) |
* | `accessToken` | `'carry'`: the dispatcher. `'withhold'`: REST and the `/auth/me` endpoints |
* | `accessToken` | `'carry'`: the dispatcher and the storage download door. `'withhold'`: REST and the `/auth/me` endpoints |
* | `oauth` | the dispatcher's `/mcp` face, which alone speaks the OAuth scope vocabulary; `undefined` elsewhere |
* | `onAdmission` | the dispatcher, whose gate needs the credential the envelope does not carry; `undefined` elsewhere |
*
Expand Down
1 change: 1 addition & 0 deletions packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@objectstack/service-messaging": "workspace:*",
"@objectstack/service-package": "workspace:*",
"@objectstack/service-settings": "workspace:*",
"@objectstack/service-storage": "workspace:*",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
},
Expand Down
Loading
Loading