capsite/webapp: the cookie's store is the owner's, apart from the instance
Letting the instance id rotate the cookie let its holder (the cache, or
whoever learns the id) swap in another account's session, which the
owner's logout would not stop. Now mint makes two things: the instance,
and a header store (lib's MapStoreDo, shaped ("cookie", { keyShape:
"cookie", valueShape: M.string() })). The instance id is authority to
use the cookie; the store id, for the account owner, to rotate (PUT
/store/<storeId>/cookie, or WebAppFactory.store) or revoke (DELETE).
Instances read the store's read-only view at every send, via init's new
headerStore.
This replaces the in-instance cookie store and its migration: instances
minted earlier keep their cookie in init.headers, untouched, and simply
cannot rotate. The MapStoreDo class arrives as wrangler migration v2
(additive).
Generated-by: Claude Opus 5.5
This commit is contained in:
1 parent
357e4561c4
commit
3f711a5f4b
5 files changed
+219
-151
No files matched your search
+26
-12
@@ -12,31 +12,45 @@ grows past a form or two.
|
||||
|
||||
- `GET /` — the mint form. Prefill via query string: `?url=…&headers=<urlencoded JSON>&msPerCall=…&title=…`; secret header keys (cookie, authorization, proxy-authorization) render **blank** — paste them at mint time; never echoed back. A dedicated `cookie` field is sugar for the `cookie` header (if the JSON also has one, they must agree).
|
||||
- `GET /form?render=1&…` — the same form for a proxying worker (diigo-cache's `/_connect`): the action is left as an `ACTION` placeholder for the proxy to point at its own relay.
|
||||
- `POST /` — mint. Form-encoded (that page) or JSON `{url, headers, msPerCall}` (service-style). Responds `{id}` (and `x-webapp-id`). The id is *not* logged: possession is authority.
|
||||
- `POST /` — mint. Form-encoded (that page) or JSON `{url, headers, msPerCall}` (service-style). Responds `{id, storeId}` (and `x-webapp-id`, `x-webapp-store-id`). Neither id is logged: possession is authority.
|
||||
- `POST /at/<id>` — drive an instance by capability: JSON `{path?, query?, method?, body?}` → the fluent chain → `{status, text}` (or `{error}`; messages carry no header/param values).
|
||||
- `POST /at/<id>/cookie` — rotate the instance's cookie in place: JSON `{cookie}` → `{ok: true}`. The id that drives an instance can also replace its cookie; the account owner revokes by logging out.
|
||||
- `PUT /store/<storeId>/<key>` — set a header-store entry (JSON value body), e.g. `PUT /store/<storeId>/cookie` with `"session=…"` rotates the cookie; `DELETE` removes it. → `{ok: true}` (or `{error}`; messages carry no keys or values).
|
||||
|
||||
## RPC surface (same-account workers; binding `entrypoint: "WebAppFactory"`)
|
||||
|
||||
- `fetch(request)` — the HTTP surface above, over the binding
|
||||
- `newWebApp({url, headers?, msPerCall?})` → `{id, app}` — the fluent stub;
|
||||
- `newWebApp({url, headers?, msPerCall?})` → `{id, storeId, app}` — the fluent stub;
|
||||
promise pipelining makes a whole `pathjoin().query().get()` chain a
|
||||
single round trip
|
||||
- `at(id)` — the fluent stub for a previously-minted instance
|
||||
- `cookies(id)` — the instance's cookie store (a MapStore from
|
||||
[the capsite lib](../lib/src/store.ts) with key shape `"cookie"`):
|
||||
`set("cookie", …)` rotates it under every handle already out
|
||||
- `store(storeId)` — a header store, with full authority:
|
||||
`set("cookie", …)` rotates the cookie under every handle already out
|
||||
|
||||
## The cookie store
|
||||
## The header store: two ids, two authorities
|
||||
|
||||
An instance is fixed at creation except for its cookie, which lives in a
|
||||
store rather than in `init`; every send reads it. Instances minted before
|
||||
the store held their cookie in `init.headers`; the first `handle()` moves
|
||||
it over in one transaction.
|
||||
Mint makes two things: the instance, and a header store (a `MapStoreDo`
|
||||
from [the capsite lib](../lib/src/store-do.ts), shaped
|
||||
`("cookie", { keyShape: "cookie", valueShape: M.string() })`). A `cookie`
|
||||
header goes into the store, not the instance's `init`; the instance reads
|
||||
the store's read-only view at every send.
|
||||
|
||||
- the **instance id** is authority to *use* the cookie: it goes to the
|
||||
cache's wiring.
|
||||
- the **store id** is authority to *choose* it: it stays with the account
|
||||
owner, who can rotate it (log in again, `PUT`) or revoke it (`DELETE`,
|
||||
or log out).
|
||||
|
||||
Kept apart because whoever can choose the cookie can swap in another
|
||||
account's session: if the cache's id could rotate, a leaked instance id
|
||||
would let Mallory fill the cache (and its public page) with her own
|
||||
bookmarks, and the owner's logout would not stop it.
|
||||
|
||||
Instances minted before header stores keep their cookie in
|
||||
`init.headers`, unchanged; they work as before and cannot rotate.
|
||||
|
||||
## Run
|
||||
|
||||
pnpm test # cookie store + rotation (vitest, workerd pool)
|
||||
pnpm test # header store + rotation (vitest, workerd pool)
|
||||
npx wrangler dev # → its own ready line (usually :8787 with the
|
||||
# two-config diigo-cache run; standalone, :8788)
|
||||
|
||||
|
||||
Reference in new issue
Block a user