@willyim/idp 0.1.0 → 0.2.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 +83 -13
- package/dist/src/api.d.ts +31 -55
- package/dist/src/api.d.ts.map +1 -1
- package/dist/src/api.js +16 -11
- package/dist/src/claims.d.ts +9 -34
- package/dist/src/claims.d.ts.map +1 -1
- package/dist/src/claims.js +12 -40
- package/dist/src/client.d.ts +30 -34
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +16 -22
- package/dist/src/drizzle/index.d.ts +78 -13
- package/dist/src/drizzle/index.d.ts.map +1 -1
- package/dist/src/errors.d.ts +8 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +12 -0
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/schemas/index.d.ts +207 -0
- package/dist/src/schemas/index.d.ts.map +1 -0
- package/dist/src/schemas/index.js +122 -0
- package/dist/src/schemas/openapi.d.ts +31 -0
- package/dist/src/schemas/openapi.d.ts.map +1 -0
- package/dist/src/schemas/openapi.js +110 -0
- package/dist/src/schemas/operations.d.ts +297 -0
- package/dist/src/schemas/operations.d.ts.map +1 -0
- package/dist/src/schemas/operations.js +118 -0
- package/dist/src/session.d.ts +17 -3
- package/dist/src/session.d.ts.map +1 -1
- package/dist/src/session.js +12 -1
- package/dist/src/user-keys.d.ts +124 -0
- package/dist/src/user-keys.d.ts.map +1 -0
- package/dist/src/user-keys.js +174 -0
- package/dist/src/validate.d.ts +13 -0
- package/dist/src/validate.d.ts.map +1 -0
- package/dist/src/validate.js +28 -0
- package/dist/src/wire.d.ts +164 -0
- package/dist/src/wire.d.ts.map +1 -0
- package/dist/src/wire.js +100 -0
- package/openapi/idp-api.json +3 -6
- package/package.json +16 -7
- package/dist/src/generated/idp-api.d.ts +0 -1022
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The published OpenAPI document, built from `./operations.ts`.
|
|
3
|
+
*
|
|
4
|
+
* `apps/idp` serves this at `/api/v1/openapi.json`; `npm run openapi` writes
|
|
5
|
+
* the same object to `openapi/idp-api.json` so the snapshot in the repo is
|
|
6
|
+
* always what the server would answer.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { lookup, operations } from "./operations.js";
|
|
10
|
+
/**
|
|
11
|
+
* Responses describe what the API *returns*, so they use zod's output types.
|
|
12
|
+
* Request bodies describe what a caller may *send*, which is a different shape:
|
|
13
|
+
* a field with a `.default()` is optional on the way in and guaranteed on the
|
|
14
|
+
* way out. Emitting output types for both would mark every defaulted field
|
|
15
|
+
* `required` and force callers to supply values the API would have defaulted.
|
|
16
|
+
*/
|
|
17
|
+
const json = (schema) => z.toJSONSchema(schema);
|
|
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 token: the superadmin `ADMIN_API_TOKEN` (every app) and per-app **scoped API keys** minted in the admin console (one app, a fixed permission set). The cross-app list endpoints below require the superadmin token. Application registration is done in the admin console (better-auth ties client creation to an admin session).";
|
|
20
|
+
function pathParamNames(path) {
|
|
21
|
+
return [...path.matchAll(/\{([^}]+)\}/g)].map((match) => match[1]);
|
|
22
|
+
}
|
|
23
|
+
function parametersFor(path, def) {
|
|
24
|
+
const pathParams = pathParamNames(path).map((name) => ({
|
|
25
|
+
name,
|
|
26
|
+
in: "path",
|
|
27
|
+
required: true,
|
|
28
|
+
...(def.params?.[name] ? { description: def.params[name] } : {}),
|
|
29
|
+
schema: { type: "string" },
|
|
30
|
+
}));
|
|
31
|
+
const queryParams = (def.query ?? []).map((param) => ({
|
|
32
|
+
name: param.name,
|
|
33
|
+
in: "query",
|
|
34
|
+
required: param.required ?? false,
|
|
35
|
+
...(param.description ? { description: param.description } : {}),
|
|
36
|
+
schema: { type: "string" },
|
|
37
|
+
}));
|
|
38
|
+
return [...pathParams, ...queryParams];
|
|
39
|
+
}
|
|
40
|
+
function operationObject(method, path, def) {
|
|
41
|
+
const scoped = def.permission !== undefined;
|
|
42
|
+
const write = method !== "get";
|
|
43
|
+
const parameters = parametersFor(path, def);
|
|
44
|
+
return {
|
|
45
|
+
summary: def.summary,
|
|
46
|
+
description: def.description ??
|
|
47
|
+
(scoped
|
|
48
|
+
? `Requires \`${def.permission}\` on the path app (or the superadmin token).`
|
|
49
|
+
: "Requires the superadmin token."),
|
|
50
|
+
security: [{ bearerAuth: [] }],
|
|
51
|
+
...(parameters.length ? { parameters } : {}),
|
|
52
|
+
...(def.input
|
|
53
|
+
? {
|
|
54
|
+
requestBody: {
|
|
55
|
+
required: true,
|
|
56
|
+
content: { "application/json": { schema: jsonInput(def.input) } },
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
: {}),
|
|
60
|
+
responses: {
|
|
61
|
+
[def.successCode]: {
|
|
62
|
+
description: "OK",
|
|
63
|
+
content: { "application/json": { schema: json(def.success) } },
|
|
64
|
+
},
|
|
65
|
+
"401": { description: "Missing or invalid bearer token" },
|
|
66
|
+
...(scoped
|
|
67
|
+
? { "403": { description: "Key lacks the permission / is bound to another app" } }
|
|
68
|
+
: {}),
|
|
69
|
+
...(write
|
|
70
|
+
? {
|
|
71
|
+
"405": {
|
|
72
|
+
description: "Method not allowed on this resource (see the `Allow` header)",
|
|
73
|
+
},
|
|
74
|
+
"409": { description: "Conflict (already a member, last admin, slug taken, …)" },
|
|
75
|
+
}
|
|
76
|
+
: {}),
|
|
77
|
+
...(def.input ? { "422": { description: "Body failed validation" } } : {}),
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function buildOpenApiDocument(input) {
|
|
82
|
+
const paths = {};
|
|
83
|
+
for (const key of Object.keys(operations)) {
|
|
84
|
+
const [method, path] = key.split(" ");
|
|
85
|
+
const def = lookup(method, path);
|
|
86
|
+
if (!def)
|
|
87
|
+
continue;
|
|
88
|
+
paths[path] ??= {};
|
|
89
|
+
paths[path][method] = operationObject(method, path, def);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
openapi: "3.1.0",
|
|
93
|
+
info: {
|
|
94
|
+
title: "willy.im IdP — Management API",
|
|
95
|
+
version: "1.0.0",
|
|
96
|
+
description: DESCRIPTION,
|
|
97
|
+
},
|
|
98
|
+
servers: [{ url: input.baseUrl }],
|
|
99
|
+
components: {
|
|
100
|
+
securitySchemes: {
|
|
101
|
+
bearerAuth: {
|
|
102
|
+
type: "http",
|
|
103
|
+
scheme: "bearer",
|
|
104
|
+
description: "Superadmin ADMIN_API_TOKEN or a scoped API key (wim_…).",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
paths,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every operation the management API exposes, in one table.
|
|
3
|
+
*
|
|
4
|
+
* Three things read it: `buildOpenApiDocument` (in `./openapi.ts`) turns it
|
|
5
|
+
* into the published document, `api.ts` uses it to type its `request()` and to
|
|
6
|
+
* parse what comes back, and `apps/idp` validates request bodies with the same
|
|
7
|
+
* `input` schemas. Adding an endpoint means adding a row here; nothing else
|
|
8
|
+
* needs to learn about it.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
export type HttpMethod = "get" | "post" | "patch" | "delete" | "put";
|
|
12
|
+
/** A query parameter, as the document describes it. Path params are inferred. */
|
|
13
|
+
export type QueryParam = {
|
|
14
|
+
name: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
};
|
|
18
|
+
export type OperationDef = {
|
|
19
|
+
summary: string;
|
|
20
|
+
/**
|
|
21
|
+
* The scoped-key permission this operation needs. Absent means superadmin
|
|
22
|
+
* only — the cross-app list endpoints.
|
|
23
|
+
*/
|
|
24
|
+
permission?: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
query?: readonly QueryParam[];
|
|
27
|
+
/** Path-parameter descriptions, keyed by name. Purely documentation. */
|
|
28
|
+
params?: Readonly<Record<string, string>>;
|
|
29
|
+
input?: z.ZodType;
|
|
30
|
+
successCode: "200" | "201";
|
|
31
|
+
success: z.ZodType;
|
|
32
|
+
};
|
|
33
|
+
export declare const operations: {
|
|
34
|
+
readonly "get /api/v1/applications": {
|
|
35
|
+
readonly summary: "List registered applications";
|
|
36
|
+
readonly successCode: "200";
|
|
37
|
+
readonly success: z.ZodObject<{
|
|
38
|
+
applications: z.ZodArray<z.ZodObject<{
|
|
39
|
+
clientId: z.ZodString;
|
|
40
|
+
name: z.ZodNullable<z.ZodString>;
|
|
41
|
+
app: z.ZodNullable<z.ZodString>;
|
|
42
|
+
redirectUris: z.ZodArray<z.ZodString>;
|
|
43
|
+
disabled: z.ZodBoolean;
|
|
44
|
+
createdAt: z.ZodString;
|
|
45
|
+
}, z.core.$strip>>;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
};
|
|
48
|
+
readonly "get /api/v1/users": {
|
|
49
|
+
readonly summary: "List users";
|
|
50
|
+
readonly successCode: "200";
|
|
51
|
+
readonly success: z.ZodObject<{
|
|
52
|
+
users: z.ZodArray<z.ZodObject<{
|
|
53
|
+
id: z.ZodString;
|
|
54
|
+
email: z.ZodString;
|
|
55
|
+
name: z.ZodNullable<z.ZodString>;
|
|
56
|
+
emailVerified: z.ZodBoolean;
|
|
57
|
+
createdAt: z.ZodString;
|
|
58
|
+
}, z.core.$strip>>;
|
|
59
|
+
}, z.core.$strip>;
|
|
60
|
+
};
|
|
61
|
+
readonly "get /api/v1/workspaces": {
|
|
62
|
+
readonly summary: "List workspaces";
|
|
63
|
+
readonly successCode: "200";
|
|
64
|
+
readonly success: z.ZodObject<{
|
|
65
|
+
workspaces: z.ZodArray<z.ZodObject<{
|
|
66
|
+
id: z.ZodString;
|
|
67
|
+
name: z.ZodString;
|
|
68
|
+
slug: z.ZodString;
|
|
69
|
+
applicationId: z.ZodNullable<z.ZodString>;
|
|
70
|
+
createdAt: z.ZodString;
|
|
71
|
+
}, z.core.$strip>>;
|
|
72
|
+
}, z.core.$strip>;
|
|
73
|
+
};
|
|
74
|
+
readonly "get /api/v1/apps/{app}/members": {
|
|
75
|
+
readonly summary: "List app members";
|
|
76
|
+
readonly permission: "member:read";
|
|
77
|
+
readonly params: {
|
|
78
|
+
readonly app: "Application key (oauth_client.metadata.app).";
|
|
79
|
+
};
|
|
80
|
+
readonly successCode: "200";
|
|
81
|
+
readonly success: z.ZodObject<{
|
|
82
|
+
members: z.ZodArray<z.ZodObject<{
|
|
83
|
+
userId: z.ZodString;
|
|
84
|
+
email: z.ZodString;
|
|
85
|
+
name: z.ZodNullable<z.ZodString>;
|
|
86
|
+
role: z.ZodEnum<{
|
|
87
|
+
admin: "admin";
|
|
88
|
+
member: "member";
|
|
89
|
+
}>;
|
|
90
|
+
permissions: z.ZodArray<z.ZodString>;
|
|
91
|
+
}, z.core.$strip>>;
|
|
92
|
+
}, z.core.$strip>;
|
|
93
|
+
};
|
|
94
|
+
readonly "post /api/v1/apps/{app}/members": {
|
|
95
|
+
readonly summary: "Add or invite a member";
|
|
96
|
+
readonly permission: "member:invite";
|
|
97
|
+
readonly params: {
|
|
98
|
+
readonly app: "Application key (oauth_client.metadata.app).";
|
|
99
|
+
};
|
|
100
|
+
readonly input: z.ZodObject<{
|
|
101
|
+
email: z.ZodString;
|
|
102
|
+
role: z.ZodDefault<z.ZodEnum<{
|
|
103
|
+
admin: "admin";
|
|
104
|
+
member: "member";
|
|
105
|
+
}>>;
|
|
106
|
+
permissions: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
107
|
+
}, z.core.$strip>;
|
|
108
|
+
readonly successCode: "201";
|
|
109
|
+
readonly success: z.ZodObject<{
|
|
110
|
+
result: z.ZodEnum<{
|
|
111
|
+
added: "added";
|
|
112
|
+
invited: "invited";
|
|
113
|
+
}>;
|
|
114
|
+
}, z.core.$strip>;
|
|
115
|
+
};
|
|
116
|
+
readonly "patch /api/v1/apps/{app}/members/{userId}": {
|
|
117
|
+
readonly summary: "Update a member's role + permissions";
|
|
118
|
+
readonly permission: "member:manage";
|
|
119
|
+
readonly params: {
|
|
120
|
+
readonly app: "Application key (oauth_client.metadata.app).";
|
|
121
|
+
};
|
|
122
|
+
readonly input: z.ZodObject<{
|
|
123
|
+
role: z.ZodEnum<{
|
|
124
|
+
admin: "admin";
|
|
125
|
+
member: "member";
|
|
126
|
+
}>;
|
|
127
|
+
permissions: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
128
|
+
}, z.core.$strip>;
|
|
129
|
+
readonly successCode: "200";
|
|
130
|
+
readonly success: z.ZodObject<{
|
|
131
|
+
ok: z.ZodLiteral<true>;
|
|
132
|
+
}, z.core.$strip>;
|
|
133
|
+
};
|
|
134
|
+
readonly "delete /api/v1/apps/{app}/members/{userId}": {
|
|
135
|
+
readonly summary: "Remove a member";
|
|
136
|
+
readonly permission: "member:manage";
|
|
137
|
+
readonly params: {
|
|
138
|
+
readonly app: "Application key (oauth_client.metadata.app).";
|
|
139
|
+
};
|
|
140
|
+
readonly successCode: "200";
|
|
141
|
+
readonly success: z.ZodObject<{
|
|
142
|
+
ok: z.ZodLiteral<true>;
|
|
143
|
+
}, z.core.$strip>;
|
|
144
|
+
};
|
|
145
|
+
readonly "get /api/v1/apps/{app}/workspaces": {
|
|
146
|
+
readonly summary: "List app workspaces";
|
|
147
|
+
readonly permission: "workspace:read";
|
|
148
|
+
readonly params: {
|
|
149
|
+
readonly app: "Application key (oauth_client.metadata.app).";
|
|
150
|
+
};
|
|
151
|
+
readonly successCode: "200";
|
|
152
|
+
readonly success: z.ZodObject<{
|
|
153
|
+
workspaces: z.ZodArray<z.ZodObject<{
|
|
154
|
+
id: z.ZodString;
|
|
155
|
+
name: z.ZodString;
|
|
156
|
+
slug: z.ZodString;
|
|
157
|
+
applicationId: z.ZodNullable<z.ZodString>;
|
|
158
|
+
createdAt: z.ZodString;
|
|
159
|
+
}, z.core.$strip>>;
|
|
160
|
+
}, z.core.$strip>;
|
|
161
|
+
};
|
|
162
|
+
readonly "post /api/v1/apps/{app}/workspaces": {
|
|
163
|
+
readonly summary: "Create a workspace";
|
|
164
|
+
readonly permission: "workspace:create";
|
|
165
|
+
readonly params: {
|
|
166
|
+
readonly app: "Application key (oauth_client.metadata.app).";
|
|
167
|
+
};
|
|
168
|
+
readonly input: z.ZodObject<{
|
|
169
|
+
name: z.ZodString;
|
|
170
|
+
slug: z.ZodString;
|
|
171
|
+
}, z.core.$strip>;
|
|
172
|
+
readonly successCode: "201";
|
|
173
|
+
readonly success: z.ZodObject<{
|
|
174
|
+
id: z.ZodString;
|
|
175
|
+
name: z.ZodString;
|
|
176
|
+
slug: z.ZodString;
|
|
177
|
+
}, z.core.$strip>;
|
|
178
|
+
};
|
|
179
|
+
readonly "get /api/v1/apps/{app}/user-keys": {
|
|
180
|
+
readonly summary: "List end-user API keys";
|
|
181
|
+
readonly permission: "userkey:read";
|
|
182
|
+
readonly params: {
|
|
183
|
+
readonly app: "Application key (oauth_client.metadata.app).";
|
|
184
|
+
};
|
|
185
|
+
readonly query: readonly [{
|
|
186
|
+
readonly name: "userId";
|
|
187
|
+
readonly description: "Only keys owned by this user.";
|
|
188
|
+
}, {
|
|
189
|
+
readonly name: "workspaceId";
|
|
190
|
+
readonly description: "Only keys bound to this workspace.";
|
|
191
|
+
}];
|
|
192
|
+
readonly successCode: "200";
|
|
193
|
+
readonly success: z.ZodObject<{
|
|
194
|
+
keys: z.ZodArray<z.ZodObject<{
|
|
195
|
+
id: z.ZodString;
|
|
196
|
+
userId: z.ZodString;
|
|
197
|
+
workspaceId: z.ZodNullable<z.ZodString>;
|
|
198
|
+
name: z.ZodString;
|
|
199
|
+
prefix: z.ZodString;
|
|
200
|
+
scopes: z.ZodArray<z.ZodString>;
|
|
201
|
+
status: z.ZodEnum<{
|
|
202
|
+
active: "active";
|
|
203
|
+
expired: "expired";
|
|
204
|
+
revoked: "revoked";
|
|
205
|
+
}>;
|
|
206
|
+
createdAt: z.ZodString;
|
|
207
|
+
lastUsedAt: z.ZodNullable<z.ZodString>;
|
|
208
|
+
expiresAt: z.ZodNullable<z.ZodString>;
|
|
209
|
+
}, z.core.$strip>>;
|
|
210
|
+
}, z.core.$strip>;
|
|
211
|
+
};
|
|
212
|
+
readonly "post /api/v1/apps/{app}/user-keys": {
|
|
213
|
+
readonly summary: "Mint an end-user API key (plaintext returned once)";
|
|
214
|
+
readonly permission: "userkey:create";
|
|
215
|
+
readonly params: {
|
|
216
|
+
readonly app: "Application key (oauth_client.metadata.app).";
|
|
217
|
+
};
|
|
218
|
+
readonly input: z.ZodObject<{
|
|
219
|
+
userId: z.ZodString;
|
|
220
|
+
name: z.ZodString;
|
|
221
|
+
scopes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
222
|
+
workspaceId: z.ZodOptional<z.ZodString>;
|
|
223
|
+
expiresAt: z.ZodOptional<z.ZodISODateTime>;
|
|
224
|
+
}, z.core.$strip>;
|
|
225
|
+
readonly successCode: "201";
|
|
226
|
+
readonly success: z.ZodObject<{
|
|
227
|
+
id: z.ZodString;
|
|
228
|
+
token: z.ZodString;
|
|
229
|
+
prefix: z.ZodString;
|
|
230
|
+
}, z.core.$strip>;
|
|
231
|
+
};
|
|
232
|
+
readonly "post /api/v1/apps/{app}/user-keys/validate": {
|
|
233
|
+
readonly summary: "Validate a presented end-user key (200 + valid discriminator)";
|
|
234
|
+
readonly permission: "userkey:validate";
|
|
235
|
+
readonly params: {
|
|
236
|
+
readonly app: "Application key (oauth_client.metadata.app).";
|
|
237
|
+
};
|
|
238
|
+
readonly input: z.ZodObject<{
|
|
239
|
+
token: z.ZodString;
|
|
240
|
+
}, z.core.$strip>;
|
|
241
|
+
readonly successCode: "200";
|
|
242
|
+
readonly success: z.ZodUnion<readonly [z.ZodObject<{
|
|
243
|
+
valid: z.ZodLiteral<true>;
|
|
244
|
+
keyId: z.ZodString;
|
|
245
|
+
userId: z.ZodString;
|
|
246
|
+
workspaceId: z.ZodNullable<z.ZodString>;
|
|
247
|
+
scopes: z.ZodArray<z.ZodString>;
|
|
248
|
+
name: z.ZodString;
|
|
249
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
250
|
+
valid: z.ZodLiteral<false>;
|
|
251
|
+
reason: z.ZodEnum<{
|
|
252
|
+
expired: "expired";
|
|
253
|
+
revoked: "revoked";
|
|
254
|
+
not_found: "not_found";
|
|
255
|
+
}>;
|
|
256
|
+
}, z.core.$strip>]>;
|
|
257
|
+
};
|
|
258
|
+
readonly "delete /api/v1/apps/{app}/user-keys/{id}": {
|
|
259
|
+
readonly summary: "Revoke an end-user API key (idempotent)";
|
|
260
|
+
readonly permission: "userkey:revoke";
|
|
261
|
+
readonly params: {
|
|
262
|
+
readonly app: "Application key (oauth_client.metadata.app).";
|
|
263
|
+
};
|
|
264
|
+
readonly successCode: "200";
|
|
265
|
+
readonly success: z.ZodObject<{
|
|
266
|
+
ok: z.ZodLiteral<true>;
|
|
267
|
+
}, z.core.$strip>;
|
|
268
|
+
};
|
|
269
|
+
readonly "get /api/v1/apps/{app}/audit": {
|
|
270
|
+
readonly summary: "List recent audit entries";
|
|
271
|
+
readonly permission: "audit:read";
|
|
272
|
+
readonly params: {
|
|
273
|
+
readonly app: "Application key (oauth_client.metadata.app).";
|
|
274
|
+
};
|
|
275
|
+
readonly successCode: "200";
|
|
276
|
+
readonly success: z.ZodObject<{
|
|
277
|
+
entries: z.ZodArray<z.ZodObject<{
|
|
278
|
+
id: z.ZodNumber;
|
|
279
|
+
tableName: z.ZodString;
|
|
280
|
+
operation: z.ZodString;
|
|
281
|
+
rowId: z.ZodNullable<z.ZodString>;
|
|
282
|
+
userId: z.ZodNullable<z.ZodString>;
|
|
283
|
+
actor: z.ZodNullable<z.ZodString>;
|
|
284
|
+
createdAt: z.ZodString;
|
|
285
|
+
}, z.core.$strip>>;
|
|
286
|
+
}, z.core.$strip>;
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
export type Operations = typeof operations;
|
|
290
|
+
export type OperationKey = keyof Operations;
|
|
291
|
+
/** `"get /api/v1/users"` -> the paths that declare that method. */
|
|
292
|
+
export type PathsFor<M extends HttpMethod> = OperationKey extends infer K ? K extends `${M} ${infer P}` ? P : never : never;
|
|
293
|
+
export type OperationFor<M extends HttpMethod, P extends string> = `${M} ${P}` extends OperationKey ? Operations[`${M} ${P}`] : never;
|
|
294
|
+
/** Path parameters read straight off the path template — `{app}`, `{userId}`. */
|
|
295
|
+
export type PathParamNames<P extends string> = P extends `${string}{${infer Name}}${infer Rest}` ? Name | PathParamNames<Rest> : never;
|
|
296
|
+
export declare function lookup(method: string, path: string): OperationDef | undefined;
|
|
297
|
+
//# sourceMappingURL=operations.d.ts.map
|
|
@@ -0,0 +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;AAqBvB,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,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAA;IACjB,WAAW,EAAE,KAAK,GAAG,KAAK,CAAA;IAC1B,OAAO,EAAE,CAAC,CAAC,OAAO,CAAA;CACnB,CAAA;AAID,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0G0B,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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every operation the management API exposes, in one table.
|
|
3
|
+
*
|
|
4
|
+
* Three things read it: `buildOpenApiDocument` (in `./openapi.ts`) turns it
|
|
5
|
+
* into the published document, `api.ts` uses it to type its `request()` and to
|
|
6
|
+
* parse what comes back, and `apps/idp` validates request bodies with the same
|
|
7
|
+
* `input` schemas. Adding an endpoint means adding a row here; nothing else
|
|
8
|
+
* needs to learn about it.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { ApplicationListSchema, AuditListSchema, CreateUserApiKeyInput, CreateWorkspaceInput, InviteMemberInput, InviteMemberResult, MemberListSchema, OkSchema, UpdateMemberInput, UserApiKeyCreatedSchema, UserApiKeyListSchema, UserApiKeyValidationSchema, UserListSchema, ValidateUserApiKeyInput, WorkspaceCreatedSchema, WorkspaceListSchema, } from "./index.js";
|
|
12
|
+
const APP_PARAM = { app: "Application key (oauth_client.metadata.app)." };
|
|
13
|
+
export const operations = {
|
|
14
|
+
"get /api/v1/applications": {
|
|
15
|
+
summary: "List registered applications",
|
|
16
|
+
successCode: "200",
|
|
17
|
+
success: ApplicationListSchema,
|
|
18
|
+
},
|
|
19
|
+
"get /api/v1/users": {
|
|
20
|
+
summary: "List users",
|
|
21
|
+
successCode: "200",
|
|
22
|
+
success: UserListSchema,
|
|
23
|
+
},
|
|
24
|
+
"get /api/v1/workspaces": {
|
|
25
|
+
summary: "List workspaces",
|
|
26
|
+
successCode: "200",
|
|
27
|
+
success: WorkspaceListSchema,
|
|
28
|
+
},
|
|
29
|
+
"get /api/v1/apps/{app}/members": {
|
|
30
|
+
summary: "List app members",
|
|
31
|
+
permission: "member:read",
|
|
32
|
+
params: APP_PARAM,
|
|
33
|
+
successCode: "200",
|
|
34
|
+
success: MemberListSchema,
|
|
35
|
+
},
|
|
36
|
+
"post /api/v1/apps/{app}/members": {
|
|
37
|
+
summary: "Add or invite a member",
|
|
38
|
+
permission: "member:invite",
|
|
39
|
+
params: APP_PARAM,
|
|
40
|
+
input: InviteMemberInput,
|
|
41
|
+
successCode: "201",
|
|
42
|
+
success: InviteMemberResult,
|
|
43
|
+
},
|
|
44
|
+
"patch /api/v1/apps/{app}/members/{userId}": {
|
|
45
|
+
summary: "Update a member's role + permissions",
|
|
46
|
+
permission: "member:manage",
|
|
47
|
+
params: APP_PARAM,
|
|
48
|
+
input: UpdateMemberInput,
|
|
49
|
+
successCode: "200",
|
|
50
|
+
success: OkSchema,
|
|
51
|
+
},
|
|
52
|
+
"delete /api/v1/apps/{app}/members/{userId}": {
|
|
53
|
+
summary: "Remove a member",
|
|
54
|
+
permission: "member:manage",
|
|
55
|
+
params: APP_PARAM,
|
|
56
|
+
successCode: "200",
|
|
57
|
+
success: OkSchema,
|
|
58
|
+
},
|
|
59
|
+
"get /api/v1/apps/{app}/workspaces": {
|
|
60
|
+
summary: "List app workspaces",
|
|
61
|
+
permission: "workspace:read",
|
|
62
|
+
params: APP_PARAM,
|
|
63
|
+
successCode: "200",
|
|
64
|
+
success: WorkspaceListSchema,
|
|
65
|
+
},
|
|
66
|
+
"post /api/v1/apps/{app}/workspaces": {
|
|
67
|
+
summary: "Create a workspace",
|
|
68
|
+
permission: "workspace:create",
|
|
69
|
+
params: APP_PARAM,
|
|
70
|
+
input: CreateWorkspaceInput,
|
|
71
|
+
successCode: "201",
|
|
72
|
+
success: WorkspaceCreatedSchema,
|
|
73
|
+
},
|
|
74
|
+
"get /api/v1/apps/{app}/user-keys": {
|
|
75
|
+
summary: "List end-user API keys",
|
|
76
|
+
permission: "userkey:read",
|
|
77
|
+
params: APP_PARAM,
|
|
78
|
+
query: [
|
|
79
|
+
{ name: "userId", description: "Only keys owned by this user." },
|
|
80
|
+
{ name: "workspaceId", description: "Only keys bound to this workspace." },
|
|
81
|
+
],
|
|
82
|
+
successCode: "200",
|
|
83
|
+
success: UserApiKeyListSchema,
|
|
84
|
+
},
|
|
85
|
+
"post /api/v1/apps/{app}/user-keys": {
|
|
86
|
+
summary: "Mint an end-user API key (plaintext returned once)",
|
|
87
|
+
permission: "userkey:create",
|
|
88
|
+
params: APP_PARAM,
|
|
89
|
+
input: CreateUserApiKeyInput,
|
|
90
|
+
successCode: "201",
|
|
91
|
+
success: UserApiKeyCreatedSchema,
|
|
92
|
+
},
|
|
93
|
+
"post /api/v1/apps/{app}/user-keys/validate": {
|
|
94
|
+
summary: "Validate a presented end-user key (200 + valid discriminator)",
|
|
95
|
+
permission: "userkey:validate",
|
|
96
|
+
params: APP_PARAM,
|
|
97
|
+
input: ValidateUserApiKeyInput,
|
|
98
|
+
successCode: "200",
|
|
99
|
+
success: UserApiKeyValidationSchema,
|
|
100
|
+
},
|
|
101
|
+
"delete /api/v1/apps/{app}/user-keys/{id}": {
|
|
102
|
+
summary: "Revoke an end-user API key (idempotent)",
|
|
103
|
+
permission: "userkey:revoke",
|
|
104
|
+
params: APP_PARAM,
|
|
105
|
+
successCode: "200",
|
|
106
|
+
success: OkSchema,
|
|
107
|
+
},
|
|
108
|
+
"get /api/v1/apps/{app}/audit": {
|
|
109
|
+
summary: "List recent audit entries",
|
|
110
|
+
permission: "audit:read",
|
|
111
|
+
params: APP_PARAM,
|
|
112
|
+
successCode: "200",
|
|
113
|
+
success: AuditListSchema,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
export function lookup(method, path) {
|
|
117
|
+
return operations[`${method.toLowerCase()} ${path}`];
|
|
118
|
+
}
|
package/dist/src/session.d.ts
CHANGED
|
@@ -68,14 +68,28 @@ export type Idp = ReturnType<typeof createIdp>;
|
|
|
68
68
|
export declare function createIdp(options: IdpOptions): {
|
|
69
69
|
/** The Layer 0 client, for anything the session layer doesn't wrap. */
|
|
70
70
|
client: {
|
|
71
|
-
discover: () => Promise<import("./
|
|
71
|
+
discover: () => Promise<import("./wire.js").Discovery>;
|
|
72
72
|
authorizationUrl(input: import("./client.js").AuthorizationUrlInput): Promise<string>;
|
|
73
73
|
exchangeCode(input: {
|
|
74
74
|
code: string;
|
|
75
75
|
redirectUri: string;
|
|
76
76
|
codeVerifier: string;
|
|
77
|
-
}): Promise<
|
|
78
|
-
|
|
77
|
+
}): Promise<{
|
|
78
|
+
accessToken: string;
|
|
79
|
+
tokenType: string;
|
|
80
|
+
expiresIn: number | null;
|
|
81
|
+
refreshToken: string | null;
|
|
82
|
+
idToken: string | null;
|
|
83
|
+
scope: string | null;
|
|
84
|
+
}>;
|
|
85
|
+
refresh(refreshToken: string): Promise<{
|
|
86
|
+
accessToken: string;
|
|
87
|
+
tokenType: string;
|
|
88
|
+
expiresIn: number | null;
|
|
89
|
+
refreshToken: string | null;
|
|
90
|
+
idToken: string | null;
|
|
91
|
+
scope: string | null;
|
|
92
|
+
}>;
|
|
79
93
|
userinfo(accessToken: string): Promise<Claims>;
|
|
80
94
|
logoutUrl(input: {
|
|
81
95
|
idToken?: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/session.ts"],"names":[],"mappings":"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"}
|
package/dist/src/session.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* call doubles as a liveness ping — it is how a revocation at the IdP reaches
|
|
9
9
|
* the app, in minutes rather than in session-lengths.
|
|
10
10
|
*/
|
|
11
|
+
import { z } from "zod";
|
|
11
12
|
import { grants } from "./claims.js";
|
|
12
13
|
import { createIdpClient, createPkce, IdpError, } from "./client.js";
|
|
13
14
|
import { clearCookie, readCookie, serializeCookie } from "./cookie.js";
|
|
@@ -16,6 +17,16 @@ import { parseDuration } from "./duration.js";
|
|
|
16
17
|
export const DEFAULT_SESSION_COOKIE = "idp_session";
|
|
17
18
|
/** How long the login handshake may take before its state cookie is stale. */
|
|
18
19
|
const STATE_COOKIE_MAX_AGE_MS = 10 * 60_000;
|
|
20
|
+
/**
|
|
21
|
+
* What rides in the login state cookie. HMAC-signed, so tampering is caught
|
|
22
|
+
* before this ever parses — the schema is here to catch a cookie left over
|
|
23
|
+
* from an older version of the SDK, not an attacker.
|
|
24
|
+
*/
|
|
25
|
+
const StatePayloadSchema = z.object({
|
|
26
|
+
state: z.string().min(1),
|
|
27
|
+
codeVerifier: z.string().min(1),
|
|
28
|
+
next: z.string().optional(),
|
|
29
|
+
});
|
|
19
30
|
export function createIdp(options) {
|
|
20
31
|
const client = createIdpClient(options);
|
|
21
32
|
const store = options.sessions;
|
|
@@ -255,7 +266,7 @@ export function createIdp(options) {
|
|
|
255
266
|
throw new IdpError("login state cookie is missing or invalid", 400);
|
|
256
267
|
let payload;
|
|
257
268
|
try {
|
|
258
|
-
payload = JSON.parse(base64urlDecodeString(raw));
|
|
269
|
+
payload = StatePayloadSchema.parse(JSON.parse(base64urlDecodeString(raw)));
|
|
259
270
|
}
|
|
260
271
|
catch {
|
|
261
272
|
throw new IdpError("login state cookie is malformed", 400);
|