@willyim/idp 0.3.0 → 0.4.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 +57 -3
- package/dist/src/api.d.ts +1 -1
- package/dist/src/api.d.ts.map +1 -1
- package/dist/src/identities.d.ts +59 -0
- package/dist/src/identities.d.ts.map +1 -0
- package/dist/src/identities.js +0 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/schemas/index.d.ts +36 -0
- package/dist/src/schemas/index.d.ts.map +1 -1
- package/dist/src/schemas/index.js +31 -0
- package/dist/src/schemas/openapi.js +4 -4
- package/dist/src/schemas/operations.d.ts +71 -4
- package/dist/src/schemas/operations.d.ts.map +1 -1
- package/dist/src/schemas/operations.js +35 -5
- package/openapi/idp-api.json +381 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -323,6 +323,55 @@ ingest token, say — identifies a site rather than a user, cannot be kept secre
|
|
|
323
323
|
and must not pay a round trip per hit. Keep those in the app's own table and
|
|
324
324
|
gate them on `Origin` plus rate limiting.
|
|
325
325
|
|
|
326
|
+
## Linked identities
|
|
327
|
+
|
|
328
|
+
A user's ids on *other* systems — their Slack member id, their WhatsApp number,
|
|
329
|
+
a Telegram id — pinned to their IdP user. The point is that an app hearing from
|
|
330
|
+
someone on Slack asks the IdP "who is this, and what may they do here?" and gets
|
|
331
|
+
the same answer a browser session for that person would carry. The app keeps no
|
|
332
|
+
table of Slack ids: the one it would write is exactly the allowlist the IdP
|
|
333
|
+
exists to replace.
|
|
334
|
+
|
|
335
|
+
Linking is **superadmin-only** — a link asserts identity with nothing to prove
|
|
336
|
+
it, so no app and no member may do it:
|
|
337
|
+
|
|
338
|
+
```sh
|
|
339
|
+
curl -X POST https://idp.willy.im/api/v1/users/<userId>/identities \
|
|
340
|
+
-H "authorization: Bearer wim_<admin key>" -H "content-type: application/json" \
|
|
341
|
+
-d '{"provider":"slack","externalId":"U0AAE7LAATD","label":"house workspace"}'
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Resolving is app-scoped and needs `identity:resolve` on the app's own key:
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
import { createIdentities, grants } from "@willyim/idp"
|
|
348
|
+
|
|
349
|
+
const identities = createIdentities({
|
|
350
|
+
baseUrl: "https://idp.willy.im",
|
|
351
|
+
token: env.IDP_MANAGEMENT_KEY, // the app's wim_… key, with identity:resolve
|
|
352
|
+
app: "bender",
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
// On every inbound Slack message:
|
|
356
|
+
const who = await identities.resolve("slack", event.user)
|
|
357
|
+
if (!who.found) return // store it, do not answer
|
|
358
|
+
if (!grants(who.permissions, "chat:respond")) return // they exist, this app never granted them
|
|
359
|
+
who.userId // the IdP user — the same id a session or a wak_ key would carry
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
`permissions` are the user's product permissions for **the asking app**,
|
|
363
|
+
computed by the same code the claims hook runs at token mint: an admin member
|
|
364
|
+
gets the whole catalog, a plain member gets their grants, a linked user with no
|
|
365
|
+
membership resolves as `found: true` with none. `found: false` is a miss, not an
|
|
366
|
+
error, and is the common case in any shared channel.
|
|
367
|
+
|
|
368
|
+
Verdicts are cached by `(provider, externalId)` — 60s for a hit and for a miss,
|
|
369
|
+
both tunable via `cache` — and concurrent lookups of the same pair share one
|
|
370
|
+
round trip. A failed round trip is never cached. The miss TTL bounds how fast a
|
|
371
|
+
*new* link takes effect; call `forget(provider, externalId)` after one you made
|
|
372
|
+
yourself. The provider is case-insensitive; the id is exact, as the other
|
|
373
|
+
system spells it.
|
|
374
|
+
|
|
326
375
|
## Management API types
|
|
327
376
|
|
|
328
377
|
Endpoints without sugar of their own go through `createManagementApi`, whose
|
|
@@ -358,9 +407,14 @@ const { token } = await api.request("post", "/api/v1/admin-keys", {
|
|
|
358
407
|
// Authorization: Bearer wim_…
|
|
359
408
|
```
|
|
360
409
|
|
|
361
|
-
|
|
362
|
-
it
|
|
363
|
-
|
|
410
|
+
There is no static superadmin secret: every bearer the IdP accepts is a key row
|
|
411
|
+
it issued, so every superadmin action names a revocable credential.
|
|
412
|
+
|
|
413
|
+
**Break-glass.** If every admin key is lost, recover by writing one bootstrap
|
|
414
|
+
key straight into D1 — insert an `api_key` row with `application_id` NULL and
|
|
415
|
+
`key_hash` set to the SHA-256 hex digest of a token you generate — then use it
|
|
416
|
+
to mint a real key via `POST /api/v1/admin-keys` and revoke the bootstrap row
|
|
417
|
+
through `DELETE /api/v1/admin-keys/{id}`.
|
|
364
418
|
|
|
365
419
|
The OIDC endpoints are not in that document and never will be: they are
|
|
366
420
|
standards-defined and discovered at runtime from `.well-known`.
|
package/dist/src/api.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export type RequestOptions<P extends string, O> = PathParams<P> & RequestBody<O>
|
|
|
42
42
|
export type ManagementApiOptions = {
|
|
43
43
|
/** IdP origin — the API lives at the root, not under the `/auth` basepath. */
|
|
44
44
|
baseUrl: string;
|
|
45
|
-
/**
|
|
45
|
+
/** An IdP-level admin key or a per-app scoped `wim_…` key. */
|
|
46
46
|
token: string;
|
|
47
47
|
fetch?: typeof fetch;
|
|
48
48
|
};
|
package/dist/src/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAG5B,OAAO,EAEL,KAAK,UAAU,EAEf,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,QAAQ,EACd,MAAM,yBAAyB,CAAA;AAGhC,MAAM,MAAM,MAAM,GAAG,UAAU,CAAA;AAC/B,YAAY,EAAE,QAAQ,EAAE,CAAA;AAExB,6EAA6E;AAC7E,KAAK,UAAU,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GACnE;IAAE,MAAM,CAAC,EAAE,SAAS,CAAA;CAAE,GACtB;IAAE,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;CAAE,CAAA;AAEjD,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;CAAE,GACpB;IAAE,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,CAAA;AAExB,mEAAmE;AACnE,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAA;CAAE,GACzE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GACX,KAAK,CAAA;AAET,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,GAC7D,WAAW,CAAC,CAAC,CAAC,GAAG;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAA;IAC7D,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,CAAA;AAEH,MAAM,MAAM,oBAAoB,GAAG;IACjC,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAA;IACf,
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAG5B,OAAO,EAEL,KAAK,UAAU,EAEf,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,QAAQ,EACd,MAAM,yBAAyB,CAAA;AAGhC,MAAM,MAAM,MAAM,GAAG,UAAU,CAAA;AAC/B,YAAY,EAAE,QAAQ,EAAE,CAAA;AAExB,6EAA6E;AAC7E,KAAK,UAAU,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GACnE;IAAE,MAAM,CAAC,EAAE,SAAS,CAAA;CAAE,GACtB;IAAE,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;CAAE,CAAA;AAEjD,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;CAAE,GACpB;IAAE,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,CAAA;AAExB,mEAAmE;AACnE,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAA;CAAE,GACzE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GACX,KAAK,CAAA;AAET,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,GAC7D,WAAW,CAAC,CAAC,CAAC,GAAG;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAA;IAC7D,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,CAAA;AAEH,MAAM,MAAM,oBAAoB,GAAG;IACjC,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CACrB,CAAA;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB;YAK/C,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,UAC3C,CAAC,QACH,CAAC,SACD,cAAc,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAC1C,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;EAuC9C;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linked identities, from the consuming app's side: "someone just messaged me
|
|
3
|
+
* on Slack as U0AAE7LAATD — who is that, and what may they do here?"
|
|
4
|
+
*
|
|
5
|
+
* The IdP is the only place the answer lives. A user's ids on other systems
|
|
6
|
+
* are pinned to their IdP user by a superadmin (the management API's
|
|
7
|
+
* `/users/{userId}/identities`), and an app resolves them with its own scoped
|
|
8
|
+
* `wim_…` key. The app never keeps a table of Slack ids — the one it would
|
|
9
|
+
* write is exactly the allowlist the IdP exists to replace.
|
|
10
|
+
*
|
|
11
|
+
* `permissions` in the answer are the user's product permissions for THIS app,
|
|
12
|
+
* computed the same way the claims hook computes them at token mint, so a
|
|
13
|
+
* Slack message and a browser session from the same person carry the same
|
|
14
|
+
* grants. Enforcement stays the app's job, same as everywhere else; `grants()`
|
|
15
|
+
* from `./claims.js` is the matcher to use.
|
|
16
|
+
*
|
|
17
|
+
* Resolution is a network round trip on a hot path — every inbound chat
|
|
18
|
+
* message — so verdicts are cached by (provider, externalId) with a short TTL,
|
|
19
|
+
* and concurrent lookups of the same pair share one request. A miss is cached
|
|
20
|
+
* too (most messages in any shared channel are from people who are not
|
|
21
|
+
* linked), which bounds how quickly a NEW link takes effect: `cache.missTtlMs`.
|
|
22
|
+
* A failed round trip is never cached, so an IdP blip does not lock anyone out
|
|
23
|
+
* for the whole TTL. `forget()` after a link you performed yourself.
|
|
24
|
+
*/
|
|
25
|
+
import type { z } from "zod";
|
|
26
|
+
import { type ManagementApiOptions } from "./api.js";
|
|
27
|
+
import type { IdentityResolutionSchema } from "./schemas/index.js";
|
|
28
|
+
/** The IdP's answer. `found: false` is data, not an error. */
|
|
29
|
+
export type IdentityResolution = z.output<typeof IdentityResolutionSchema>;
|
|
30
|
+
/** The `found: true` half. */
|
|
31
|
+
export type ResolvedIdentity = Extract<IdentityResolution, {
|
|
32
|
+
found: true;
|
|
33
|
+
}>;
|
|
34
|
+
export type IdentityCacheOptions = {
|
|
35
|
+
/** How long a `found: true` verdict is reused. Default 60s. */
|
|
36
|
+
ttlMs?: number;
|
|
37
|
+
/** How long a `found: false` verdict is reused. Default 60s — see the header. */
|
|
38
|
+
missTtlMs?: number;
|
|
39
|
+
/** Entry ceiling before the oldest are dropped. Default 1000. */
|
|
40
|
+
max?: number;
|
|
41
|
+
};
|
|
42
|
+
export type IdentitiesOptions = ManagementApiOptions & {
|
|
43
|
+
/** The app the permissions in each answer are scoped to. */
|
|
44
|
+
app: string;
|
|
45
|
+
/** `false` disables caching entirely (every resolve is a round trip). */
|
|
46
|
+
cache?: IdentityCacheOptions | false;
|
|
47
|
+
/** Clock seam, for tests. */
|
|
48
|
+
now?: () => number;
|
|
49
|
+
};
|
|
50
|
+
export declare function createIdentities(options: IdentitiesOptions): {
|
|
51
|
+
resolve: (provider: string, externalId: string, init?: {
|
|
52
|
+
signal?: AbortSignal;
|
|
53
|
+
fresh?: boolean;
|
|
54
|
+
}) => Promise<IdentityResolution>;
|
|
55
|
+
/** Drops one pair's cached verdict, or the whole cache when called bare. */
|
|
56
|
+
forget(provider?: string, externalId?: string): void;
|
|
57
|
+
};
|
|
58
|
+
export type Identities = ReturnType<typeof createIdentities>;
|
|
59
|
+
//# sourceMappingURL=identities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identities.d.ts","sourceRoot":"","sources":["../../src/identities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAE5B,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAA;AACzE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AAElE,8DAA8D;AAC9D,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,wBAAwB,CAAC,CAAA;AAE1E,8BAA8B;AAC9B,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,kBAAkB,EAAE;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,CAAC,CAAA;AAE3E,MAAM,MAAM,oBAAoB,GAAG;IACjC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,GAAG;IACrD,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAA;IACX,yEAAyE;IACzE,KAAK,CAAC,EAAE,oBAAoB,GAAG,KAAK,CAAA;IACpC,6BAA6B;IAC7B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAQD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB;wBAiC7C,MAAM,cACJ,MAAM,SACZ;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,KAC9C,OAAO,CAAC,kBAAkB,CAAC;IAgC5B,4EAA4E;sBAC1D,MAAM,eAAe,MAAM,GAAG,IAAI;EAQvD;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAA"}
|
|
Binary file
|
package/dist/src/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export { memorySessions, type MemorySessionStore, type SessionRecord, type Sessi
|
|
|
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
20
|
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
|
+
export { createIdentities, type Identities, type IdentitiesOptions, type IdentityCacheOptions, type ResolvedIdentity, type IdentityResolution, } from "./identities.js";
|
|
21
22
|
export { parseDuration, type Duration } from "./duration.js";
|
|
22
23
|
export { clearCookie, parseCookies, readCookie, serializeCookie, type CookieOptions, } from "./cookie.js";
|
|
23
24
|
//# sourceMappingURL=index.d.ts.map
|
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;AAEjB,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,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"}
|
|
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;AAEjB,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,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
|
@@ -18,5 +18,6 @@ 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
20
|
export { createUserKeys, readApiKey, } from "./user-keys.js";
|
|
21
|
+
export { createIdentities, } from "./identities.js";
|
|
21
22
|
export { parseDuration } from "./duration.js";
|
|
22
23
|
export { clearCookie, parseCookies, readCookie, serializeCookie, } from "./cookie.js";
|
|
@@ -219,6 +219,42 @@ export declare const UserApiKeyValidationSchema: z.ZodUnion<readonly [z.ZodObjec
|
|
|
219
219
|
not_found: "not_found";
|
|
220
220
|
}>;
|
|
221
221
|
}, z.core.$strip>]>;
|
|
222
|
+
export declare const LinkedIdentitySchema: z.ZodObject<{
|
|
223
|
+
id: z.ZodString;
|
|
224
|
+
userId: z.ZodString;
|
|
225
|
+
provider: z.ZodString;
|
|
226
|
+
externalId: z.ZodString;
|
|
227
|
+
label: z.ZodNullable<z.ZodString>;
|
|
228
|
+
createdAt: z.ZodString;
|
|
229
|
+
}, z.core.$strip>;
|
|
230
|
+
export declare const LinkedIdentityListSchema: z.ZodObject<{
|
|
231
|
+
identities: z.ZodArray<z.ZodObject<{
|
|
232
|
+
id: z.ZodString;
|
|
233
|
+
userId: z.ZodString;
|
|
234
|
+
provider: z.ZodString;
|
|
235
|
+
externalId: z.ZodString;
|
|
236
|
+
label: z.ZodNullable<z.ZodString>;
|
|
237
|
+
createdAt: z.ZodString;
|
|
238
|
+
}, z.core.$strip>>;
|
|
239
|
+
}, z.core.$strip>;
|
|
240
|
+
export declare const LinkIdentityInput: z.ZodObject<{
|
|
241
|
+
provider: z.ZodString;
|
|
242
|
+
externalId: z.ZodString;
|
|
243
|
+
label: z.ZodOptional<z.ZodString>;
|
|
244
|
+
}, z.core.$strip>;
|
|
245
|
+
export declare const LinkedIdentityCreatedSchema: z.ZodObject<{
|
|
246
|
+
id: z.ZodString;
|
|
247
|
+
created: z.ZodBoolean;
|
|
248
|
+
}, z.core.$strip>;
|
|
249
|
+
export declare const IdentityResolutionSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
250
|
+
found: z.ZodLiteral<true>;
|
|
251
|
+
userId: z.ZodString;
|
|
252
|
+
email: z.ZodString;
|
|
253
|
+
name: z.ZodNullable<z.ZodString>;
|
|
254
|
+
permissions: z.ZodArray<z.ZodString>;
|
|
255
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
256
|
+
found: z.ZodLiteral<false>;
|
|
257
|
+
}, z.core.$strip>]>;
|
|
222
258
|
export declare const AuditEntrySchema: z.ZodObject<{
|
|
223
259
|
id: z.ZodNumber;
|
|
224
260
|
tableName: z.ZodString;
|
|
@@ -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;AAEvB,eAAO,MAAM,iBAAiB;;;;;;;;;iBAS5B,CAAA;AAEF,eAAO,MAAM,UAAU;;;;;;iBAMrB,CAAA;AAEF,eAAO,MAAM,eAAe;;;;;;iBAM1B,CAAA;AAEF,eAAO,MAAM,qBAAqB;;;;;;;;;;;iBAAyD,CAAA;AAE3F;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;;iBAejC,CAAA;AACF,eAAO,MAAM,wBAAwB;;;;iBAInC,CAAA;AAEF,uEAAuE;AACvE,eAAO,MAAM,sBAAsB;;;;iBAIjC,CAAA;AAEF,eAAO,MAAM,kBAAkB;;iBAE7B,CAAA;AAEF,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB;;iBAEjC,CAAA;AACF,eAAO,MAAM,oBAAoB;;iBAAiD,CAAA;AAClF,eAAO,MAAM,cAAc;;;;;;;;iBAA2C,CAAA;AACtE,eAAO,MAAM,mBAAmB;;;;;;;;iBAAqD,CAAA;AAIrF,eAAO,MAAM,UAAU;;;EAA8B,CAAA;AAErD,eAAO,MAAM,YAAY;;;;;;;;;iBAMvB,CAAA;AACF,eAAO,MAAM,gBAAgB;;;;;;;;;;;iBAA+C,CAAA;AAE5E,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB;;;;;;;iBAI5B,CAAA;AACF,eAAO,MAAM,kBAAkB;;;;;iBAG7B,CAAA;AAEF,eAAO,MAAM,iBAAiB;;;;;;iBAG5B,CAAA;AAEF,eAAO,MAAM,oBAAoB;;;iBAM/B,CAAA;AACF,eAAO,MAAM,sBAAsB;;;;iBAIjC,CAAA;AAEF,eAAO,MAAM,QAAQ;;iBAAoC,CAAA;AAIzD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;iBAW3B,CAAA;AACF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;iBAAgD,CAAA;AAEjF,eAAO,MAAM,qBAAqB;;;;;;iBAMhC,CAAA;AACF,eAAO,MAAM,uBAAuB;;;;iBAIlC,CAAA;AAEF,eAAO,MAAM,uBAAuB;;iBAAyC,CAAA;AAC7E,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;mBAUrC,CAAA;AAEF,eAAO,MAAM,gBAAgB;;;;;;;;iBAQ3B,CAAA;AACF,eAAO,MAAM,eAAe;;;;;;;;;;iBAAmD,CAAA;AAI/E,eAAO,MAAM,YAAY;;;;;;;;;;;;;;iBAUvB,CAAA;AACF,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;iBAA4C,CAAA;AAEzE,eAAO,MAAM,iBAAiB;;;;iBAM5B,CAAA;AACF,eAAO,MAAM,mBAAmB;;;;iBAI9B,CAAA;AAIF,eAAO,MAAM,cAAc;;;;;;;;;;;;;iBASzB,CAAA;AACF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;iBAA8C,CAAA;AAE7E,eAAO,MAAM,mBAAmB;;;iBAG9B,CAAA;AACF,eAAO,MAAM,qBAAqB;;;;iBAIhC,CAAA"}
|
|
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;AAEvB,eAAO,MAAM,iBAAiB;;;;;;;;;iBAS5B,CAAA;AAEF,eAAO,MAAM,UAAU;;;;;;iBAMrB,CAAA;AAEF,eAAO,MAAM,eAAe;;;;;;iBAM1B,CAAA;AAEF,eAAO,MAAM,qBAAqB;;;;;;;;;;;iBAAyD,CAAA;AAE3F;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;;iBAejC,CAAA;AACF,eAAO,MAAM,wBAAwB;;;;iBAInC,CAAA;AAEF,uEAAuE;AACvE,eAAO,MAAM,sBAAsB;;;;iBAIjC,CAAA;AAEF,eAAO,MAAM,kBAAkB;;iBAE7B,CAAA;AAEF,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB;;iBAEjC,CAAA;AACF,eAAO,MAAM,oBAAoB;;iBAAiD,CAAA;AAClF,eAAO,MAAM,cAAc;;;;;;;;iBAA2C,CAAA;AACtE,eAAO,MAAM,mBAAmB;;;;;;;;iBAAqD,CAAA;AAIrF,eAAO,MAAM,UAAU;;;EAA8B,CAAA;AAErD,eAAO,MAAM,YAAY;;;;;;;;;iBAMvB,CAAA;AACF,eAAO,MAAM,gBAAgB;;;;;;;;;;;iBAA+C,CAAA;AAE5E,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB;;;;;;;iBAI5B,CAAA;AACF,eAAO,MAAM,kBAAkB;;;;;iBAG7B,CAAA;AAEF,eAAO,MAAM,iBAAiB;;;;;;iBAG5B,CAAA;AAEF,eAAO,MAAM,oBAAoB;;;iBAM/B,CAAA;AACF,eAAO,MAAM,sBAAsB;;;;iBAIjC,CAAA;AAEF,eAAO,MAAM,QAAQ;;iBAAoC,CAAA;AAIzD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;iBAW3B,CAAA;AACF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;iBAAgD,CAAA;AAEjF,eAAO,MAAM,qBAAqB;;;;;;iBAMhC,CAAA;AACF,eAAO,MAAM,uBAAuB;;;;iBAIlC,CAAA;AAEF,eAAO,MAAM,uBAAuB;;iBAAyC,CAAA;AAC7E,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;mBAUrC,CAAA;AAIF,eAAO,MAAM,oBAAoB;;;;;;;iBAO/B,CAAA;AACF,eAAO,MAAM,wBAAwB;;;;;;;;;iBAA0D,CAAA;AAE/F,eAAO,MAAM,iBAAiB;;;;iBAI5B,CAAA;AACF,eAAO,MAAM,2BAA2B;;;iBAGtC,CAAA;AAEF,eAAO,MAAM,wBAAwB;;;;;;;;mBAWnC,CAAA;AAEF,eAAO,MAAM,gBAAgB;;;;;;;;iBAQ3B,CAAA;AACF,eAAO,MAAM,eAAe;;;;;;;;;;iBAAmD,CAAA;AAI/E,eAAO,MAAM,YAAY;;;;;;;;;;;;;;iBAUvB,CAAA;AACF,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;iBAA4C,CAAA;AAEzE,eAAO,MAAM,iBAAiB;;;;iBAM5B,CAAA;AACF,eAAO,MAAM,mBAAmB;;;;iBAI9B,CAAA;AAIF,eAAO,MAAM,cAAc;;;;;;;;;;;;;iBASzB,CAAA;AACF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;iBAA8C,CAAA;AAE7E,eAAO,MAAM,mBAAmB;;;iBAG9B,CAAA;AACF,eAAO,MAAM,qBAAqB;;;;iBAIhC,CAAA"}
|
|
@@ -149,6 +149,37 @@ export const UserApiKeyValidationSchema = z.union([
|
|
|
149
149
|
}),
|
|
150
150
|
z.object({ valid: z.literal(false), reason: z.enum(["not_found", "revoked", "expired"]) }),
|
|
151
151
|
]);
|
|
152
|
+
// --- Linked identities (a user's ids on other systems) ---
|
|
153
|
+
export const LinkedIdentitySchema = z.object({
|
|
154
|
+
id: z.string(),
|
|
155
|
+
userId: z.string(),
|
|
156
|
+
provider: z.string().describe("The other system, lowercase — slack, whatsapp, telegram"),
|
|
157
|
+
externalId: z.string().describe("The id exactly as that system spells it"),
|
|
158
|
+
label: z.string().nullable(),
|
|
159
|
+
createdAt: z.string(),
|
|
160
|
+
});
|
|
161
|
+
export const LinkedIdentityListSchema = z.object({ identities: z.array(LinkedIdentitySchema) });
|
|
162
|
+
export const LinkIdentityInput = z.object({
|
|
163
|
+
provider: z.string().min(1).describe("slack, whatsapp, telegram… — normalised to lowercase"),
|
|
164
|
+
externalId: z.string().min(1).describe("The id as that system spells it, e.g. a Slack member id"),
|
|
165
|
+
label: z.string().optional().describe("A human label for the console"),
|
|
166
|
+
});
|
|
167
|
+
export const LinkedIdentityCreatedSchema = z.object({
|
|
168
|
+
id: z.string(),
|
|
169
|
+
created: z.boolean().describe("false when the same pair was already this user's"),
|
|
170
|
+
});
|
|
171
|
+
export const IdentityResolutionSchema = z.union([
|
|
172
|
+
z.object({
|
|
173
|
+
found: z.literal(true),
|
|
174
|
+
userId: z.string(),
|
|
175
|
+
email: z.string(),
|
|
176
|
+
name: z.string().nullable(),
|
|
177
|
+
permissions: z
|
|
178
|
+
.array(z.string())
|
|
179
|
+
.describe("The user's product permissions for the asking app; admins get the whole catalog"),
|
|
180
|
+
}),
|
|
181
|
+
z.object({ found: z.literal(false) }),
|
|
182
|
+
]);
|
|
152
183
|
export const AuditEntrySchema = z.object({
|
|
153
184
|
id: z.number(),
|
|
154
185
|
tableName: z.string(),
|
|
@@ -16,7 +16,7 @@ import { lookup, operations } from "./operations.js";
|
|
|
16
16
|
*/
|
|
17
17
|
const json = (schema) => z.toJSONSchema(schema);
|
|
18
18
|
const jsonInput = (schema) => z.toJSONSchema(schema, { io: "input" });
|
|
19
|
-
const DESCRIPTION = "Management API for the willy.im identity provider. Authenticate with `Authorization: Bearer <token>`. Two kinds of
|
|
19
|
+
const DESCRIPTION = "Management API for the willy.im identity provider. Authenticate with `Authorization: Bearer <token>`. Two kinds of bearer, both `wim_` keys: IdP-level **admin keys** (every permission on every app, minted at `/api/v1/admin-keys`) and per-app **scoped keys** minted in the admin console (one app, a fixed permission set). The cross-app endpoints below require an admin key.";
|
|
20
20
|
function pathParamNames(path) {
|
|
21
21
|
return [...path.matchAll(/\{([^}]+)\}/g)].map((match) => match[1]);
|
|
22
22
|
}
|
|
@@ -45,8 +45,8 @@ function operationObject(method, path, def) {
|
|
|
45
45
|
summary: def.summary,
|
|
46
46
|
description: def.description ??
|
|
47
47
|
(scoped
|
|
48
|
-
? `Requires \`${def.permission}\` on the path app (or
|
|
49
|
-
: "Requires
|
|
48
|
+
? `Requires \`${def.permission}\` on the path app (or an admin key).`
|
|
49
|
+
: "Requires an admin key."),
|
|
50
50
|
security: [{ bearerAuth: [] }],
|
|
51
51
|
...(parameters.length ? { parameters } : {}),
|
|
52
52
|
...(def.input
|
|
@@ -102,7 +102,7 @@ export function buildOpenApiDocument(input) {
|
|
|
102
102
|
bearerAuth: {
|
|
103
103
|
type: "http",
|
|
104
104
|
scheme: "bearer",
|
|
105
|
-
description: "
|
|
105
|
+
description: "An IdP-level admin key or a per-app scoped key (wim_…).",
|
|
106
106
|
},
|
|
107
107
|
},
|
|
108
108
|
},
|
|
@@ -51,7 +51,7 @@ export declare const operations: {
|
|
|
51
51
|
};
|
|
52
52
|
readonly "post /api/v1/applications": {
|
|
53
53
|
readonly summary: "Register an application (client secret returned once)";
|
|
54
|
-
readonly description: "Requires
|
|
54
|
+
readonly description: "Requires an admin key. Creating an application is an IdP-level act — there is no app to scope a permission to yet.";
|
|
55
55
|
readonly input: z.ZodObject<{
|
|
56
56
|
name: z.ZodString;
|
|
57
57
|
app: z.ZodString;
|
|
@@ -174,7 +174,7 @@ export declare const operations: {
|
|
|
174
174
|
};
|
|
175
175
|
readonly "post /api/v1/apps/{app}/keys": {
|
|
176
176
|
readonly summary: "Mint a scoped management API key (plaintext returned once)";
|
|
177
|
-
readonly description: "Requires `apikey:create` on the path app (or
|
|
177
|
+
readonly description: "Requires `apikey:create` on the path app (or an admin key). The requested permissions must be a subset of the caller's own, otherwise 403 `permissions_exceed_caller` — without that rule any key holding `apikey:create` could mint itself a more powerful successor.";
|
|
178
178
|
readonly permission: "apikey:create";
|
|
179
179
|
readonly params: {
|
|
180
180
|
readonly app: "Application key (oauth_client.metadata.app).";
|
|
@@ -205,7 +205,7 @@ export declare const operations: {
|
|
|
205
205
|
};
|
|
206
206
|
readonly "get /api/v1/admin-keys": {
|
|
207
207
|
readonly summary: "List IdP-level admin keys (never the hashes)";
|
|
208
|
-
readonly description: "Requires
|
|
208
|
+
readonly description: "Requires an admin key. Admin keys are `api_key` rows with no application scope, so they hold every permission on every app.";
|
|
209
209
|
readonly successCode: "200";
|
|
210
210
|
readonly success: z.ZodObject<{
|
|
211
211
|
keys: z.ZodArray<z.ZodObject<{
|
|
@@ -226,7 +226,7 @@ export declare const operations: {
|
|
|
226
226
|
};
|
|
227
227
|
readonly "post /api/v1/admin-keys": {
|
|
228
228
|
readonly summary: "Mint an IdP-level admin key (plaintext returned once)";
|
|
229
|
-
readonly description: "Requires
|
|
229
|
+
readonly description: "Requires an admin key. Mint one per agent: an admin key has a name, an optional expiry, a revoke switch, and its own `adminkey:<id>` identity in the audit log, so every superadmin action is attributable.";
|
|
230
230
|
readonly input: z.ZodObject<{
|
|
231
231
|
name: z.ZodString;
|
|
232
232
|
expiresAt: z.ZodOptional<z.ZodISODateTime>;
|
|
@@ -471,6 +471,73 @@ export declare const operations: {
|
|
|
471
471
|
ok: z.ZodLiteral<true>;
|
|
472
472
|
}, z.core.$strip>;
|
|
473
473
|
};
|
|
474
|
+
readonly "get /api/v1/users/{userId}/identities": {
|
|
475
|
+
readonly summary: "List a user's linked identities (their ids on other systems)";
|
|
476
|
+
readonly description: "Requires an admin key. Identities are global to the user, not per app — a Slack id identifies a person regardless of who is asking.";
|
|
477
|
+
readonly params: {
|
|
478
|
+
readonly userId: "IdP user id.";
|
|
479
|
+
};
|
|
480
|
+
readonly successCode: "200";
|
|
481
|
+
readonly success: z.ZodObject<{
|
|
482
|
+
identities: z.ZodArray<z.ZodObject<{
|
|
483
|
+
id: z.ZodString;
|
|
484
|
+
userId: z.ZodString;
|
|
485
|
+
provider: z.ZodString;
|
|
486
|
+
externalId: z.ZodString;
|
|
487
|
+
label: z.ZodNullable<z.ZodString>;
|
|
488
|
+
createdAt: z.ZodString;
|
|
489
|
+
}, z.core.$strip>>;
|
|
490
|
+
}, z.core.$strip>;
|
|
491
|
+
};
|
|
492
|
+
readonly "post /api/v1/users/{userId}/identities": {
|
|
493
|
+
readonly summary: "Link an external id to a user";
|
|
494
|
+
readonly description: "Requires an admin key: a link asserts identity with nothing to prove it, so no app or member may do it. 201 on a new link, 200 when the same pair was already this user's, 409 `already_linked` when it belongs to someone else — an identity is never silently re-pointed.";
|
|
495
|
+
readonly params: {
|
|
496
|
+
readonly userId: "IdP user id.";
|
|
497
|
+
};
|
|
498
|
+
readonly input: z.ZodObject<{
|
|
499
|
+
provider: z.ZodString;
|
|
500
|
+
externalId: z.ZodString;
|
|
501
|
+
label: z.ZodOptional<z.ZodString>;
|
|
502
|
+
}, z.core.$strip>;
|
|
503
|
+
readonly successCode: "201";
|
|
504
|
+
readonly success: z.ZodObject<{
|
|
505
|
+
id: z.ZodString;
|
|
506
|
+
created: z.ZodBoolean;
|
|
507
|
+
}, z.core.$strip>;
|
|
508
|
+
};
|
|
509
|
+
readonly "delete /api/v1/users/{userId}/identities/{id}": {
|
|
510
|
+
readonly summary: "Unlink an external id (idempotent)";
|
|
511
|
+
readonly description: "Requires an admin key.";
|
|
512
|
+
readonly params: {
|
|
513
|
+
readonly userId: "IdP user id.";
|
|
514
|
+
readonly id: "Linked identity id.";
|
|
515
|
+
};
|
|
516
|
+
readonly successCode: "200";
|
|
517
|
+
readonly success: z.ZodObject<{
|
|
518
|
+
ok: z.ZodLiteral<true>;
|
|
519
|
+
}, z.core.$strip>;
|
|
520
|
+
};
|
|
521
|
+
readonly "get /api/v1/apps/{app}/identities/{provider}/{externalId}": {
|
|
522
|
+
readonly summary: "Resolve an external id to a user and their permissions in this app";
|
|
523
|
+
readonly description: "The hot path for an app that hears from someone on another system. Always 200 with a `found` discriminator — a miss is data, and the common case in any shared channel. `permissions` are the user's product permissions for THIS app, computed exactly as the claims hook computes them at token mint, so a Slack message and a browser session from the same person carry the same grants. A user with no membership resolves as found with no permissions.";
|
|
524
|
+
readonly permission: "identity:resolve";
|
|
525
|
+
readonly params: {
|
|
526
|
+
readonly provider: "The other system, e.g. slack.";
|
|
527
|
+
readonly externalId: "The id as that system spells it.";
|
|
528
|
+
readonly app: "Application key (oauth_client.metadata.app).";
|
|
529
|
+
};
|
|
530
|
+
readonly successCode: "200";
|
|
531
|
+
readonly success: z.ZodUnion<readonly [z.ZodObject<{
|
|
532
|
+
found: z.ZodLiteral<true>;
|
|
533
|
+
userId: z.ZodString;
|
|
534
|
+
email: z.ZodString;
|
|
535
|
+
name: z.ZodNullable<z.ZodString>;
|
|
536
|
+
permissions: z.ZodArray<z.ZodString>;
|
|
537
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
538
|
+
found: z.ZodLiteral<false>;
|
|
539
|
+
}, z.core.$strip>]>;
|
|
540
|
+
};
|
|
474
541
|
readonly "get /api/v1/apps/{app}/audit": {
|
|
475
542
|
readonly summary: "List recent audit entries";
|
|
476
543
|
readonly permission: "audit:read";
|
|
@@ -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;
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+O0B,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"}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* needs to learn about it.
|
|
9
9
|
*/
|
|
10
10
|
import { z } from "zod";
|
|
11
|
-
import { AdminKeyCreatedSchema, AdminKeyListSchema, ApiKeyCreatedSchema, ApiKeyListSchema, ApplicationCreatedSchema, ApplicationListSchema, ApplicationSchema, AppPermissionsSchema, AuditListSchema, ClientSecretSchema, CreateAdminKeyInput, CreateApiKeyInput, CreateApplicationInput, CreateUserApiKeyInput, CreateWorkspaceInput, InviteMemberInput, InviteMemberResult, MemberListSchema, OkSchema, SetAppPermissionsInput, UpdateApplicationInput, UpdateMemberInput, UserApiKeyCreatedSchema, UserApiKeyListSchema, UserApiKeyValidationSchema, UserListSchema, ValidateUserApiKeyInput, WorkspaceCreatedSchema, WorkspaceListSchema, } from "./index.js";
|
|
11
|
+
import { AdminKeyCreatedSchema, AdminKeyListSchema, ApiKeyCreatedSchema, ApiKeyListSchema, ApplicationCreatedSchema, ApplicationListSchema, ApplicationSchema, AppPermissionsSchema, AuditListSchema, ClientSecretSchema, CreateAdminKeyInput, CreateApiKeyInput, CreateApplicationInput, CreateUserApiKeyInput, CreateWorkspaceInput, InviteMemberInput, InviteMemberResult, MemberListSchema, OkSchema, SetAppPermissionsInput, UpdateApplicationInput, UpdateMemberInput, UserApiKeyCreatedSchema, UserApiKeyListSchema, UserApiKeyValidationSchema, LinkedIdentityListSchema, LinkIdentityInput, LinkedIdentityCreatedSchema, IdentityResolutionSchema, UserListSchema, ValidateUserApiKeyInput, WorkspaceCreatedSchema, WorkspaceListSchema, } from "./index.js";
|
|
12
12
|
const APP_PARAM = { app: "Application key (oauth_client.metadata.app)." };
|
|
13
13
|
const CLIENT_PARAM = { clientId: "OAuth client id of the application." };
|
|
14
14
|
export const operations = {
|
|
@@ -19,7 +19,7 @@ export const operations = {
|
|
|
19
19
|
},
|
|
20
20
|
"post /api/v1/applications": {
|
|
21
21
|
summary: "Register an application (client secret returned once)",
|
|
22
|
-
description: "Requires
|
|
22
|
+
description: "Requires an admin key. Creating an application is an IdP-level act — there is no app to scope a permission to yet.",
|
|
23
23
|
input: CreateApplicationInput,
|
|
24
24
|
successCode: "201",
|
|
25
25
|
success: ApplicationCreatedSchema,
|
|
@@ -75,7 +75,7 @@ export const operations = {
|
|
|
75
75
|
},
|
|
76
76
|
"post /api/v1/apps/{app}/keys": {
|
|
77
77
|
summary: "Mint a scoped management API key (plaintext returned once)",
|
|
78
|
-
description: "Requires `apikey:create` on the path app (or
|
|
78
|
+
description: "Requires `apikey:create` on the path app (or an admin key). The requested permissions must be a subset of the caller's own, otherwise 403 `permissions_exceed_caller` — without that rule any key holding `apikey:create` could mint itself a more powerful successor.",
|
|
79
79
|
permission: "apikey:create",
|
|
80
80
|
params: APP_PARAM,
|
|
81
81
|
input: CreateApiKeyInput,
|
|
@@ -92,13 +92,13 @@ export const operations = {
|
|
|
92
92
|
},
|
|
93
93
|
"get /api/v1/admin-keys": {
|
|
94
94
|
summary: "List IdP-level admin keys (never the hashes)",
|
|
95
|
-
description: "Requires
|
|
95
|
+
description: "Requires an admin key. Admin keys are `api_key` rows with no application scope, so they hold every permission on every app.",
|
|
96
96
|
successCode: "200",
|
|
97
97
|
success: AdminKeyListSchema,
|
|
98
98
|
},
|
|
99
99
|
"post /api/v1/admin-keys": {
|
|
100
100
|
summary: "Mint an IdP-level admin key (plaintext returned once)",
|
|
101
|
-
description: "Requires
|
|
101
|
+
description: "Requires an admin key. Mint one per agent: an admin key has a name, an optional expiry, a revoke switch, and its own `adminkey:<id>` identity in the audit log, so every superadmin action is attributable.",
|
|
102
102
|
input: CreateAdminKeyInput,
|
|
103
103
|
successCode: "201",
|
|
104
104
|
success: AdminKeyCreatedSchema,
|
|
@@ -200,6 +200,36 @@ export const operations = {
|
|
|
200
200
|
successCode: "200",
|
|
201
201
|
success: OkSchema,
|
|
202
202
|
},
|
|
203
|
+
"get /api/v1/users/{userId}/identities": {
|
|
204
|
+
summary: "List a user's linked identities (their ids on other systems)",
|
|
205
|
+
description: "Requires an admin key. Identities are global to the user, not per app — a Slack id identifies a person regardless of who is asking.",
|
|
206
|
+
params: { userId: "IdP user id." },
|
|
207
|
+
successCode: "200",
|
|
208
|
+
success: LinkedIdentityListSchema,
|
|
209
|
+
},
|
|
210
|
+
"post /api/v1/users/{userId}/identities": {
|
|
211
|
+
summary: "Link an external id to a user",
|
|
212
|
+
description: "Requires an admin key: a link asserts identity with nothing to prove it, so no app or member may do it. 201 on a new link, 200 when the same pair was already this user's, 409 `already_linked` when it belongs to someone else — an identity is never silently re-pointed.",
|
|
213
|
+
params: { userId: "IdP user id." },
|
|
214
|
+
input: LinkIdentityInput,
|
|
215
|
+
successCode: "201",
|
|
216
|
+
success: LinkedIdentityCreatedSchema,
|
|
217
|
+
},
|
|
218
|
+
"delete /api/v1/users/{userId}/identities/{id}": {
|
|
219
|
+
summary: "Unlink an external id (idempotent)",
|
|
220
|
+
description: "Requires an admin key.",
|
|
221
|
+
params: { userId: "IdP user id.", id: "Linked identity id." },
|
|
222
|
+
successCode: "200",
|
|
223
|
+
success: OkSchema,
|
|
224
|
+
},
|
|
225
|
+
"get /api/v1/apps/{app}/identities/{provider}/{externalId}": {
|
|
226
|
+
summary: "Resolve an external id to a user and their permissions in this app",
|
|
227
|
+
description: "The hot path for an app that hears from someone on another system. Always 200 with a `found` discriminator — a miss is data, and the common case in any shared channel. `permissions` are the user's product permissions for THIS app, computed exactly as the claims hook computes them at token mint, so a Slack message and a browser session from the same person carry the same grants. A user with no membership resolves as found with no permissions.",
|
|
228
|
+
permission: "identity:resolve",
|
|
229
|
+
params: { ...APP_PARAM, provider: "The other system, e.g. slack.", externalId: "The id as that system spells it." },
|
|
230
|
+
successCode: "200",
|
|
231
|
+
success: IdentityResolutionSchema,
|
|
232
|
+
},
|
|
203
233
|
"get /api/v1/apps/{app}/audit": {
|
|
204
234
|
summary: "List recent audit entries",
|
|
205
235
|
permission: "audit:read",
|
package/openapi/idp-api.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "willy.im IdP — Management API",
|
|
5
5
|
"version": "1.0.0",
|
|
6
|
-
"description": "Management API for the willy.im identity provider. Authenticate with `Authorization: Bearer <token>`. Two kinds of
|
|
6
|
+
"description": "Management API for the willy.im identity provider. Authenticate with `Authorization: Bearer <token>`. Two kinds of bearer, both `wim_` keys: IdP-level **admin keys** (every permission on every app, minted at `/api/v1/admin-keys`) and per-app **scoped keys** minted in the admin console (one app, a fixed permission set). The cross-app endpoints below require an admin key."
|
|
7
7
|
},
|
|
8
8
|
"servers": [
|
|
9
9
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"bearerAuth": {
|
|
16
16
|
"type": "http",
|
|
17
17
|
"scheme": "bearer",
|
|
18
|
-
"description": "
|
|
18
|
+
"description": "An IdP-level admin key or a per-app scoped key (wim_…)."
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
},
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"/api/v1/applications": {
|
|
24
24
|
"get": {
|
|
25
25
|
"summary": "List registered applications",
|
|
26
|
-
"description": "Requires
|
|
26
|
+
"description": "Requires an admin key.",
|
|
27
27
|
"security": [
|
|
28
28
|
{
|
|
29
29
|
"bearerAuth": []
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
},
|
|
122
122
|
"post": {
|
|
123
123
|
"summary": "Register an application (client secret returned once)",
|
|
124
|
-
"description": "Requires
|
|
124
|
+
"description": "Requires an admin key. Creating an application is an IdP-level act — there is no app to scope a permission to yet.",
|
|
125
125
|
"security": [
|
|
126
126
|
{
|
|
127
127
|
"bearerAuth": []
|
|
@@ -222,7 +222,7 @@
|
|
|
222
222
|
"/api/v1/applications/{clientId}": {
|
|
223
223
|
"get": {
|
|
224
224
|
"summary": "Get one application",
|
|
225
|
-
"description": "Requires `app:read` on the path app (or
|
|
225
|
+
"description": "Requires `app:read` on the path app (or an admin key).",
|
|
226
226
|
"security": [
|
|
227
227
|
{
|
|
228
228
|
"bearerAuth": []
|
|
@@ -325,7 +325,7 @@
|
|
|
325
325
|
},
|
|
326
326
|
"patch": {
|
|
327
327
|
"summary": "Update an application",
|
|
328
|
-
"description": "Requires `app:update` on the path app (or
|
|
328
|
+
"description": "Requires `app:update` on the path app (or an admin key).",
|
|
329
329
|
"security": [
|
|
330
330
|
{
|
|
331
331
|
"bearerAuth": []
|
|
@@ -465,7 +465,7 @@
|
|
|
465
465
|
},
|
|
466
466
|
"delete": {
|
|
467
467
|
"summary": "Deregister an application",
|
|
468
|
-
"description": "Requires `app:delete` on the path app (or
|
|
468
|
+
"description": "Requires `app:delete` on the path app (or an admin key).",
|
|
469
469
|
"security": [
|
|
470
470
|
{
|
|
471
471
|
"bearerAuth": []
|
|
@@ -525,7 +525,7 @@
|
|
|
525
525
|
"/api/v1/applications/{clientId}/rotate-secret": {
|
|
526
526
|
"post": {
|
|
527
527
|
"summary": "Rotate the client secret (returned once; the old one stops working)",
|
|
528
|
-
"description": "Requires `app:update` on the path app (or
|
|
528
|
+
"description": "Requires `app:update` on the path app (or an admin key).",
|
|
529
529
|
"security": [
|
|
530
530
|
{
|
|
531
531
|
"bearerAuth": []
|
|
@@ -585,7 +585,7 @@
|
|
|
585
585
|
"/api/v1/apps/{app}/permissions": {
|
|
586
586
|
"put": {
|
|
587
587
|
"summary": "Replace the app's product-permission catalog",
|
|
588
|
-
"description": "Requires `app:update` on the path app (or
|
|
588
|
+
"description": "Requires `app:update` on the path app (or an admin key).",
|
|
589
589
|
"security": [
|
|
590
590
|
{
|
|
591
591
|
"bearerAuth": []
|
|
@@ -673,7 +673,7 @@
|
|
|
673
673
|
"/api/v1/apps/{app}/keys": {
|
|
674
674
|
"get": {
|
|
675
675
|
"summary": "List scoped management API keys (never the hashes)",
|
|
676
|
-
"description": "Requires `apikey:read` on the path app (or
|
|
676
|
+
"description": "Requires `apikey:read` on the path app (or an admin key).",
|
|
677
677
|
"security": [
|
|
678
678
|
{
|
|
679
679
|
"bearerAuth": []
|
|
@@ -796,7 +796,7 @@
|
|
|
796
796
|
},
|
|
797
797
|
"post": {
|
|
798
798
|
"summary": "Mint a scoped management API key (plaintext returned once)",
|
|
799
|
-
"description": "Requires `apikey:create` on the path app (or
|
|
799
|
+
"description": "Requires `apikey:create` on the path app (or an admin key). The requested permissions must be a subset of the caller's own, otherwise 403 `permissions_exceed_caller` — without that rule any key holding `apikey:create` could mint itself a more powerful successor.",
|
|
800
800
|
"security": [
|
|
801
801
|
{
|
|
802
802
|
"bearerAuth": []
|
|
@@ -899,7 +899,7 @@
|
|
|
899
899
|
"/api/v1/apps/{app}/keys/{id}": {
|
|
900
900
|
"delete": {
|
|
901
901
|
"summary": "Revoke a scoped management API key (idempotent)",
|
|
902
|
-
"description": "Requires `apikey:revoke` on the path app (or
|
|
902
|
+
"description": "Requires `apikey:revoke` on the path app (or an admin key).",
|
|
903
903
|
"security": [
|
|
904
904
|
{
|
|
905
905
|
"bearerAuth": []
|
|
@@ -967,7 +967,7 @@
|
|
|
967
967
|
"/api/v1/admin-keys": {
|
|
968
968
|
"get": {
|
|
969
969
|
"summary": "List IdP-level admin keys (never the hashes)",
|
|
970
|
-
"description": "Requires
|
|
970
|
+
"description": "Requires an admin key. Admin keys are `api_key` rows with no application scope, so they hold every permission on every app.",
|
|
971
971
|
"security": [
|
|
972
972
|
{
|
|
973
973
|
"bearerAuth": []
|
|
@@ -1068,7 +1068,7 @@
|
|
|
1068
1068
|
},
|
|
1069
1069
|
"post": {
|
|
1070
1070
|
"summary": "Mint an IdP-level admin key (plaintext returned once)",
|
|
1071
|
-
"description": "Requires
|
|
1071
|
+
"description": "Requires an admin key. Mint one per agent: an admin key has a name, an optional expiry, a revoke switch, and its own `adminkey:<id>` identity in the audit log, so every superadmin action is attributable.",
|
|
1072
1072
|
"security": [
|
|
1073
1073
|
{
|
|
1074
1074
|
"bearerAuth": []
|
|
@@ -1206,7 +1206,7 @@
|
|
|
1206
1206
|
"/api/v1/users": {
|
|
1207
1207
|
"get": {
|
|
1208
1208
|
"summary": "List users",
|
|
1209
|
-
"description": "Requires
|
|
1209
|
+
"description": "Requires an admin key.",
|
|
1210
1210
|
"security": [
|
|
1211
1211
|
{
|
|
1212
1212
|
"bearerAuth": []
|
|
@@ -1277,7 +1277,7 @@
|
|
|
1277
1277
|
"/api/v1/workspaces": {
|
|
1278
1278
|
"get": {
|
|
1279
1279
|
"summary": "List workspaces",
|
|
1280
|
-
"description": "Requires
|
|
1280
|
+
"description": "Requires an admin key.",
|
|
1281
1281
|
"security": [
|
|
1282
1282
|
{
|
|
1283
1283
|
"bearerAuth": []
|
|
@@ -1348,7 +1348,7 @@
|
|
|
1348
1348
|
"/api/v1/apps/{app}/members": {
|
|
1349
1349
|
"get": {
|
|
1350
1350
|
"summary": "List app members",
|
|
1351
|
-
"description": "Requires `member:read` on the path app (or
|
|
1351
|
+
"description": "Requires `member:read` on the path app (or an admin key).",
|
|
1352
1352
|
"security": [
|
|
1353
1353
|
{
|
|
1354
1354
|
"bearerAuth": []
|
|
@@ -1438,7 +1438,7 @@
|
|
|
1438
1438
|
},
|
|
1439
1439
|
"post": {
|
|
1440
1440
|
"summary": "Add or invite a member",
|
|
1441
|
-
"description": "Requires `member:invite` on the path app (or
|
|
1441
|
+
"description": "Requires `member:invite` on the path app (or an admin key).",
|
|
1442
1442
|
"security": [
|
|
1443
1443
|
{
|
|
1444
1444
|
"bearerAuth": []
|
|
@@ -1537,7 +1537,7 @@
|
|
|
1537
1537
|
"/api/v1/apps/{app}/members/{userId}": {
|
|
1538
1538
|
"patch": {
|
|
1539
1539
|
"summary": "Update a member's role + permissions",
|
|
1540
|
-
"description": "Requires `member:manage` on the path app (or
|
|
1540
|
+
"description": "Requires `member:manage` on the path app (or an admin key).",
|
|
1541
1541
|
"security": [
|
|
1542
1542
|
{
|
|
1543
1543
|
"bearerAuth": []
|
|
@@ -1633,7 +1633,7 @@
|
|
|
1633
1633
|
},
|
|
1634
1634
|
"delete": {
|
|
1635
1635
|
"summary": "Remove a member",
|
|
1636
|
-
"description": "Requires `member:manage` on the path app (or
|
|
1636
|
+
"description": "Requires `member:manage` on the path app (or an admin key).",
|
|
1637
1637
|
"security": [
|
|
1638
1638
|
{
|
|
1639
1639
|
"bearerAuth": []
|
|
@@ -1698,7 +1698,7 @@
|
|
|
1698
1698
|
"/api/v1/apps/{app}/workspaces": {
|
|
1699
1699
|
"get": {
|
|
1700
1700
|
"summary": "List app workspaces",
|
|
1701
|
-
"description": "Requires `workspace:read` on the path app (or
|
|
1701
|
+
"description": "Requires `workspace:read` on the path app (or an admin key).",
|
|
1702
1702
|
"security": [
|
|
1703
1703
|
{
|
|
1704
1704
|
"bearerAuth": []
|
|
@@ -1781,7 +1781,7 @@
|
|
|
1781
1781
|
},
|
|
1782
1782
|
"post": {
|
|
1783
1783
|
"summary": "Create a workspace",
|
|
1784
|
-
"description": "Requires `workspace:create` on the path app (or
|
|
1784
|
+
"description": "Requires `workspace:create` on the path app (or an admin key).",
|
|
1785
1785
|
"security": [
|
|
1786
1786
|
{
|
|
1787
1787
|
"bearerAuth": []
|
|
@@ -1874,7 +1874,7 @@
|
|
|
1874
1874
|
"/api/v1/apps/{app}/user-keys": {
|
|
1875
1875
|
"get": {
|
|
1876
1876
|
"summary": "List end-user API keys",
|
|
1877
|
-
"description": "Requires `userkey:read` on the path app (or
|
|
1877
|
+
"description": "Requires `userkey:read` on the path app (or an admin key).",
|
|
1878
1878
|
"security": [
|
|
1879
1879
|
{
|
|
1880
1880
|
"bearerAuth": []
|
|
@@ -2018,7 +2018,7 @@
|
|
|
2018
2018
|
},
|
|
2019
2019
|
"post": {
|
|
2020
2020
|
"summary": "Mint an end-user API key (plaintext returned once)",
|
|
2021
|
-
"description": "Requires `userkey:create` on the path app (or
|
|
2021
|
+
"description": "Requires `userkey:create` on the path app (or an admin key).",
|
|
2022
2022
|
"security": [
|
|
2023
2023
|
{
|
|
2024
2024
|
"bearerAuth": []
|
|
@@ -2128,7 +2128,7 @@
|
|
|
2128
2128
|
"/api/v1/apps/{app}/user-keys/validate": {
|
|
2129
2129
|
"post": {
|
|
2130
2130
|
"summary": "Validate a presented end-user key (200 + valid discriminator)",
|
|
2131
|
-
"description": "Requires `userkey:validate` on the path app (or
|
|
2131
|
+
"description": "Requires `userkey:validate` on the path app (or an admin key).",
|
|
2132
2132
|
"security": [
|
|
2133
2133
|
{
|
|
2134
2134
|
"bearerAuth": []
|
|
@@ -2264,7 +2264,7 @@
|
|
|
2264
2264
|
"/api/v1/apps/{app}/user-keys/{id}": {
|
|
2265
2265
|
"delete": {
|
|
2266
2266
|
"summary": "Revoke an end-user API key (idempotent)",
|
|
2267
|
-
"description": "Requires `userkey:revoke` on the path app (or
|
|
2267
|
+
"description": "Requires `userkey:revoke` on the path app (or an admin key).",
|
|
2268
2268
|
"security": [
|
|
2269
2269
|
{
|
|
2270
2270
|
"bearerAuth": []
|
|
@@ -2326,10 +2326,364 @@
|
|
|
2326
2326
|
}
|
|
2327
2327
|
}
|
|
2328
2328
|
},
|
|
2329
|
+
"/api/v1/users/{userId}/identities": {
|
|
2330
|
+
"get": {
|
|
2331
|
+
"summary": "List a user's linked identities (their ids on other systems)",
|
|
2332
|
+
"description": "Requires an admin key. Identities are global to the user, not per app — a Slack id identifies a person regardless of who is asking.",
|
|
2333
|
+
"security": [
|
|
2334
|
+
{
|
|
2335
|
+
"bearerAuth": []
|
|
2336
|
+
}
|
|
2337
|
+
],
|
|
2338
|
+
"parameters": [
|
|
2339
|
+
{
|
|
2340
|
+
"name": "userId",
|
|
2341
|
+
"in": "path",
|
|
2342
|
+
"required": true,
|
|
2343
|
+
"description": "IdP user id.",
|
|
2344
|
+
"schema": {
|
|
2345
|
+
"type": "string"
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
],
|
|
2349
|
+
"responses": {
|
|
2350
|
+
"200": {
|
|
2351
|
+
"description": "OK",
|
|
2352
|
+
"content": {
|
|
2353
|
+
"application/json": {
|
|
2354
|
+
"schema": {
|
|
2355
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
2356
|
+
"type": "object",
|
|
2357
|
+
"properties": {
|
|
2358
|
+
"identities": {
|
|
2359
|
+
"type": "array",
|
|
2360
|
+
"items": {
|
|
2361
|
+
"type": "object",
|
|
2362
|
+
"properties": {
|
|
2363
|
+
"id": {
|
|
2364
|
+
"type": "string"
|
|
2365
|
+
},
|
|
2366
|
+
"userId": {
|
|
2367
|
+
"type": "string"
|
|
2368
|
+
},
|
|
2369
|
+
"provider": {
|
|
2370
|
+
"type": "string",
|
|
2371
|
+
"description": "The other system, lowercase — slack, whatsapp, telegram"
|
|
2372
|
+
},
|
|
2373
|
+
"externalId": {
|
|
2374
|
+
"type": "string",
|
|
2375
|
+
"description": "The id exactly as that system spells it"
|
|
2376
|
+
},
|
|
2377
|
+
"label": {
|
|
2378
|
+
"anyOf": [
|
|
2379
|
+
{
|
|
2380
|
+
"type": "string"
|
|
2381
|
+
},
|
|
2382
|
+
{
|
|
2383
|
+
"type": "null"
|
|
2384
|
+
}
|
|
2385
|
+
]
|
|
2386
|
+
},
|
|
2387
|
+
"createdAt": {
|
|
2388
|
+
"type": "string"
|
|
2389
|
+
}
|
|
2390
|
+
},
|
|
2391
|
+
"required": [
|
|
2392
|
+
"id",
|
|
2393
|
+
"userId",
|
|
2394
|
+
"provider",
|
|
2395
|
+
"externalId",
|
|
2396
|
+
"label",
|
|
2397
|
+
"createdAt"
|
|
2398
|
+
],
|
|
2399
|
+
"additionalProperties": false
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
},
|
|
2403
|
+
"required": [
|
|
2404
|
+
"identities"
|
|
2405
|
+
],
|
|
2406
|
+
"additionalProperties": false
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
},
|
|
2411
|
+
"401": {
|
|
2412
|
+
"description": "Missing or invalid bearer token"
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
},
|
|
2416
|
+
"post": {
|
|
2417
|
+
"summary": "Link an external id to a user",
|
|
2418
|
+
"description": "Requires an admin key: a link asserts identity with nothing to prove it, so no app or member may do it. 201 on a new link, 200 when the same pair was already this user's, 409 `already_linked` when it belongs to someone else — an identity is never silently re-pointed.",
|
|
2419
|
+
"security": [
|
|
2420
|
+
{
|
|
2421
|
+
"bearerAuth": []
|
|
2422
|
+
}
|
|
2423
|
+
],
|
|
2424
|
+
"parameters": [
|
|
2425
|
+
{
|
|
2426
|
+
"name": "userId",
|
|
2427
|
+
"in": "path",
|
|
2428
|
+
"required": true,
|
|
2429
|
+
"description": "IdP user id.",
|
|
2430
|
+
"schema": {
|
|
2431
|
+
"type": "string"
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
],
|
|
2435
|
+
"requestBody": {
|
|
2436
|
+
"required": true,
|
|
2437
|
+
"content": {
|
|
2438
|
+
"application/json": {
|
|
2439
|
+
"schema": {
|
|
2440
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
2441
|
+
"type": "object",
|
|
2442
|
+
"properties": {
|
|
2443
|
+
"provider": {
|
|
2444
|
+
"type": "string",
|
|
2445
|
+
"minLength": 1,
|
|
2446
|
+
"description": "slack, whatsapp, telegram… — normalised to lowercase"
|
|
2447
|
+
},
|
|
2448
|
+
"externalId": {
|
|
2449
|
+
"type": "string",
|
|
2450
|
+
"minLength": 1,
|
|
2451
|
+
"description": "The id as that system spells it, e.g. a Slack member id"
|
|
2452
|
+
},
|
|
2453
|
+
"label": {
|
|
2454
|
+
"description": "A human label for the console",
|
|
2455
|
+
"type": "string"
|
|
2456
|
+
}
|
|
2457
|
+
},
|
|
2458
|
+
"required": [
|
|
2459
|
+
"provider",
|
|
2460
|
+
"externalId"
|
|
2461
|
+
]
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
},
|
|
2466
|
+
"responses": {
|
|
2467
|
+
"201": {
|
|
2468
|
+
"description": "OK",
|
|
2469
|
+
"content": {
|
|
2470
|
+
"application/json": {
|
|
2471
|
+
"schema": {
|
|
2472
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
2473
|
+
"type": "object",
|
|
2474
|
+
"properties": {
|
|
2475
|
+
"id": {
|
|
2476
|
+
"type": "string"
|
|
2477
|
+
},
|
|
2478
|
+
"created": {
|
|
2479
|
+
"type": "boolean",
|
|
2480
|
+
"description": "false when the same pair was already this user's"
|
|
2481
|
+
}
|
|
2482
|
+
},
|
|
2483
|
+
"required": [
|
|
2484
|
+
"id",
|
|
2485
|
+
"created"
|
|
2486
|
+
],
|
|
2487
|
+
"additionalProperties": false
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
},
|
|
2492
|
+
"401": {
|
|
2493
|
+
"description": "Missing or invalid bearer token"
|
|
2494
|
+
},
|
|
2495
|
+
"405": {
|
|
2496
|
+
"description": "Method not allowed on this resource (see the `Allow` header)"
|
|
2497
|
+
},
|
|
2498
|
+
"409": {
|
|
2499
|
+
"description": "Conflict (already a member, last admin, slug taken, …)"
|
|
2500
|
+
},
|
|
2501
|
+
"422": {
|
|
2502
|
+
"description": "Body failed validation"
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
},
|
|
2507
|
+
"/api/v1/users/{userId}/identities/{id}": {
|
|
2508
|
+
"delete": {
|
|
2509
|
+
"summary": "Unlink an external id (idempotent)",
|
|
2510
|
+
"description": "Requires an admin key.",
|
|
2511
|
+
"security": [
|
|
2512
|
+
{
|
|
2513
|
+
"bearerAuth": []
|
|
2514
|
+
}
|
|
2515
|
+
],
|
|
2516
|
+
"parameters": [
|
|
2517
|
+
{
|
|
2518
|
+
"name": "userId",
|
|
2519
|
+
"in": "path",
|
|
2520
|
+
"required": true,
|
|
2521
|
+
"description": "IdP user id.",
|
|
2522
|
+
"schema": {
|
|
2523
|
+
"type": "string"
|
|
2524
|
+
}
|
|
2525
|
+
},
|
|
2526
|
+
{
|
|
2527
|
+
"name": "id",
|
|
2528
|
+
"in": "path",
|
|
2529
|
+
"required": true,
|
|
2530
|
+
"description": "Linked identity id.",
|
|
2531
|
+
"schema": {
|
|
2532
|
+
"type": "string"
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
],
|
|
2536
|
+
"responses": {
|
|
2537
|
+
"200": {
|
|
2538
|
+
"description": "OK",
|
|
2539
|
+
"content": {
|
|
2540
|
+
"application/json": {
|
|
2541
|
+
"schema": {
|
|
2542
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
2543
|
+
"type": "object",
|
|
2544
|
+
"properties": {
|
|
2545
|
+
"ok": {
|
|
2546
|
+
"type": "boolean",
|
|
2547
|
+
"const": true
|
|
2548
|
+
}
|
|
2549
|
+
},
|
|
2550
|
+
"required": [
|
|
2551
|
+
"ok"
|
|
2552
|
+
],
|
|
2553
|
+
"additionalProperties": false
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
},
|
|
2558
|
+
"401": {
|
|
2559
|
+
"description": "Missing or invalid bearer token"
|
|
2560
|
+
},
|
|
2561
|
+
"405": {
|
|
2562
|
+
"description": "Method not allowed on this resource (see the `Allow` header)"
|
|
2563
|
+
},
|
|
2564
|
+
"409": {
|
|
2565
|
+
"description": "Conflict (already a member, last admin, slug taken, …)"
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
},
|
|
2570
|
+
"/api/v1/apps/{app}/identities/{provider}/{externalId}": {
|
|
2571
|
+
"get": {
|
|
2572
|
+
"summary": "Resolve an external id to a user and their permissions in this app",
|
|
2573
|
+
"description": "The hot path for an app that hears from someone on another system. Always 200 with a `found` discriminator — a miss is data, and the common case in any shared channel. `permissions` are the user's product permissions for THIS app, computed exactly as the claims hook computes them at token mint, so a Slack message and a browser session from the same person carry the same grants. A user with no membership resolves as found with no permissions.",
|
|
2574
|
+
"security": [
|
|
2575
|
+
{
|
|
2576
|
+
"bearerAuth": []
|
|
2577
|
+
}
|
|
2578
|
+
],
|
|
2579
|
+
"parameters": [
|
|
2580
|
+
{
|
|
2581
|
+
"name": "app",
|
|
2582
|
+
"in": "path",
|
|
2583
|
+
"required": true,
|
|
2584
|
+
"description": "Application key (oauth_client.metadata.app).",
|
|
2585
|
+
"schema": {
|
|
2586
|
+
"type": "string"
|
|
2587
|
+
}
|
|
2588
|
+
},
|
|
2589
|
+
{
|
|
2590
|
+
"name": "provider",
|
|
2591
|
+
"in": "path",
|
|
2592
|
+
"required": true,
|
|
2593
|
+
"description": "The other system, e.g. slack.",
|
|
2594
|
+
"schema": {
|
|
2595
|
+
"type": "string"
|
|
2596
|
+
}
|
|
2597
|
+
},
|
|
2598
|
+
{
|
|
2599
|
+
"name": "externalId",
|
|
2600
|
+
"in": "path",
|
|
2601
|
+
"required": true,
|
|
2602
|
+
"description": "The id as that system spells it.",
|
|
2603
|
+
"schema": {
|
|
2604
|
+
"type": "string"
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
],
|
|
2608
|
+
"responses": {
|
|
2609
|
+
"200": {
|
|
2610
|
+
"description": "OK",
|
|
2611
|
+
"content": {
|
|
2612
|
+
"application/json": {
|
|
2613
|
+
"schema": {
|
|
2614
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
2615
|
+
"anyOf": [
|
|
2616
|
+
{
|
|
2617
|
+
"type": "object",
|
|
2618
|
+
"properties": {
|
|
2619
|
+
"found": {
|
|
2620
|
+
"type": "boolean",
|
|
2621
|
+
"const": true
|
|
2622
|
+
},
|
|
2623
|
+
"userId": {
|
|
2624
|
+
"type": "string"
|
|
2625
|
+
},
|
|
2626
|
+
"email": {
|
|
2627
|
+
"type": "string"
|
|
2628
|
+
},
|
|
2629
|
+
"name": {
|
|
2630
|
+
"anyOf": [
|
|
2631
|
+
{
|
|
2632
|
+
"type": "string"
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
"type": "null"
|
|
2636
|
+
}
|
|
2637
|
+
]
|
|
2638
|
+
},
|
|
2639
|
+
"permissions": {
|
|
2640
|
+
"type": "array",
|
|
2641
|
+
"items": {
|
|
2642
|
+
"type": "string"
|
|
2643
|
+
},
|
|
2644
|
+
"description": "The user's product permissions for the asking app; admins get the whole catalog"
|
|
2645
|
+
}
|
|
2646
|
+
},
|
|
2647
|
+
"required": [
|
|
2648
|
+
"found",
|
|
2649
|
+
"userId",
|
|
2650
|
+
"email",
|
|
2651
|
+
"name",
|
|
2652
|
+
"permissions"
|
|
2653
|
+
],
|
|
2654
|
+
"additionalProperties": false
|
|
2655
|
+
},
|
|
2656
|
+
{
|
|
2657
|
+
"type": "object",
|
|
2658
|
+
"properties": {
|
|
2659
|
+
"found": {
|
|
2660
|
+
"type": "boolean",
|
|
2661
|
+
"const": false
|
|
2662
|
+
}
|
|
2663
|
+
},
|
|
2664
|
+
"required": [
|
|
2665
|
+
"found"
|
|
2666
|
+
],
|
|
2667
|
+
"additionalProperties": false
|
|
2668
|
+
}
|
|
2669
|
+
]
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
},
|
|
2674
|
+
"401": {
|
|
2675
|
+
"description": "Missing or invalid bearer token"
|
|
2676
|
+
},
|
|
2677
|
+
"403": {
|
|
2678
|
+
"description": "Key lacks the permission / is bound to another app"
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
},
|
|
2329
2683
|
"/api/v1/apps/{app}/audit": {
|
|
2330
2684
|
"get": {
|
|
2331
2685
|
"summary": "List recent audit entries",
|
|
2332
|
-
"description": "Requires `audit:read` on the path app (or
|
|
2686
|
+
"description": "Requires `audit:read` on the path app (or an admin key).",
|
|
2333
2687
|
"security": [
|
|
2334
2688
|
{
|
|
2335
2689
|
"bearerAuth": []
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@willyim/idp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|