@willyim/idp 0.6.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 +61 -2
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/schemas/index.d.ts +61 -1
- package/dist/src/schemas/index.d.ts.map +1 -1
- package/dist/src/schemas/index.js +62 -3
- package/dist/src/schemas/operations.d.ts +27 -0
- package/dist/src/schemas/operations.d.ts.map +1 -1
- package/dist/src/schemas/operations.js +2 -0
- package/openapi/idp-api.json +131 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -215,7 +215,9 @@ type Session = {
|
|
|
215
215
|
}
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
-
`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)).
|
|
219
221
|
|
|
220
222
|
`image` is effectively always set against the willy.im IdP: it renders a
|
|
221
223
|
deterministic [blobatar](https://blobatar.dev) for anyone who never uploaded a
|
|
@@ -321,7 +323,9 @@ const keys = createUserKeys({
|
|
|
321
323
|
const minted = await keys.create({
|
|
322
324
|
userId: session.userId,
|
|
323
325
|
name: "cli",
|
|
324
|
-
|
|
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"],
|
|
325
329
|
workspaceId: session.workspaceId,
|
|
326
330
|
})
|
|
327
331
|
|
|
@@ -347,6 +351,61 @@ ingest token, say — identifies a site rather than a user, cannot be kept secre
|
|
|
347
351
|
and must not pay a round trip per hit. Keep those in the app's own table and
|
|
348
352
|
gate them on `Origin` plus rate limiting.
|
|
349
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
|
+
|
|
350
409
|
## Linked identities
|
|
351
410
|
|
|
352
411
|
A user's ids on *other* systems — their Slack member id, their WhatsApp number,
|
package/dist/src/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export { createIdp, DEFAULT_SESSION_COOKIE, publicSession, safeNext, type Idp, t
|
|
|
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,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;
|
|
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
|
@@ -17,6 +17,9 @@ export { createIdp, DEFAULT_SESSION_COOKIE, publicSession, safeNext, } from "./s
|
|
|
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";
|
|
@@ -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;
|
|
@@ -43,6 +84,11 @@ export declare const ApplicationListSchema: z.ZodObject<{
|
|
|
43
84
|
app: z.ZodNullable<z.ZodString>;
|
|
44
85
|
allowSignup: z.ZodBoolean;
|
|
45
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>>;
|
|
46
92
|
resources: z.ZodArray<z.ZodString>;
|
|
47
93
|
redirectUris: z.ZodArray<z.ZodString>;
|
|
48
94
|
disabled: z.ZodBoolean;
|
|
@@ -74,12 +120,26 @@ export declare const UpdateApplicationInput: z.ZodObject<{
|
|
|
74
120
|
export declare const ClientSecretSchema: z.ZodObject<{
|
|
75
121
|
clientSecret: z.ZodString;
|
|
76
122
|
}, z.core.$strip>;
|
|
77
|
-
/**
|
|
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
|
+
*/
|
|
78
128
|
export declare const SetAppPermissionsInput: z.ZodObject<{
|
|
79
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>>>;
|
|
80
135
|
}, z.core.$strip>;
|
|
81
136
|
export declare const AppPermissionsSchema: z.ZodObject<{
|
|
82
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>>;
|
|
83
143
|
}, z.core.$strip>;
|
|
84
144
|
export declare const UserListSchema: z.ZodObject<{
|
|
85
145
|
users: z.ZodArray<z.ZodObject<{
|
|
@@ -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"),
|
|
@@ -77,11 +125,19 @@ export const UpdateApplicationInput = z.object({
|
|
|
77
125
|
export const ClientSecretSchema = z.object({
|
|
78
126
|
clientSecret: z.string().describe("Plaintext client secret — shown exactly once, never stored"),
|
|
79
127
|
});
|
|
80
|
-
/**
|
|
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
|
+
*/
|
|
81
133
|
export const SetAppPermissionsInput = z.object({
|
|
82
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),
|
|
83
140
|
});
|
|
84
|
-
export const AppPermissionsSchema = z.object({ permissions: z.array(z.string()) });
|
|
85
141
|
export const UserListSchema = z.object({ users: z.array(UserSchema) });
|
|
86
142
|
export const WorkspaceListSchema = z.object({ workspaces: z.array(WorkspaceSchema) });
|
|
87
143
|
// --- Write management API (scoped-key authenticated, per-app) ---
|
|
@@ -138,7 +194,10 @@ export const UserApiKeyListSchema = z.object({ keys: z.array(UserApiKeySchema) }
|
|
|
138
194
|
export const CreateUserApiKeyInput = z.object({
|
|
139
195
|
userId: z.string().min(1),
|
|
140
196
|
name: z.string().min(1),
|
|
141
|
-
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"),
|
|
142
201
|
workspaceId: z.string().optional(),
|
|
143
202
|
expiresAt: z.iso.datetime().optional().describe("ISO 8601; omit for non-expiring"),
|
|
144
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": {
|
|
@@ -421,6 +447,7 @@ export declare const operations: {
|
|
|
421
447
|
};
|
|
422
448
|
readonly "post /api/v1/apps/{app}/user-keys": {
|
|
423
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.";
|
|
424
451
|
readonly permission: "userkey:create";
|
|
425
452
|
readonly params: {
|
|
426
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/openapi/idp-api.json
CHANGED
|
@@ -78,6 +78,30 @@
|
|
|
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
|
+
},
|
|
81
105
|
"resources": {
|
|
82
106
|
"type": "array",
|
|
83
107
|
"items": {
|
|
@@ -105,6 +129,7 @@
|
|
|
105
129
|
"app",
|
|
106
130
|
"allowSignup",
|
|
107
131
|
"permissions",
|
|
132
|
+
"resourceTypes",
|
|
108
133
|
"resources",
|
|
109
134
|
"redirectUris",
|
|
110
135
|
"disabled",
|
|
@@ -291,6 +316,30 @@
|
|
|
291
316
|
},
|
|
292
317
|
"description": "The app's declared product-permission catalog"
|
|
293
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
|
+
},
|
|
294
343
|
"resources": {
|
|
295
344
|
"type": "array",
|
|
296
345
|
"items": {
|
|
@@ -318,6 +367,7 @@
|
|
|
318
367
|
"app",
|
|
319
368
|
"allowSignup",
|
|
320
369
|
"permissions",
|
|
370
|
+
"resourceTypes",
|
|
321
371
|
"resources",
|
|
322
372
|
"redirectUris",
|
|
323
373
|
"disabled",
|
|
@@ -438,6 +488,30 @@
|
|
|
438
488
|
},
|
|
439
489
|
"description": "The app's declared product-permission catalog"
|
|
440
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
|
+
},
|
|
441
515
|
"resources": {
|
|
442
516
|
"type": "array",
|
|
443
517
|
"items": {
|
|
@@ -465,6 +539,7 @@
|
|
|
465
539
|
"app",
|
|
466
540
|
"allowSignup",
|
|
467
541
|
"permissions",
|
|
542
|
+
"resourceTypes",
|
|
468
543
|
"resources",
|
|
469
544
|
"redirectUris",
|
|
470
545
|
"disabled",
|
|
@@ -617,7 +692,7 @@
|
|
|
617
692
|
"/api/v1/apps/{app}/permissions": {
|
|
618
693
|
"put": {
|
|
619
694
|
"summary": "Replace the app's product-permission catalog",
|
|
620
|
-
"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).",
|
|
621
696
|
"security": [
|
|
622
697
|
{
|
|
623
698
|
"bearerAuth": []
|
|
@@ -648,6 +723,34 @@
|
|
|
648
723
|
"type": "string",
|
|
649
724
|
"minLength": 1
|
|
650
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
|
+
}
|
|
651
754
|
}
|
|
652
755
|
},
|
|
653
756
|
"required": [
|
|
@@ -671,10 +774,34 @@
|
|
|
671
774
|
"items": {
|
|
672
775
|
"type": "string"
|
|
673
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
|
+
}
|
|
674
800
|
}
|
|
675
801
|
},
|
|
676
802
|
"required": [
|
|
677
|
-
"permissions"
|
|
803
|
+
"permissions",
|
|
804
|
+
"resourceTypes"
|
|
678
805
|
],
|
|
679
806
|
"additionalProperties": false
|
|
680
807
|
}
|
|
@@ -2055,7 +2182,7 @@
|
|
|
2055
2182
|
},
|
|
2056
2183
|
"post": {
|
|
2057
2184
|
"summary": "Mint an end-user API key (plaintext returned once)",
|
|
2058
|
-
"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.",
|
|
2059
2186
|
"security": [
|
|
2060
2187
|
{
|
|
2061
2188
|
"bearerAuth": []
|
|
@@ -2090,7 +2217,7 @@
|
|
|
2090
2217
|
},
|
|
2091
2218
|
"scopes": {
|
|
2092
2219
|
"default": [],
|
|
2093
|
-
"description": "
|
|
2220
|
+
"description": "Declared permissions, or `<type>:<id>` grants over a declared resource type whose instance the app currently lists",
|
|
2094
2221
|
"type": "array",
|
|
2095
2222
|
"items": {
|
|
2096
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",
|