@willyim/idp 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -3
- package/dist/src/client.d.ts +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -1
- package/dist/src/react-router/index.d.ts +3 -0
- package/dist/src/react-router/index.d.ts.map +1 -1
- package/dist/src/react-router/index.js +24 -1
- package/dist/src/schemas/index.d.ts +63 -1
- package/dist/src/schemas/index.d.ts.map +1 -1
- package/dist/src/schemas/index.js +65 -3
- package/dist/src/schemas/operations.d.ts +28 -0
- package/dist/src/schemas/operations.d.ts.map +1 -1
- package/dist/src/schemas/operations.js +2 -0
- package/dist/src/session.d.ts +20 -0
- package/dist/src/session.d.ts.map +1 -1
- package/dist/src/session.js +13 -0
- package/openapi/idp-api.json +168 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,7 +82,8 @@ return requestHandler(request, {
|
|
|
82
82
|
})
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
**`app/routes/auth.$.tsx`** — serves `/auth/login`, `/auth/callback`, `/auth/logout
|
|
85
|
+
**`app/routes/auth.$.tsx`** — serves `/auth/login`, `/auth/callback`, `/auth/logout`
|
|
86
|
+
and `/auth/me`:
|
|
86
87
|
|
|
87
88
|
```ts
|
|
88
89
|
import { createAuthRoute } from "@willyim/idp/react-router"
|
|
@@ -110,6 +111,17 @@ export async function loader(args: Route.LoaderArgs) {
|
|
|
110
111
|
}
|
|
111
112
|
```
|
|
112
113
|
|
|
114
|
+
For code that can't reach a loader — a client-only route, a widget mounted
|
|
115
|
+
outside the router, a fetch from a worker — the same route answers `/auth/me`:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const { user } = await fetch("/auth/me").then((r) => r.json())
|
|
119
|
+
// 200 { user: PublicSession } · 401 { user: null }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
A server-rendered page should keep using `requireSession`: `/auth/me` is the
|
|
123
|
+
same data, one network hop later.
|
|
124
|
+
|
|
113
125
|
## Env
|
|
114
126
|
|
|
115
127
|
| Var | Purpose |
|
|
@@ -203,7 +215,21 @@ type Session = {
|
|
|
203
215
|
}
|
|
204
216
|
```
|
|
205
217
|
|
|
206
|
-
`can()` matches exactly, and honours `resource:*` and `*` grants
|
|
218
|
+
`can()` matches exactly, and honours `resource:*` and `*` grants — so a grant of
|
|
219
|
+
`kirby:*` covers `kirby:read` and a per-instance `kirby:thread:t_7f3a` alike
|
|
220
|
+
(see [Resource-scoped grants](#resource-scoped-grants)).
|
|
221
|
+
|
|
222
|
+
`image` is effectively always set against the willy.im IdP: it renders a
|
|
223
|
+
deterministic [blobatar](https://blobatar.dev) for anyone who never uploaded a
|
|
224
|
+
picture, so `<img src={session.image} />` needs no fallback of its own. The type
|
|
225
|
+
stays nullable for other issuers. Show `session.name || session.email` — a name
|
|
226
|
+
is a person, an email is a login.
|
|
227
|
+
|
|
228
|
+
### `publicSession(session)`
|
|
229
|
+
|
|
230
|
+
`Session` as JSON, without the methods and without the session id — the shape
|
|
231
|
+
that is safe to hand a browser. This is what `/auth/me` (below) returns, and
|
|
232
|
+
what your own `/me` should.
|
|
207
233
|
|
|
208
234
|
### `createIdpClient(options)` — the OIDC relying party on its own
|
|
209
235
|
|
|
@@ -297,7 +323,9 @@ const keys = createUserKeys({
|
|
|
297
323
|
const minted = await keys.create({
|
|
298
324
|
userId: session.userId,
|
|
299
325
|
name: "cli",
|
|
300
|
-
|
|
326
|
+
// A declared permission, or `<type>:<id>` for an instance the app lists —
|
|
327
|
+
// anything else is a 422 (`unknown_scopes` / `unknown_resource`).
|
|
328
|
+
scopes: ["analytics:read", "kirby:thread:t_7f3a"],
|
|
301
329
|
workspaceId: session.workspaceId,
|
|
302
330
|
})
|
|
303
331
|
|
|
@@ -323,6 +351,61 @@ ingest token, say — identifies a site rather than a user, cannot be kept secre
|
|
|
323
351
|
and must not pay a round trip per hit. Keep those in the app's own table and
|
|
324
352
|
gate them on `Origin` plus rate limiting.
|
|
325
353
|
|
|
354
|
+
## Resource-scoped grants
|
|
355
|
+
|
|
356
|
+
A permission names a surface (`kirby:read`). When the honest grant is one thing
|
|
357
|
+
inside it — one conversation, one document, one workspace — the app declares a
|
|
358
|
+
resource **type** in its catalog and the grant becomes `<type>:<id>`:
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
await api.request("put", "/api/v1/apps/{app}/permissions", {
|
|
362
|
+
params: { app: "bender" },
|
|
363
|
+
body: {
|
|
364
|
+
permissions: ["kirby:read", "kirby:write"],
|
|
365
|
+
resourceTypes: [
|
|
366
|
+
{
|
|
367
|
+
type: "kirby:thread", // grants compose as kirby:thread:<id>
|
|
368
|
+
label: "WhatsApp conversation", // what the console calls one
|
|
369
|
+
list: "https://bender.romo.fyi/idp/resources/kirby-thread",
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
},
|
|
373
|
+
})
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
The IdP never stores the instances. When someone picks one in the console, or a
|
|
377
|
+
key is minted for one, it GETs the type's `list` URL and reads
|
|
378
|
+
`{ resources: [{ id, label, description? }] }` (`ResourceListSchema` in
|
|
379
|
+
`@willyim/idp/schemas`). The call carries a one-minute JWT the IdP signs with
|
|
380
|
+
its OIDC key — `aud` is the list URL, the permissions claim is
|
|
381
|
+
`idp:resources:list` — which the app verifies exactly as it verifies an MCP
|
|
382
|
+
access token:
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
import { createResourceServer, RESOURCE_LIST_PERMISSION } from "@willyim/idp"
|
|
386
|
+
|
|
387
|
+
const listing = createResourceServer({
|
|
388
|
+
issuer: "https://idp.willy.im/auth",
|
|
389
|
+
resource: "https://bender.romo.fyi/idp/resources/kirby-thread",
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
const auth = await listing.authenticate(request, { permissions: [RESOURCE_LIST_PERMISSION] })
|
|
393
|
+
if (!auth.ok) return new Response(auth.error, { status: auth.status })
|
|
394
|
+
return Response.json({ resources: threads.map((t) => ({ id: t.handle, label: t.title })) })
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
`id` is the stable, opaque handle the grant will name — never an alias — and
|
|
398
|
+
is one segment (no colon, no `*`, no whitespace). On the consuming side nothing
|
|
399
|
+
changes: the composed string arrives in `permissions` / `scopes`, and
|
|
400
|
+
`grants(permissions, "kirby:thread:t_7f3a")` is true for that grant, for
|
|
401
|
+
`kirby:*`, and for `*`. App admins hold `<type>:*` for every declared type. A
|
|
402
|
+
grant whose instance later disappears stays on the key and simply matches
|
|
403
|
+
nothing; removing it is a console act, not the app's.
|
|
404
|
+
|
|
405
|
+
The full contract — token claims, every error shape, what happens when the
|
|
406
|
+
list is unreachable — is in
|
|
407
|
+
[`apps/idp/docs/resource-scopes.md`](../../apps/idp/docs/resource-scopes.md).
|
|
408
|
+
|
|
326
409
|
## Linked identities
|
|
327
410
|
|
|
328
411
|
A user's ids on *other* systems — their Slack member id, their WhatsApp number,
|
package/dist/src/client.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export declare const DEFAULT_SCOPES: readonly IdpScope[];
|
|
|
21
21
|
export type IdpClientOptions = {
|
|
22
22
|
/**
|
|
23
23
|
* The OIDC issuer, including the basepath the IdP is mounted on:
|
|
24
|
-
* `https://idp.willy.im/auth` (or a vanity domain, `https://idp.
|
|
24
|
+
* `https://idp.willy.im/auth` (or a vanity domain, `https://idp.app1.com/auth`).
|
|
25
25
|
* Discovery is `${issuer}/.well-known/openid-configuration`.
|
|
26
26
|
*/
|
|
27
27
|
issuer: string;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
* @willyim/idp/react-router the auth route and the loader guards
|
|
14
14
|
*/
|
|
15
15
|
export { createIdpClient, createPkce, DEFAULT_SCOPES, IdpError, SUPPORTED_SCOPES, type AuthorizationUrlInput, type Discovery, type IdpClient, type IdpClientOptions, type IdpScope, type Tokens, } from "./client.js";
|
|
16
|
-
export { createIdp, DEFAULT_SESSION_COOKIE, safeNext, type Idp, type IdpOptions, type Session, type SessionOptions, } from "./session.js";
|
|
16
|
+
export { createIdp, DEFAULT_SESSION_COOKIE, publicSession, safeNext, type Idp, type IdpOptions, type PublicSession, type Session, type SessionOptions, } from "./session.js";
|
|
17
17
|
export { memorySessions, type MemorySessionStore, type SessionRecord, type SessionStore, } from "./store.js";
|
|
18
18
|
export { grants, normalizeClaims, PERMISSIONS_CLAIM, WORKSPACES_CLAIM, type Actor, type Claims, type Workspace, } from "./claims.js";
|
|
19
19
|
export { createManagementApi, type ManagementApi, type ManagementApiOptions, } from "./api.js";
|
|
20
|
+
export { RESOURCE_ID_RE, RESOURCE_LIST_PERMISSION, RESOURCE_LIST_TOKEN_TTL_S, RESOURCE_TYPE_RE, ResourceInstanceSchema, ResourceListSchema, ResourceTypeInput, ResourceTypeSchema, } from "./schemas/index.js";
|
|
20
21
|
export { createUserKeys, readApiKey, type AuthenticatedKey, type AuthenticateOptions, type AuthenticateResult, type CreateUserApiKeyInput, type ListFilter, type MintedUserApiKey, type UserApiKey, type UserKeyCacheOptions, type UserKeys, type UserKeysOptions, type UserKeyValidation, } from "./user-keys.js";
|
|
21
22
|
export { createIdentities, type Identities, type IdentitiesOptions, type IdentityCacheOptions, type ResolvedIdentity, type IdentityResolution, } from "./identities.js";
|
|
22
23
|
export { createResourceServer, type ResourceServer, type ResourceServerOptions, type VerifiedAccessToken, type AuthResult, } from "./resource-server.js";
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,eAAe,EACf,UAAU,EACV,cAAc,EACd,QAAQ,EACR,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,QAAQ,EACb,KAAK,MAAM,GACZ,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,SAAS,EACT,sBAAsB,EACtB,QAAQ,EACR,KAAK,GAAG,EACR,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,cAAc,GACpB,MAAM,cAAc,CAAA;AAErB,OAAO,EACL,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,MAAM,EACN,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,KAAK,EACV,KAAK,MAAM,EACX,KAAK,SAAS,GACf,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,oBAAoB,GAC1B,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,eAAe,EACf,UAAU,EACV,cAAc,EACd,QAAQ,EACR,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,QAAQ,EACb,KAAK,MAAM,GACZ,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,SAAS,EACT,sBAAsB,EACtB,aAAa,EACb,QAAQ,EACR,KAAK,GAAG,EACR,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,OAAO,EACZ,KAAK,cAAc,GACpB,MAAM,cAAc,CAAA;AAErB,OAAO,EACL,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,MAAM,EACN,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,KAAK,EACV,KAAK,MAAM,EACX,KAAK,SAAS,GACf,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,oBAAoB,GAC1B,MAAM,UAAU,CAAA;AAIjB,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,yBAAyB,EACzB,gBAAgB,EAChB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EACL,cAAc,EACd,UAAU,EACV,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,iBAAiB,GACvB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACL,gBAAgB,EAChB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,iBAAiB,CAAA;AAExB,OAAO,EACL,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,UAAU,GAChB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC5D,OAAO,EACL,WAAW,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,KAAK,aAAa,GACnB,MAAM,aAAa,CAAA"}
|
package/dist/src/index.js
CHANGED
|
@@ -13,10 +13,13 @@
|
|
|
13
13
|
* @willyim/idp/react-router the auth route and the loader guards
|
|
14
14
|
*/
|
|
15
15
|
export { createIdpClient, createPkce, DEFAULT_SCOPES, IdpError, SUPPORTED_SCOPES, } from "./client.js";
|
|
16
|
-
export { createIdp, DEFAULT_SESSION_COOKIE, safeNext, } from "./session.js";
|
|
16
|
+
export { createIdp, DEFAULT_SESSION_COOKIE, publicSession, safeNext, } from "./session.js";
|
|
17
17
|
export { memorySessions, } from "./store.js";
|
|
18
18
|
export { grants, normalizeClaims, PERMISSIONS_CLAIM, WORKSPACES_CLAIM, } from "./claims.js";
|
|
19
19
|
export { createManagementApi, } from "./api.js";
|
|
20
|
+
// Resource-scoped grants: the permission the IdP's listing token carries, and
|
|
21
|
+
// the shapes of a type and an instance. The full set lives in ./schemas.
|
|
22
|
+
export { RESOURCE_ID_RE, RESOURCE_LIST_PERMISSION, RESOURCE_LIST_TOKEN_TTL_S, RESOURCE_TYPE_RE, ResourceInstanceSchema, ResourceListSchema, ResourceTypeInput, ResourceTypeSchema, } from "./schemas/index.js";
|
|
20
23
|
export { createUserKeys, readApiKey, } from "./user-keys.js";
|
|
21
24
|
export { createIdentities, } from "./identities.js";
|
|
22
25
|
export { createResourceServer, } from "./resource-server.js";
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* const session = await requireSession(args)
|
|
10
10
|
* const session = await requirePermission(args, "admin")
|
|
11
11
|
*
|
|
12
|
+
* // from the browser, when there's no loader to read
|
|
13
|
+
* await fetch("/auth/me").then((r) => r.json()) // { user: PublicSession | null }
|
|
14
|
+
*
|
|
12
15
|
* No react-router import: the adapter only ever produces `Response`s and
|
|
13
16
|
* functions, both of which react-router already understands. That keeps the
|
|
14
17
|
* subpath dependency-free too, and makes it usable from any framework whose
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react-router/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react-router/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAGjD,4EAA4E;AAC5E,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CAC5C,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,SAAS,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;AAEjE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAA;CAC3C,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,gBAAqB;mBAmEhE,SAAS;mBACT,SAAS;EAE3B;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,GAAG,CAAA;IACT,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oCAAoC;IACpC,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,kFAAkF;AAClF,wBAAsB,UAAU,CAC9B,IAAI,EAAE,SAAS,EACf,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAEzB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,SAAS,EACf,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,SAAS,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,OAAO,CAAC,CAMlB"}
|
|
@@ -9,12 +9,15 @@
|
|
|
9
9
|
* const session = await requireSession(args)
|
|
10
10
|
* const session = await requirePermission(args, "admin")
|
|
11
11
|
*
|
|
12
|
+
* // from the browser, when there's no loader to read
|
|
13
|
+
* await fetch("/auth/me").then((r) => r.json()) // { user: PublicSession | null }
|
|
14
|
+
*
|
|
12
15
|
* No react-router import: the adapter only ever produces `Response`s and
|
|
13
16
|
* functions, both of which react-router already understands. That keeps the
|
|
14
17
|
* subpath dependency-free too, and makes it usable from any framework whose
|
|
15
18
|
* handlers take a `Request`.
|
|
16
19
|
*/
|
|
17
|
-
import { safeNext } from "../session.js";
|
|
20
|
+
import { publicSession, safeNext } from "../session.js";
|
|
18
21
|
/**
|
|
19
22
|
* Serves `/auth/login`, `/auth/callback` and `/auth/logout` from a single splat
|
|
20
23
|
* route. `loader` covers the GET navigations; `action` covers a POSTed logout
|
|
@@ -57,6 +60,26 @@ export function createAuthRoute(getIdp, options = {}) {
|
|
|
57
60
|
// outlives the app's.
|
|
58
61
|
return redirect(endSession ?? redirectTo, headers);
|
|
59
62
|
}
|
|
63
|
+
// The session as JSON, for the code that can't reach a loader: a client-only
|
|
64
|
+
// route, a widget mounted outside the router, a fetch from a worker. A
|
|
65
|
+
// server-rendered page should keep reading `requireSession` — this is the
|
|
66
|
+
// same data one network hop later.
|
|
67
|
+
if (action === "me") {
|
|
68
|
+
const session = await idp.getSession(args.request);
|
|
69
|
+
const headers = new Headers({
|
|
70
|
+
"content-type": "application/json",
|
|
71
|
+
// Per-user and revocable — never let a shared cache hold it.
|
|
72
|
+
"cache-control": "no-store, private",
|
|
73
|
+
});
|
|
74
|
+
if (!session) {
|
|
75
|
+
return new Response(JSON.stringify({ user: null }), { status: 401, headers });
|
|
76
|
+
}
|
|
77
|
+
// Polling this keeps a sliding session alive, same as any other request.
|
|
78
|
+
const renewed = session.renewCookie();
|
|
79
|
+
if (renewed)
|
|
80
|
+
headers.append("set-cookie", renewed);
|
|
81
|
+
return new Response(JSON.stringify({ user: publicSession(session) }), { status: 200, headers });
|
|
82
|
+
}
|
|
60
83
|
return new Response("not found", { status: 404 });
|
|
61
84
|
}
|
|
62
85
|
return {
|
|
@@ -10,12 +10,53 @@
|
|
|
10
10
|
* `../wire.ts`, next to the code that talks to them.
|
|
11
11
|
*/
|
|
12
12
|
import { z } from "zod";
|
|
13
|
+
/** A resource type name: colon-separated lowercase segments, no wildcard. */
|
|
14
|
+
export declare const RESOURCE_TYPE_RE: RegExp;
|
|
15
|
+
/** An instance id: the final segment of a grant, so no colon, no `*`, no whitespace. */
|
|
16
|
+
export declare const RESOURCE_ID_RE: RegExp;
|
|
17
|
+
/**
|
|
18
|
+
* The permission the IdP's listing token carries, so an app can gate its `list`
|
|
19
|
+
* endpoint with `resourceServer.authenticate(request, { permissions: [...] })`.
|
|
20
|
+
* Namespaced under `idp:` so it can never collide with an app's own catalog.
|
|
21
|
+
*/
|
|
22
|
+
export declare const RESOURCE_LIST_PERMISSION = "idp:resources:list";
|
|
23
|
+
/** Lifetime of the IdP-signed token that authenticates a listing call. */
|
|
24
|
+
export declare const RESOURCE_LIST_TOKEN_TTL_S = 60;
|
|
25
|
+
export declare const ResourceTypeInput: z.ZodObject<{
|
|
26
|
+
type: z.ZodString;
|
|
27
|
+
label: z.ZodOptional<z.ZodString>;
|
|
28
|
+
list: z.ZodString;
|
|
29
|
+
}, z.core.$strip>;
|
|
30
|
+
export declare const ResourceTypeSchema: z.ZodObject<{
|
|
31
|
+
type: z.ZodString;
|
|
32
|
+
label: z.ZodString;
|
|
33
|
+
list: z.ZodString;
|
|
34
|
+
}, z.core.$strip>;
|
|
35
|
+
/** One instance of a resource type, as the app's `list` endpoint reports it. */
|
|
36
|
+
export declare const ResourceInstanceSchema: z.ZodObject<{
|
|
37
|
+
id: z.ZodString;
|
|
38
|
+
label: z.ZodString;
|
|
39
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
40
|
+
}, z.core.$strip>;
|
|
41
|
+
/** The body an app's resource `list` endpoint must answer with. */
|
|
42
|
+
export declare const ResourceListSchema: z.ZodObject<{
|
|
43
|
+
resources: z.ZodArray<z.ZodObject<{
|
|
44
|
+
id: z.ZodString;
|
|
45
|
+
label: z.ZodString;
|
|
46
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
47
|
+
}, z.core.$strip>>;
|
|
48
|
+
}, z.core.$strip>;
|
|
13
49
|
export declare const ApplicationSchema: z.ZodObject<{
|
|
14
50
|
clientId: z.ZodString;
|
|
15
51
|
name: z.ZodNullable<z.ZodString>;
|
|
16
52
|
app: z.ZodNullable<z.ZodString>;
|
|
17
53
|
allowSignup: z.ZodBoolean;
|
|
18
54
|
permissions: z.ZodArray<z.ZodString>;
|
|
55
|
+
resourceTypes: z.ZodArray<z.ZodObject<{
|
|
56
|
+
type: z.ZodString;
|
|
57
|
+
label: z.ZodString;
|
|
58
|
+
list: z.ZodString;
|
|
59
|
+
}, z.core.$strip>>;
|
|
19
60
|
resources: z.ZodArray<z.ZodString>;
|
|
20
61
|
redirectUris: z.ZodArray<z.ZodString>;
|
|
21
62
|
disabled: z.ZodBoolean;
|
|
@@ -25,6 +66,7 @@ export declare const UserSchema: z.ZodObject<{
|
|
|
25
66
|
id: z.ZodString;
|
|
26
67
|
email: z.ZodString;
|
|
27
68
|
name: z.ZodNullable<z.ZodString>;
|
|
69
|
+
image: z.ZodString;
|
|
28
70
|
emailVerified: z.ZodBoolean;
|
|
29
71
|
createdAt: z.ZodString;
|
|
30
72
|
}, z.core.$strip>;
|
|
@@ -42,6 +84,11 @@ export declare const ApplicationListSchema: z.ZodObject<{
|
|
|
42
84
|
app: z.ZodNullable<z.ZodString>;
|
|
43
85
|
allowSignup: z.ZodBoolean;
|
|
44
86
|
permissions: z.ZodArray<z.ZodString>;
|
|
87
|
+
resourceTypes: z.ZodArray<z.ZodObject<{
|
|
88
|
+
type: z.ZodString;
|
|
89
|
+
label: z.ZodString;
|
|
90
|
+
list: z.ZodString;
|
|
91
|
+
}, z.core.$strip>>;
|
|
45
92
|
resources: z.ZodArray<z.ZodString>;
|
|
46
93
|
redirectUris: z.ZodArray<z.ZodString>;
|
|
47
94
|
disabled: z.ZodBoolean;
|
|
@@ -73,18 +120,33 @@ export declare const UpdateApplicationInput: z.ZodObject<{
|
|
|
73
120
|
export declare const ClientSecretSchema: z.ZodObject<{
|
|
74
121
|
clientSecret: z.ZodString;
|
|
75
122
|
}, z.core.$strip>;
|
|
76
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* Replaces the app's product-permission catalog wholesale — both the flat
|
|
125
|
+
* permissions and the resource types. Omitting `resourceTypes` clears them,
|
|
126
|
+
* the same way omitting a permission removes it.
|
|
127
|
+
*/
|
|
77
128
|
export declare const SetAppPermissionsInput: z.ZodObject<{
|
|
78
129
|
permissions: z.ZodArray<z.ZodString>;
|
|
130
|
+
resourceTypes: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
131
|
+
type: z.ZodString;
|
|
132
|
+
label: z.ZodOptional<z.ZodString>;
|
|
133
|
+
list: z.ZodString;
|
|
134
|
+
}, z.core.$strip>>>;
|
|
79
135
|
}, z.core.$strip>;
|
|
80
136
|
export declare const AppPermissionsSchema: z.ZodObject<{
|
|
81
137
|
permissions: z.ZodArray<z.ZodString>;
|
|
138
|
+
resourceTypes: z.ZodArray<z.ZodObject<{
|
|
139
|
+
type: z.ZodString;
|
|
140
|
+
label: z.ZodString;
|
|
141
|
+
list: z.ZodString;
|
|
142
|
+
}, z.core.$strip>>;
|
|
82
143
|
}, z.core.$strip>;
|
|
83
144
|
export declare const UserListSchema: z.ZodObject<{
|
|
84
145
|
users: z.ZodArray<z.ZodObject<{
|
|
85
146
|
id: z.ZodString;
|
|
86
147
|
email: z.ZodString;
|
|
87
148
|
name: z.ZodNullable<z.ZodString>;
|
|
149
|
+
image: z.ZodString;
|
|
88
150
|
emailVerified: z.ZodBoolean;
|
|
89
151
|
createdAt: z.ZodString;
|
|
90
152
|
}, z.core.$strip>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/schemas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/schemas/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,12 +10,60 @@
|
|
|
10
10
|
* `../wire.ts`, next to the code that talks to them.
|
|
11
11
|
*/
|
|
12
12
|
import { z } from "zod";
|
|
13
|
+
// --- Resource-scoped permissions ---
|
|
14
|
+
//
|
|
15
|
+
// A permission names a surface (`kirby:read`). A resource TYPE names a family
|
|
16
|
+
// of permissions over instances the app holds (`kirby:thread` over every
|
|
17
|
+
// conversation), and a grant is the composed string `<type>:<id>` —
|
|
18
|
+
// `kirby:thread:t_7f3a`. The IdP never stores the instances: it asks the app
|
|
19
|
+
// for them, over the `list` URL the type declares, whenever a human picks one
|
|
20
|
+
// or a key is minted. `grants()` is untouched — `kirby:*` and `*` cover every
|
|
21
|
+
// instance the same way they cover `kirby:read`.
|
|
22
|
+
/** A resource type name: colon-separated lowercase segments, no wildcard. */
|
|
23
|
+
export const RESOURCE_TYPE_RE = /^[a-z0-9_-]+(:[a-z0-9_-]+)*$/;
|
|
24
|
+
/** An instance id: the final segment of a grant, so no colon, no `*`, no whitespace. */
|
|
25
|
+
export const RESOURCE_ID_RE = /^[^\s:*]+$/;
|
|
26
|
+
/**
|
|
27
|
+
* The permission the IdP's listing token carries, so an app can gate its `list`
|
|
28
|
+
* endpoint with `resourceServer.authenticate(request, { permissions: [...] })`.
|
|
29
|
+
* Namespaced under `idp:` so it can never collide with an app's own catalog.
|
|
30
|
+
*/
|
|
31
|
+
export const RESOURCE_LIST_PERMISSION = "idp:resources:list";
|
|
32
|
+
/** Lifetime of the IdP-signed token that authenticates a listing call. */
|
|
33
|
+
export const RESOURCE_LIST_TOKEN_TTL_S = 60;
|
|
34
|
+
export const ResourceTypeInput = z.object({
|
|
35
|
+
type: z
|
|
36
|
+
.string()
|
|
37
|
+
.regex(RESOURCE_TYPE_RE, "lowercase segments separated by colons, e.g. kirby:thread")
|
|
38
|
+
.describe("Permission prefix a grant composes under: `kirby:thread` → `kirby:thread:<id>`"),
|
|
39
|
+
label: z.string().min(1).optional().describe("Human name for one instance, shown in the console — “WhatsApp conversation”"),
|
|
40
|
+
list: z
|
|
41
|
+
.string()
|
|
42
|
+
.url()
|
|
43
|
+
.describe("Absolute URL the IdP GETs to enumerate instances. Called with an IdP-signed bearer JWT (aud = this URL); must answer `ResourceListSchema`"),
|
|
44
|
+
});
|
|
45
|
+
export const ResourceTypeSchema = z.object({
|
|
46
|
+
type: z.string(),
|
|
47
|
+
label: z.string(),
|
|
48
|
+
list: z.string(),
|
|
49
|
+
});
|
|
50
|
+
/** One instance of a resource type, as the app's `list` endpoint reports it. */
|
|
51
|
+
export const ResourceInstanceSchema = z.object({
|
|
52
|
+
id: z.string().regex(RESOURCE_ID_RE).describe("The stable, opaque id the grant will name — a handle, never an alias"),
|
|
53
|
+
label: z.string().min(1).describe("What a human sees when picking it"),
|
|
54
|
+
description: z.string().nullable().optional().describe("Secondary line in the picker — kind, size, last activity"),
|
|
55
|
+
});
|
|
56
|
+
/** The body an app's resource `list` endpoint must answer with. */
|
|
57
|
+
export const ResourceListSchema = z.object({ resources: z.array(ResourceInstanceSchema) });
|
|
13
58
|
export const ApplicationSchema = z.object({
|
|
14
59
|
clientId: z.string(),
|
|
15
60
|
name: z.string().nullable(),
|
|
16
61
|
app: z.string().nullable().describe("Application key; consumer workspace claims are filtered by this"),
|
|
17
62
|
allowSignup: z.boolean().describe("Whether unknown users may sign themselves up"),
|
|
18
63
|
permissions: z.array(z.string()).describe("The app's declared product-permission catalog"),
|
|
64
|
+
resourceTypes: z
|
|
65
|
+
.array(ResourceTypeSchema)
|
|
66
|
+
.describe("Declared resource types — permission families over instances the app holds"),
|
|
19
67
|
resources: z
|
|
20
68
|
.array(z.string())
|
|
21
69
|
.describe("Protected resource URIs (e.g. the app's MCP server) — valid `resource` audiences for access tokens"),
|
|
@@ -27,6 +75,9 @@ export const UserSchema = z.object({
|
|
|
27
75
|
id: z.string(),
|
|
28
76
|
email: z.string(),
|
|
29
77
|
name: z.string().nullable(),
|
|
78
|
+
image: z
|
|
79
|
+
.string()
|
|
80
|
+
.describe("Avatar URL — the user's own picture, or the blobatar the IdP renders for them"),
|
|
30
81
|
emailVerified: z.boolean(),
|
|
31
82
|
createdAt: z.string(),
|
|
32
83
|
});
|
|
@@ -74,11 +125,19 @@ export const UpdateApplicationInput = z.object({
|
|
|
74
125
|
export const ClientSecretSchema = z.object({
|
|
75
126
|
clientSecret: z.string().describe("Plaintext client secret — shown exactly once, never stored"),
|
|
76
127
|
});
|
|
77
|
-
/**
|
|
128
|
+
/**
|
|
129
|
+
* Replaces the app's product-permission catalog wholesale — both the flat
|
|
130
|
+
* permissions and the resource types. Omitting `resourceTypes` clears them,
|
|
131
|
+
* the same way omitting a permission removes it.
|
|
132
|
+
*/
|
|
78
133
|
export const SetAppPermissionsInput = z.object({
|
|
79
134
|
permissions: z.array(z.string().min(1)),
|
|
135
|
+
resourceTypes: z.array(ResourceTypeInput).default([]),
|
|
136
|
+
});
|
|
137
|
+
export const AppPermissionsSchema = z.object({
|
|
138
|
+
permissions: z.array(z.string()),
|
|
139
|
+
resourceTypes: z.array(ResourceTypeSchema),
|
|
80
140
|
});
|
|
81
|
-
export const AppPermissionsSchema = z.object({ permissions: z.array(z.string()) });
|
|
82
141
|
export const UserListSchema = z.object({ users: z.array(UserSchema) });
|
|
83
142
|
export const WorkspaceListSchema = z.object({ workspaces: z.array(WorkspaceSchema) });
|
|
84
143
|
// --- Write management API (scoped-key authenticated, per-app) ---
|
|
@@ -135,7 +194,10 @@ export const UserApiKeyListSchema = z.object({ keys: z.array(UserApiKeySchema) }
|
|
|
135
194
|
export const CreateUserApiKeyInput = z.object({
|
|
136
195
|
userId: z.string().min(1),
|
|
137
196
|
name: z.string().min(1),
|
|
138
|
-
scopes: z
|
|
197
|
+
scopes: z
|
|
198
|
+
.array(z.string())
|
|
199
|
+
.default([])
|
|
200
|
+
.describe("Declared permissions, or `<type>:<id>` grants over a declared resource type whose instance the app currently lists"),
|
|
139
201
|
workspaceId: z.string().optional(),
|
|
140
202
|
expiresAt: z.iso.datetime().optional().describe("ISO 8601; omit for non-expiring"),
|
|
141
203
|
});
|
|
@@ -43,6 +43,11 @@ export declare const operations: {
|
|
|
43
43
|
app: z.ZodNullable<z.ZodString>;
|
|
44
44
|
allowSignup: z.ZodBoolean;
|
|
45
45
|
permissions: z.ZodArray<z.ZodString>;
|
|
46
|
+
resourceTypes: z.ZodArray<z.ZodObject<{
|
|
47
|
+
type: z.ZodString;
|
|
48
|
+
label: z.ZodString;
|
|
49
|
+
list: z.ZodString;
|
|
50
|
+
}, z.core.$strip>>;
|
|
46
51
|
resources: z.ZodArray<z.ZodString>;
|
|
47
52
|
redirectUris: z.ZodArray<z.ZodString>;
|
|
48
53
|
disabled: z.ZodBoolean;
|
|
@@ -80,6 +85,11 @@ export declare const operations: {
|
|
|
80
85
|
app: z.ZodNullable<z.ZodString>;
|
|
81
86
|
allowSignup: z.ZodBoolean;
|
|
82
87
|
permissions: z.ZodArray<z.ZodString>;
|
|
88
|
+
resourceTypes: z.ZodArray<z.ZodObject<{
|
|
89
|
+
type: z.ZodString;
|
|
90
|
+
label: z.ZodString;
|
|
91
|
+
list: z.ZodString;
|
|
92
|
+
}, z.core.$strip>>;
|
|
83
93
|
resources: z.ZodArray<z.ZodString>;
|
|
84
94
|
redirectUris: z.ZodArray<z.ZodString>;
|
|
85
95
|
disabled: z.ZodBoolean;
|
|
@@ -106,6 +116,11 @@ export declare const operations: {
|
|
|
106
116
|
app: z.ZodNullable<z.ZodString>;
|
|
107
117
|
allowSignup: z.ZodBoolean;
|
|
108
118
|
permissions: z.ZodArray<z.ZodString>;
|
|
119
|
+
resourceTypes: z.ZodArray<z.ZodObject<{
|
|
120
|
+
type: z.ZodString;
|
|
121
|
+
label: z.ZodString;
|
|
122
|
+
list: z.ZodString;
|
|
123
|
+
}, z.core.$strip>>;
|
|
109
124
|
resources: z.ZodArray<z.ZodString>;
|
|
110
125
|
redirectUris: z.ZodArray<z.ZodString>;
|
|
111
126
|
disabled: z.ZodBoolean;
|
|
@@ -138,6 +153,7 @@ export declare const operations: {
|
|
|
138
153
|
};
|
|
139
154
|
readonly "put /api/v1/apps/{app}/permissions": {
|
|
140
155
|
readonly summary: "Replace the app's product-permission catalog";
|
|
156
|
+
readonly description: "Wholesale replace of both halves of the catalog: the flat permissions, and the resource types (`{ type, label, list }`) that per-instance grants such as `kirby:thread:<id>` compose under. The IdP GETs each type's `list` URL — with a 60s IdP-signed JWT whose `aud` is that URL — whenever it needs the instances; it never stores them. Omitting `resourceTypes` clears them. 422 `invalid_resource_type` names a `list` URL that is not absolute https (http is allowed for loopback hosts only).";
|
|
141
157
|
readonly permission: "app:update";
|
|
142
158
|
readonly params: {
|
|
143
159
|
readonly app: "Application key (oauth_client.metadata.app).";
|
|
@@ -145,10 +161,20 @@ export declare const operations: {
|
|
|
145
161
|
readonly notFound: "No application with that app key";
|
|
146
162
|
readonly input: z.ZodObject<{
|
|
147
163
|
permissions: z.ZodArray<z.ZodString>;
|
|
164
|
+
resourceTypes: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
165
|
+
type: z.ZodString;
|
|
166
|
+
label: z.ZodOptional<z.ZodString>;
|
|
167
|
+
list: z.ZodString;
|
|
168
|
+
}, z.core.$strip>>>;
|
|
148
169
|
}, z.core.$strip>;
|
|
149
170
|
readonly successCode: "200";
|
|
150
171
|
readonly success: z.ZodObject<{
|
|
151
172
|
permissions: z.ZodArray<z.ZodString>;
|
|
173
|
+
resourceTypes: z.ZodArray<z.ZodObject<{
|
|
174
|
+
type: z.ZodString;
|
|
175
|
+
label: z.ZodString;
|
|
176
|
+
list: z.ZodString;
|
|
177
|
+
}, z.core.$strip>>;
|
|
152
178
|
}, z.core.$strip>;
|
|
153
179
|
};
|
|
154
180
|
readonly "get /api/v1/apps/{app}/keys": {
|
|
@@ -262,6 +288,7 @@ export declare const operations: {
|
|
|
262
288
|
id: z.ZodString;
|
|
263
289
|
email: z.ZodString;
|
|
264
290
|
name: z.ZodNullable<z.ZodString>;
|
|
291
|
+
image: z.ZodString;
|
|
265
292
|
emailVerified: z.ZodBoolean;
|
|
266
293
|
createdAt: z.ZodString;
|
|
267
294
|
}, z.core.$strip>>;
|
|
@@ -420,6 +447,7 @@ export declare const operations: {
|
|
|
420
447
|
};
|
|
421
448
|
readonly "post /api/v1/apps/{app}/user-keys": {
|
|
422
449
|
readonly summary: "Mint an end-user API key (plaintext returned once)";
|
|
450
|
+
readonly description: "Every scope must be a declared permission or `<declared type>:<id>` for an instance the app currently lists. 422 `unknown_scopes` names scopes the catalog does not declare; 422 `unknown_resource` names `<type>:<id>` grants whose instance the app did not list; 502 `resource_lookup_failed` names the types whose `list` URL could not be read.";
|
|
423
451
|
readonly permission: "userkey:create";
|
|
424
452
|
readonly params: {
|
|
425
453
|
readonly app: "Application key (oauth_client.metadata.app).";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../../src/schemas/operations.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAsCvB,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;AAEpE,iFAAiF;AACjF,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAEnF,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,SAAS,UAAU,EAAE,CAAA;IAC7B,wEAAwE;IACxE,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACzC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAA;IACjB,WAAW,EAAE,KAAK,GAAG,KAAK,CAAA;IAC1B,OAAO,EAAE,CAAC,CAAC,OAAO,CAAA;CACnB,CAAA;AAKD,eAAO,MAAM,UAAU
|
|
1
|
+
{"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../../src/schemas/operations.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAsCvB,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;AAEpE,iFAAiF;AACjF,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAEnF,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,SAAS,UAAU,EAAE,CAAA;IAC7B,wEAAwE;IACxE,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACzC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAA;IACjB,WAAW,EAAE,KAAK,GAAG,KAAK,CAAA;IAC1B,OAAO,EAAE,CAAC,CAAC,OAAO,CAAA;CACnB,CAAA;AAKD,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmP0B,CAAA;AAEjD,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAA;AAC1C,MAAM,MAAM,YAAY,GAAG,MAAM,UAAU,CAAA;AAE3C,mEAAmE;AACnE,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,UAAU,IAAI,YAAY,SAAS,MAAM,CAAC,GACrE,CAAC,SAAS,GAAG,CAAC,IAAI,MAAM,CAAC,EAAE,GACzB,CAAC,GACD,KAAK,GACP,KAAK,CAAA;AAET,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,YAAY,GAC/F,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GACvB,KAAK,CAAA;AAET,iFAAiF;AACjF,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,IAAI,MAAM,IAAI,IAAI,MAAM,IAAI,EAAE,GAC5F,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,GAC3B,KAAK,CAAA;AAET,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAE7E"}
|
|
@@ -59,6 +59,7 @@ export const operations = {
|
|
|
59
59
|
},
|
|
60
60
|
"put /api/v1/apps/{app}/permissions": {
|
|
61
61
|
summary: "Replace the app's product-permission catalog",
|
|
62
|
+
description: "Wholesale replace of both halves of the catalog: the flat permissions, and the resource types (`{ type, label, list }`) that per-instance grants such as `kirby:thread:<id>` compose under. The IdP GETs each type's `list` URL — with a 60s IdP-signed JWT whose `aud` is that URL — whenever it needs the instances; it never stores them. Omitting `resourceTypes` clears them. 422 `invalid_resource_type` names a `list` URL that is not absolute https (http is allowed for loopback hosts only).",
|
|
62
63
|
permission: "app:update",
|
|
63
64
|
params: APP_PARAM,
|
|
64
65
|
notFound: "No application with that app key",
|
|
@@ -179,6 +180,7 @@ export const operations = {
|
|
|
179
180
|
},
|
|
180
181
|
"post /api/v1/apps/{app}/user-keys": {
|
|
181
182
|
summary: "Mint an end-user API key (plaintext returned once)",
|
|
183
|
+
description: "Every scope must be a declared permission or `<declared type>:<id>` for an instance the app currently lists. 422 `unknown_scopes` names scopes the catalog does not declare; 422 `unknown_resource` names `<type>:<id>` grants whose instance the app did not list; 502 `resource_lookup_failed` names the types whose `list` URL could not be read.",
|
|
182
184
|
permission: "userkey:create",
|
|
183
185
|
params: APP_PARAM,
|
|
184
186
|
input: CreateUserApiKeyInput,
|
package/dist/src/session.d.ts
CHANGED
|
@@ -64,6 +64,26 @@ export type Session = {
|
|
|
64
64
|
*/
|
|
65
65
|
renewCookie(): string | null;
|
|
66
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* A `Session` as JSON — the shape that is safe to hand a browser.
|
|
69
|
+
*
|
|
70
|
+
* Two deliberate omissions. The methods are gone because they don't serialize.
|
|
71
|
+
* The session *id* is gone because it is the thing the signed cookie resolves
|
|
72
|
+
* to: it never needs to be in reach of script, and a projection that leaves it
|
|
73
|
+
* out can't leak it by accident later.
|
|
74
|
+
*/
|
|
75
|
+
export type PublicSession = {
|
|
76
|
+
sub: string;
|
|
77
|
+
email: string;
|
|
78
|
+
name: string | null;
|
|
79
|
+
image: string | null;
|
|
80
|
+
permissions: string[];
|
|
81
|
+
workspaces: Workspace[];
|
|
82
|
+
actor: Actor | null;
|
|
83
|
+
expiresAt: string;
|
|
84
|
+
};
|
|
85
|
+
/** `Session` -> `PublicSession`. What `/auth/me` returns, and what an app's own `/me` should. */
|
|
86
|
+
export declare function publicSession(session: Session): PublicSession;
|
|
67
87
|
export type Idp = ReturnType<typeof createIdp>;
|
|
68
88
|
export declare function createIdp(options: IdpOptions): {
|
|
69
89
|
/** The Layer 0 client, for anything the session layer doesn't wrap. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE3D,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,aAAa,CAAA;AAQpB,OAAO,EAAiB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC5D,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,YAAY,CAAA;AAE7D,eAAO,MAAM,sBAAsB,gBAAgB,CAAA;AAKnD,MAAM,MAAM,cAAc,GAAG;IAC3B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,SAAS,CAAC,EAAE,QAAQ,CAAA;IACpB,wFAAwF;IACxF,SAAS,CAAC,EAAE,QAAQ,CAAA;IACpB,wFAAwF;IACxF,SAAS,CAAC,EAAE,QAAQ,CAAA;IACpB,wEAAwE;IACxE,iBAAiB,CAAC,EAAE,QAAQ,CAAA;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yDAAyD;IACzD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAA;IACpC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,gBAAgB,GAAG;IAC1C,QAAQ,EAAE,YAAY,CAAA;IACtB,OAAO,EAAE,cAAc,CAAA;IACvB,uDAAuD;IACvD,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IAChB,sFAAsF;IACtF,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;IACf,iEAAiE;IACjE,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAA;IAChC;;;;OAIG;IACH,WAAW,IAAI,MAAM,GAAG,IAAI,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAA;AAe9C,wBAAgB,SAAS,CAAC,OAAO,EAAE,UAAU;IAqNzC,uEAAuE;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/session.ts"],"names":[],"mappings":"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"}
|
package/dist/src/session.js
CHANGED
|
@@ -17,6 +17,19 @@ import { parseDuration } from "./duration.js";
|
|
|
17
17
|
export const DEFAULT_SESSION_COOKIE = "idp_session";
|
|
18
18
|
/** How long the login handshake may take before its state cookie is stale. */
|
|
19
19
|
const STATE_COOKIE_MAX_AGE_MS = 10 * 60_000;
|
|
20
|
+
/** `Session` -> `PublicSession`. What `/auth/me` returns, and what an app's own `/me` should. */
|
|
21
|
+
export function publicSession(session) {
|
|
22
|
+
return {
|
|
23
|
+
sub: session.sub,
|
|
24
|
+
email: session.email,
|
|
25
|
+
name: session.name,
|
|
26
|
+
image: session.image,
|
|
27
|
+
permissions: session.permissions,
|
|
28
|
+
workspaces: session.workspaces,
|
|
29
|
+
actor: session.actor,
|
|
30
|
+
expiresAt: session.expiresAt.toISOString(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
20
33
|
/**
|
|
21
34
|
* What rides in the login state cookie. HMAC-signed, so tampering is caught
|
|
22
35
|
* before this ever parses — the schema is here to catch a cookie left over
|
package/openapi/idp-api.json
CHANGED
|
@@ -78,6 +78,37 @@
|
|
|
78
78
|
},
|
|
79
79
|
"description": "The app's declared product-permission catalog"
|
|
80
80
|
},
|
|
81
|
+
"resourceTypes": {
|
|
82
|
+
"type": "array",
|
|
83
|
+
"items": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"properties": {
|
|
86
|
+
"type": {
|
|
87
|
+
"type": "string"
|
|
88
|
+
},
|
|
89
|
+
"label": {
|
|
90
|
+
"type": "string"
|
|
91
|
+
},
|
|
92
|
+
"list": {
|
|
93
|
+
"type": "string"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"required": [
|
|
97
|
+
"type",
|
|
98
|
+
"label",
|
|
99
|
+
"list"
|
|
100
|
+
],
|
|
101
|
+
"additionalProperties": false
|
|
102
|
+
},
|
|
103
|
+
"description": "Declared resource types — permission families over instances the app holds"
|
|
104
|
+
},
|
|
105
|
+
"resources": {
|
|
106
|
+
"type": "array",
|
|
107
|
+
"items": {
|
|
108
|
+
"type": "string"
|
|
109
|
+
},
|
|
110
|
+
"description": "Protected resource URIs (e.g. the app's MCP server) — valid `resource` audiences for access tokens"
|
|
111
|
+
},
|
|
81
112
|
"redirectUris": {
|
|
82
113
|
"type": "array",
|
|
83
114
|
"items": {
|
|
@@ -98,6 +129,8 @@
|
|
|
98
129
|
"app",
|
|
99
130
|
"allowSignup",
|
|
100
131
|
"permissions",
|
|
132
|
+
"resourceTypes",
|
|
133
|
+
"resources",
|
|
101
134
|
"redirectUris",
|
|
102
135
|
"disabled",
|
|
103
136
|
"createdAt"
|
|
@@ -283,6 +316,37 @@
|
|
|
283
316
|
},
|
|
284
317
|
"description": "The app's declared product-permission catalog"
|
|
285
318
|
},
|
|
319
|
+
"resourceTypes": {
|
|
320
|
+
"type": "array",
|
|
321
|
+
"items": {
|
|
322
|
+
"type": "object",
|
|
323
|
+
"properties": {
|
|
324
|
+
"type": {
|
|
325
|
+
"type": "string"
|
|
326
|
+
},
|
|
327
|
+
"label": {
|
|
328
|
+
"type": "string"
|
|
329
|
+
},
|
|
330
|
+
"list": {
|
|
331
|
+
"type": "string"
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
"required": [
|
|
335
|
+
"type",
|
|
336
|
+
"label",
|
|
337
|
+
"list"
|
|
338
|
+
],
|
|
339
|
+
"additionalProperties": false
|
|
340
|
+
},
|
|
341
|
+
"description": "Declared resource types — permission families over instances the app holds"
|
|
342
|
+
},
|
|
343
|
+
"resources": {
|
|
344
|
+
"type": "array",
|
|
345
|
+
"items": {
|
|
346
|
+
"type": "string"
|
|
347
|
+
},
|
|
348
|
+
"description": "Protected resource URIs (e.g. the app's MCP server) — valid `resource` audiences for access tokens"
|
|
349
|
+
},
|
|
286
350
|
"redirectUris": {
|
|
287
351
|
"type": "array",
|
|
288
352
|
"items": {
|
|
@@ -303,6 +367,8 @@
|
|
|
303
367
|
"app",
|
|
304
368
|
"allowSignup",
|
|
305
369
|
"permissions",
|
|
370
|
+
"resourceTypes",
|
|
371
|
+
"resources",
|
|
306
372
|
"redirectUris",
|
|
307
373
|
"disabled",
|
|
308
374
|
"createdAt"
|
|
@@ -364,6 +430,14 @@
|
|
|
364
430
|
},
|
|
365
431
|
"allowSignup": {
|
|
366
432
|
"type": "boolean"
|
|
433
|
+
},
|
|
434
|
+
"resources": {
|
|
435
|
+
"description": "Replace the app's protected resource URIs — absolute https, no fragment",
|
|
436
|
+
"type": "array",
|
|
437
|
+
"items": {
|
|
438
|
+
"type": "string",
|
|
439
|
+
"format": "uri"
|
|
440
|
+
}
|
|
367
441
|
}
|
|
368
442
|
}
|
|
369
443
|
}
|
|
@@ -414,6 +488,37 @@
|
|
|
414
488
|
},
|
|
415
489
|
"description": "The app's declared product-permission catalog"
|
|
416
490
|
},
|
|
491
|
+
"resourceTypes": {
|
|
492
|
+
"type": "array",
|
|
493
|
+
"items": {
|
|
494
|
+
"type": "object",
|
|
495
|
+
"properties": {
|
|
496
|
+
"type": {
|
|
497
|
+
"type": "string"
|
|
498
|
+
},
|
|
499
|
+
"label": {
|
|
500
|
+
"type": "string"
|
|
501
|
+
},
|
|
502
|
+
"list": {
|
|
503
|
+
"type": "string"
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
"required": [
|
|
507
|
+
"type",
|
|
508
|
+
"label",
|
|
509
|
+
"list"
|
|
510
|
+
],
|
|
511
|
+
"additionalProperties": false
|
|
512
|
+
},
|
|
513
|
+
"description": "Declared resource types — permission families over instances the app holds"
|
|
514
|
+
},
|
|
515
|
+
"resources": {
|
|
516
|
+
"type": "array",
|
|
517
|
+
"items": {
|
|
518
|
+
"type": "string"
|
|
519
|
+
},
|
|
520
|
+
"description": "Protected resource URIs (e.g. the app's MCP server) — valid `resource` audiences for access tokens"
|
|
521
|
+
},
|
|
417
522
|
"redirectUris": {
|
|
418
523
|
"type": "array",
|
|
419
524
|
"items": {
|
|
@@ -434,6 +539,8 @@
|
|
|
434
539
|
"app",
|
|
435
540
|
"allowSignup",
|
|
436
541
|
"permissions",
|
|
542
|
+
"resourceTypes",
|
|
543
|
+
"resources",
|
|
437
544
|
"redirectUris",
|
|
438
545
|
"disabled",
|
|
439
546
|
"createdAt"
|
|
@@ -585,7 +692,7 @@
|
|
|
585
692
|
"/api/v1/apps/{app}/permissions": {
|
|
586
693
|
"put": {
|
|
587
694
|
"summary": "Replace the app's product-permission catalog",
|
|
588
|
-
"description": "
|
|
695
|
+
"description": "Wholesale replace of both halves of the catalog: the flat permissions, and the resource types (`{ type, label, list }`) that per-instance grants such as `kirby:thread:<id>` compose under. The IdP GETs each type's `list` URL — with a 60s IdP-signed JWT whose `aud` is that URL — whenever it needs the instances; it never stores them. Omitting `resourceTypes` clears them. 422 `invalid_resource_type` names a `list` URL that is not absolute https (http is allowed for loopback hosts only).",
|
|
589
696
|
"security": [
|
|
590
697
|
{
|
|
591
698
|
"bearerAuth": []
|
|
@@ -616,6 +723,34 @@
|
|
|
616
723
|
"type": "string",
|
|
617
724
|
"minLength": 1
|
|
618
725
|
}
|
|
726
|
+
},
|
|
727
|
+
"resourceTypes": {
|
|
728
|
+
"default": [],
|
|
729
|
+
"type": "array",
|
|
730
|
+
"items": {
|
|
731
|
+
"type": "object",
|
|
732
|
+
"properties": {
|
|
733
|
+
"type": {
|
|
734
|
+
"type": "string",
|
|
735
|
+
"pattern": "^[a-z0-9_-]+(:[a-z0-9_-]+)*$",
|
|
736
|
+
"description": "Permission prefix a grant composes under: `kirby:thread` → `kirby:thread:<id>`"
|
|
737
|
+
},
|
|
738
|
+
"label": {
|
|
739
|
+
"description": "Human name for one instance, shown in the console — “WhatsApp conversation”",
|
|
740
|
+
"type": "string",
|
|
741
|
+
"minLength": 1
|
|
742
|
+
},
|
|
743
|
+
"list": {
|
|
744
|
+
"type": "string",
|
|
745
|
+
"format": "uri",
|
|
746
|
+
"description": "Absolute URL the IdP GETs to enumerate instances. Called with an IdP-signed bearer JWT (aud = this URL); must answer `ResourceListSchema`"
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
"required": [
|
|
750
|
+
"type",
|
|
751
|
+
"list"
|
|
752
|
+
]
|
|
753
|
+
}
|
|
619
754
|
}
|
|
620
755
|
},
|
|
621
756
|
"required": [
|
|
@@ -639,10 +774,34 @@
|
|
|
639
774
|
"items": {
|
|
640
775
|
"type": "string"
|
|
641
776
|
}
|
|
777
|
+
},
|
|
778
|
+
"resourceTypes": {
|
|
779
|
+
"type": "array",
|
|
780
|
+
"items": {
|
|
781
|
+
"type": "object",
|
|
782
|
+
"properties": {
|
|
783
|
+
"type": {
|
|
784
|
+
"type": "string"
|
|
785
|
+
},
|
|
786
|
+
"label": {
|
|
787
|
+
"type": "string"
|
|
788
|
+
},
|
|
789
|
+
"list": {
|
|
790
|
+
"type": "string"
|
|
791
|
+
}
|
|
792
|
+
},
|
|
793
|
+
"required": [
|
|
794
|
+
"type",
|
|
795
|
+
"label",
|
|
796
|
+
"list"
|
|
797
|
+
],
|
|
798
|
+
"additionalProperties": false
|
|
799
|
+
}
|
|
642
800
|
}
|
|
643
801
|
},
|
|
644
802
|
"required": [
|
|
645
|
-
"permissions"
|
|
803
|
+
"permissions",
|
|
804
|
+
"resourceTypes"
|
|
646
805
|
],
|
|
647
806
|
"additionalProperties": false
|
|
648
807
|
}
|
|
@@ -1242,6 +1401,10 @@
|
|
|
1242
1401
|
}
|
|
1243
1402
|
]
|
|
1244
1403
|
},
|
|
1404
|
+
"image": {
|
|
1405
|
+
"type": "string",
|
|
1406
|
+
"description": "Avatar URL — the user's own picture, or the blobatar the IdP renders for them"
|
|
1407
|
+
},
|
|
1245
1408
|
"emailVerified": {
|
|
1246
1409
|
"type": "boolean"
|
|
1247
1410
|
},
|
|
@@ -1253,6 +1416,7 @@
|
|
|
1253
1416
|
"id",
|
|
1254
1417
|
"email",
|
|
1255
1418
|
"name",
|
|
1419
|
+
"image",
|
|
1256
1420
|
"emailVerified",
|
|
1257
1421
|
"createdAt"
|
|
1258
1422
|
],
|
|
@@ -2018,7 +2182,7 @@
|
|
|
2018
2182
|
},
|
|
2019
2183
|
"post": {
|
|
2020
2184
|
"summary": "Mint an end-user API key (plaintext returned once)",
|
|
2021
|
-
"description": "
|
|
2185
|
+
"description": "Every scope must be a declared permission or `<declared type>:<id>` for an instance the app currently lists. 422 `unknown_scopes` names scopes the catalog does not declare; 422 `unknown_resource` names `<type>:<id>` grants whose instance the app did not list; 502 `resource_lookup_failed` names the types whose `list` URL could not be read.",
|
|
2022
2186
|
"security": [
|
|
2023
2187
|
{
|
|
2024
2188
|
"bearerAuth": []
|
|
@@ -2053,7 +2217,7 @@
|
|
|
2053
2217
|
},
|
|
2054
2218
|
"scopes": {
|
|
2055
2219
|
"default": [],
|
|
2056
|
-
"description": "
|
|
2220
|
+
"description": "Declared permissions, or `<type>:<id>` grants over a declared resource type whose instance the app currently lists",
|
|
2057
2221
|
"type": "array",
|
|
2058
2222
|
"items": {
|
|
2059
2223
|
"type": "string"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@willyim/idp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Login for apps that don't own identity \u2014 OIDC client, server sessions, and react-router guards against the willy.im IdP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|