@apicircle/core 1.0.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.
@@ -0,0 +1,85 @@
1
+ import { WorkspaceSynced, WorkspaceLocal, Request, Folder, Environment, EnvPriorityRef, Assertion, MockServer, ExecutionPlan, WorkspaceSnapshotTrigger } from '@apicircle/shared';
2
+
3
+ type WorkspacePatch = {
4
+ kind: 'request.create';
5
+ request: Request;
6
+ } | {
7
+ kind: 'request.update';
8
+ id: string;
9
+ patch: Partial<Omit<Request, 'id' | 'createdAt'>>;
10
+ } | {
11
+ kind: 'request.delete';
12
+ id: string;
13
+ } | {
14
+ kind: 'folder.create';
15
+ folder: Folder;
16
+ } | {
17
+ kind: 'folder.delete';
18
+ id: string;
19
+ } | {
20
+ kind: 'folder.move';
21
+ id: string;
22
+ newParentId: string | null;
23
+ } | {
24
+ kind: 'environment.upsert';
25
+ environment: Environment;
26
+ } | {
27
+ kind: 'environment.delete';
28
+ name: string;
29
+ } | {
30
+ kind: 'environment.setActive';
31
+ name: string | null;
32
+ } | {
33
+ kind: 'environment.setPriority';
34
+ order: EnvPriorityRef[];
35
+ } | {
36
+ kind: 'assertion.upsert';
37
+ requestId: string;
38
+ assertion: Assertion;
39
+ } | {
40
+ kind: 'assertion.delete';
41
+ requestId: string;
42
+ assertionId: string;
43
+ } | {
44
+ kind: 'mock.upsert';
45
+ mock: MockServer;
46
+ } | {
47
+ kind: 'mock.delete';
48
+ id: string;
49
+ } | {
50
+ kind: 'plan.upsert';
51
+ plan: ExecutionPlan;
52
+ } | {
53
+ kind: 'plan.delete';
54
+ id: string;
55
+ } | {
56
+ kind: 'history.delete_run';
57
+ runId: string;
58
+ } | {
59
+ kind: 'history.delete_plan_run';
60
+ planRunId: string;
61
+ } | {
62
+ kind: 'history.purge';
63
+ olderThanMs: number;
64
+ } | {
65
+ kind: 'snapshot.capture';
66
+ trigger: WorkspaceSnapshotTrigger;
67
+ note?: string;
68
+ id?: string;
69
+ } | {
70
+ kind: 'snapshot.delete';
71
+ id: string;
72
+ } | {
73
+ kind: 'snapshot.restore';
74
+ id: string;
75
+ } | {
76
+ kind: 'snapshot.set_max_bytes';
77
+ maxBytes: number;
78
+ };
79
+ type WorkspacePatchKind = WorkspacePatch['kind'];
80
+ interface WorkspaceState {
81
+ synced: WorkspaceSynced;
82
+ local: WorkspaceLocal;
83
+ }
84
+
85
+ export type { WorkspaceState as W, WorkspacePatch as a, WorkspacePatchKind as b };
@@ -0,0 +1,85 @@
1
+ import { WorkspaceSynced, WorkspaceLocal, Request, Folder, Environment, EnvPriorityRef, Assertion, MockServer, ExecutionPlan, WorkspaceSnapshotTrigger } from '@apicircle/shared';
2
+
3
+ type WorkspacePatch = {
4
+ kind: 'request.create';
5
+ request: Request;
6
+ } | {
7
+ kind: 'request.update';
8
+ id: string;
9
+ patch: Partial<Omit<Request, 'id' | 'createdAt'>>;
10
+ } | {
11
+ kind: 'request.delete';
12
+ id: string;
13
+ } | {
14
+ kind: 'folder.create';
15
+ folder: Folder;
16
+ } | {
17
+ kind: 'folder.delete';
18
+ id: string;
19
+ } | {
20
+ kind: 'folder.move';
21
+ id: string;
22
+ newParentId: string | null;
23
+ } | {
24
+ kind: 'environment.upsert';
25
+ environment: Environment;
26
+ } | {
27
+ kind: 'environment.delete';
28
+ name: string;
29
+ } | {
30
+ kind: 'environment.setActive';
31
+ name: string | null;
32
+ } | {
33
+ kind: 'environment.setPriority';
34
+ order: EnvPriorityRef[];
35
+ } | {
36
+ kind: 'assertion.upsert';
37
+ requestId: string;
38
+ assertion: Assertion;
39
+ } | {
40
+ kind: 'assertion.delete';
41
+ requestId: string;
42
+ assertionId: string;
43
+ } | {
44
+ kind: 'mock.upsert';
45
+ mock: MockServer;
46
+ } | {
47
+ kind: 'mock.delete';
48
+ id: string;
49
+ } | {
50
+ kind: 'plan.upsert';
51
+ plan: ExecutionPlan;
52
+ } | {
53
+ kind: 'plan.delete';
54
+ id: string;
55
+ } | {
56
+ kind: 'history.delete_run';
57
+ runId: string;
58
+ } | {
59
+ kind: 'history.delete_plan_run';
60
+ planRunId: string;
61
+ } | {
62
+ kind: 'history.purge';
63
+ olderThanMs: number;
64
+ } | {
65
+ kind: 'snapshot.capture';
66
+ trigger: WorkspaceSnapshotTrigger;
67
+ note?: string;
68
+ id?: string;
69
+ } | {
70
+ kind: 'snapshot.delete';
71
+ id: string;
72
+ } | {
73
+ kind: 'snapshot.restore';
74
+ id: string;
75
+ } | {
76
+ kind: 'snapshot.set_max_bytes';
77
+ maxBytes: number;
78
+ };
79
+ type WorkspacePatchKind = WorkspacePatch['kind'];
80
+ interface WorkspaceState {
81
+ synced: WorkspaceSynced;
82
+ local: WorkspaceLocal;
83
+ }
84
+
85
+ export type { WorkspaceState as W, WorkspacePatch as a, WorkspacePatchKind as b };
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/auth/oauth2/__fixtures__/mockIdp.ts
21
+ var mockIdp_exports = {};
22
+ __export(mockIdp_exports, {
23
+ startMockIdp: () => startMockIdp
24
+ });
25
+ module.exports = __toCommonJS(mockIdp_exports);
26
+ var import_node_http = require("http");
27
+ async function startMockIdp() {
28
+ let nextAuthorizeError = null;
29
+ const deviceCodes = /* @__PURE__ */ new Map();
30
+ const server = (0, import_node_http.createServer)((req, res) => {
31
+ const url = new URL(req.url ?? "/", `http://127.0.0.1`);
32
+ res.setHeader("Access-Control-Allow-Origin", "*");
33
+ res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
34
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept");
35
+ if (req.method === "OPTIONS") {
36
+ res.statusCode = 204;
37
+ res.end();
38
+ return;
39
+ }
40
+ if (url.pathname === "/authorize") {
41
+ const redirectUri = url.searchParams.get("redirect_uri") ?? "";
42
+ const state = url.searchParams.get("state") ?? "";
43
+ const responseType = url.searchParams.get("response_type") ?? "code";
44
+ if (!redirectUri) {
45
+ res.statusCode = 400;
46
+ res.end("redirect_uri required");
47
+ return;
48
+ }
49
+ if (nextAuthorizeError) {
50
+ const params = new URLSearchParams({
51
+ error: nextAuthorizeError.error,
52
+ state
53
+ });
54
+ if (nextAuthorizeError.description) {
55
+ params.set("error_description", nextAuthorizeError.description);
56
+ }
57
+ res.statusCode = 302;
58
+ res.setHeader("Location", `${redirectUri}?${params.toString()}`);
59
+ res.end();
60
+ nextAuthorizeError = null;
61
+ return;
62
+ }
63
+ if (responseType === "token") {
64
+ res.statusCode = 302;
65
+ res.setHeader(
66
+ "Location",
67
+ `${redirectUri}#access_token=tk-implicit&token_type=Bearer&expires_in=3600&state=${state}`
68
+ );
69
+ res.end();
70
+ return;
71
+ }
72
+ res.statusCode = 302;
73
+ res.setHeader("Location", `${redirectUri}?code=test-code&state=${state}`);
74
+ res.end();
75
+ return;
76
+ }
77
+ if (url.pathname === "/token" && req.method === "POST") {
78
+ collectBody(req, (body) => {
79
+ const params = new URLSearchParams(body);
80
+ const grant = params.get("grant_type");
81
+ const clientId = params.get("client_id");
82
+ if (!clientId) {
83
+ jsonError(res, 400, "invalid_client", "client_id missing");
84
+ return;
85
+ }
86
+ if (grant === "client_credentials") {
87
+ jsonOk(res, {
88
+ access_token: `tk-cc-${clientId}`,
89
+ token_type: "Bearer",
90
+ expires_in: 3600,
91
+ scope: params.get("scope") ?? ""
92
+ });
93
+ return;
94
+ }
95
+ if (grant === "password") {
96
+ if (params.get("password") !== "hunter2") {
97
+ jsonError(res, 400, "invalid_grant", "wrong password");
98
+ return;
99
+ }
100
+ jsonOk(res, {
101
+ access_token: `tk-ropc-${params.get("username") ?? ""}`,
102
+ token_type: "Bearer",
103
+ expires_in: 3600,
104
+ refresh_token: "rt-ropc"
105
+ });
106
+ return;
107
+ }
108
+ if (grant === "authorization_code") {
109
+ if (params.get("code") !== "test-code") {
110
+ jsonError(res, 400, "invalid_grant", "unknown code");
111
+ return;
112
+ }
113
+ jsonOk(res, {
114
+ access_token: "tk-authcode",
115
+ token_type: "Bearer",
116
+ expires_in: 3600,
117
+ refresh_token: "rt-authcode",
118
+ scope: params.get("scope") ?? ""
119
+ });
120
+ return;
121
+ }
122
+ if (grant === "refresh_token") {
123
+ if (params.get("refresh_token") === "rt-rotated-once") {
124
+ jsonError(res, 400, "invalid_grant", "refresh already used");
125
+ return;
126
+ }
127
+ jsonOk(res, {
128
+ access_token: "tk-refreshed",
129
+ token_type: "Bearer",
130
+ expires_in: 3600,
131
+ // Rotate: hand back a fresh refresh_token.
132
+ refresh_token: "rt-rotated-once"
133
+ });
134
+ return;
135
+ }
136
+ if (grant === "urn:ietf:params:oauth:grant-type:device_code") {
137
+ const code = params.get("device_code") ?? "";
138
+ const state = deviceCodes.get(code);
139
+ if (!state) {
140
+ jsonError(res, 400, "invalid_grant", "unknown device_code");
141
+ return;
142
+ }
143
+ state.pollCount++;
144
+ if (state.pollCount < state.approvedAfter) {
145
+ jsonError(res, 400, "authorization_pending");
146
+ return;
147
+ }
148
+ jsonOk(res, {
149
+ access_token: "tk-device",
150
+ token_type: "Bearer",
151
+ expires_in: 3600
152
+ });
153
+ return;
154
+ }
155
+ jsonError(res, 400, "unsupported_grant_type");
156
+ });
157
+ return;
158
+ }
159
+ if (url.pathname === "/device_authorize" && req.method === "POST") {
160
+ const code = `dc-${Date.now()}`;
161
+ deviceCodes.set(code, { approvedAfter: 2, pollCount: 0 });
162
+ jsonOk(res, {
163
+ device_code: code,
164
+ user_code: "ABCD-EFGH",
165
+ verification_uri: `http://127.0.0.1:${server.address().port}/device`,
166
+ interval: 1,
167
+ expires_in: 600
168
+ });
169
+ return;
170
+ }
171
+ if (url.pathname === "/protected") {
172
+ const auth = req.headers["authorization"] ?? "";
173
+ if (!auth.toLowerCase().startsWith("bearer tk-")) {
174
+ res.statusCode = 401;
175
+ res.setHeader("Content-Type", "application/json");
176
+ res.end(JSON.stringify({ error: "unauthorized" }));
177
+ return;
178
+ }
179
+ jsonOk(res, { ok: true, sawAuth: auth });
180
+ return;
181
+ }
182
+ if (url.pathname === "/www-auth-bearer") {
183
+ res.statusCode = 401;
184
+ res.setHeader(
185
+ "WWW-Authenticate",
186
+ 'Bearer error="invalid_token", error_description="The access token expired"'
187
+ );
188
+ res.end();
189
+ return;
190
+ }
191
+ res.statusCode = 404;
192
+ res.end("Not Found");
193
+ });
194
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
195
+ const port = server.address().port;
196
+ const baseUrl = `http://127.0.0.1:${port}`;
197
+ return {
198
+ port,
199
+ url: (path) => `${baseUrl}${path}`,
200
+ setNextAuthorizeError: (err) => {
201
+ nextAuthorizeError = err;
202
+ },
203
+ approveDevice: () => {
204
+ for (const state of deviceCodes.values()) state.approvedAfter = state.pollCount + 1;
205
+ },
206
+ close: () => new Promise((resolve, reject) => {
207
+ server.close((err) => err ? reject(err) : resolve());
208
+ })
209
+ };
210
+ }
211
+ function jsonOk(res, body) {
212
+ res.statusCode = 200;
213
+ res.setHeader("Content-Type", "application/json");
214
+ res.end(JSON.stringify(body));
215
+ }
216
+ function jsonError(res, status, error, description) {
217
+ res.statusCode = status;
218
+ res.setHeader("Content-Type", "application/json");
219
+ const body = { error };
220
+ if (description) body.error_description = description;
221
+ res.end(JSON.stringify(body));
222
+ }
223
+ function collectBody(req, cb) {
224
+ const chunks = [];
225
+ req.on("data", (c) => chunks.push(c));
226
+ req.on("end", () => cb(Buffer.concat(chunks).toString("utf8")));
227
+ }
228
+ // Annotate the CommonJS export names for ESM import in node:
229
+ 0 && (module.exports = {
230
+ startMockIdp
231
+ });
232
+ //# sourceMappingURL=mock-idp.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/auth/oauth2/__fixtures__/mockIdp.ts"],"sourcesContent":["/**\n * Programmatic OAuth2 IdP for E2E tests. Implements every grant the\n * studio supports with the simplest possible logic — no real user\n * accounts, no real key material. Tokens are deterministic strings\n * keyed by `client_id` so assertions can match exactly.\n *\n * POST /token — token endpoint (every grant_type)\n * GET /authorize — auth-code / implicit redirect\n * POST /device_authorize — device flow user-code endpoint\n * GET /protected — resource server, requires Bearer header\n * POST /protected — same, lets tests assert on POST too\n * GET /www-auth-bearer — emits WWW-Authenticate Bearer error (no JSON)\n * POST /digest-protected — Digest 401-retry endpoint\n *\n * Spin up via `await startMockIdp()` and tear down with the returned\n * `close()`. Port is dynamic to avoid collisions in CI.\n */\n\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { type AddressInfo } from 'node:net';\n\ninterface DeviceState {\n approvedAfter: number; // poll count threshold\n pollCount: number;\n}\n\nexport interface MockIdp {\n port: number;\n url: (path: string) => string;\n /** Force the next /authorize redirect to include this error param. */\n setNextAuthorizeError: (err: { error: string; description?: string } | null) => void;\n /** Approve the current device flow (subsequent /token polls succeed). */\n approveDevice: () => void;\n close: () => Promise<void>;\n}\n\nexport async function startMockIdp(): Promise<MockIdp> {\n let nextAuthorizeError: { error: string; description?: string } | null = null;\n const deviceCodes = new Map<string, DeviceState>();\n\n const server: Server = createServer((req, res) => {\n const url = new URL(req.url ?? '/', `http://127.0.0.1`);\n\n // Permissive CORS so the web app's fetch can reach the IdP across\n // origins (e.g. http://localhost:5174 → http://127.0.0.1:<idp>).\n // Real IdPs aren't this permissive — but only the test harness\n // ever talks to this server.\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Accept');\n if (req.method === 'OPTIONS') {\n res.statusCode = 204;\n res.end();\n return;\n }\n\n if (url.pathname === '/authorize') {\n const redirectUri = url.searchParams.get('redirect_uri') ?? '';\n const state = url.searchParams.get('state') ?? '';\n const responseType = url.searchParams.get('response_type') ?? 'code';\n if (!redirectUri) {\n res.statusCode = 400;\n res.end('redirect_uri required');\n return;\n }\n if (nextAuthorizeError) {\n const params = new URLSearchParams({\n error: nextAuthorizeError.error,\n state,\n });\n if (nextAuthorizeError.description) {\n params.set('error_description', nextAuthorizeError.description);\n }\n res.statusCode = 302;\n res.setHeader('Location', `${redirectUri}?${params.toString()}`);\n res.end();\n nextAuthorizeError = null;\n return;\n }\n if (responseType === 'token') {\n // Implicit: redirect with fragment.\n res.statusCode = 302;\n res.setHeader(\n 'Location',\n `${redirectUri}#access_token=tk-implicit&token_type=Bearer&expires_in=3600&state=${state}`,\n );\n res.end();\n return;\n }\n // Default: auth-code redirect.\n res.statusCode = 302;\n res.setHeader('Location', `${redirectUri}?code=test-code&state=${state}`);\n res.end();\n return;\n }\n\n if (url.pathname === '/token' && req.method === 'POST') {\n collectBody(req, (body) => {\n const params = new URLSearchParams(body);\n const grant = params.get('grant_type');\n const clientId = params.get('client_id');\n if (!clientId) {\n jsonError(res, 400, 'invalid_client', 'client_id missing');\n return;\n }\n if (grant === 'client_credentials') {\n jsonOk(res, {\n access_token: `tk-cc-${clientId}`,\n token_type: 'Bearer',\n expires_in: 3600,\n scope: params.get('scope') ?? '',\n });\n return;\n }\n if (grant === 'password') {\n if (params.get('password') !== 'hunter2') {\n jsonError(res, 400, 'invalid_grant', 'wrong password');\n return;\n }\n jsonOk(res, {\n access_token: `tk-ropc-${params.get('username') ?? ''}`,\n token_type: 'Bearer',\n expires_in: 3600,\n refresh_token: 'rt-ropc',\n });\n return;\n }\n if (grant === 'authorization_code') {\n if (params.get('code') !== 'test-code') {\n jsonError(res, 400, 'invalid_grant', 'unknown code');\n return;\n }\n // PKCE clients carry code_verifier — accept any non-empty value.\n jsonOk(res, {\n access_token: 'tk-authcode',\n token_type: 'Bearer',\n expires_in: 3600,\n refresh_token: 'rt-authcode',\n scope: params.get('scope') ?? '',\n });\n return;\n }\n if (grant === 'refresh_token') {\n if (params.get('refresh_token') === 'rt-rotated-once') {\n jsonError(res, 400, 'invalid_grant', 'refresh already used');\n return;\n }\n jsonOk(res, {\n access_token: 'tk-refreshed',\n token_type: 'Bearer',\n expires_in: 3600,\n // Rotate: hand back a fresh refresh_token.\n refresh_token: 'rt-rotated-once',\n });\n return;\n }\n if (grant === 'urn:ietf:params:oauth:grant-type:device_code') {\n const code = params.get('device_code') ?? '';\n const state = deviceCodes.get(code);\n if (!state) {\n jsonError(res, 400, 'invalid_grant', 'unknown device_code');\n return;\n }\n state.pollCount++;\n if (state.pollCount < state.approvedAfter) {\n jsonError(res, 400, 'authorization_pending');\n return;\n }\n jsonOk(res, {\n access_token: 'tk-device',\n token_type: 'Bearer',\n expires_in: 3600,\n });\n return;\n }\n jsonError(res, 400, 'unsupported_grant_type');\n });\n return;\n }\n\n if (url.pathname === '/device_authorize' && req.method === 'POST') {\n const code = `dc-${Date.now()}`;\n deviceCodes.set(code, { approvedAfter: 2, pollCount: 0 });\n jsonOk(res, {\n device_code: code,\n user_code: 'ABCD-EFGH',\n verification_uri: `http://127.0.0.1:${(server.address() as AddressInfo).port}/device`,\n interval: 1,\n expires_in: 600,\n });\n return;\n }\n\n if (url.pathname === '/protected') {\n const auth = req.headers['authorization'] ?? '';\n if (!auth.toLowerCase().startsWith('bearer tk-')) {\n res.statusCode = 401;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ error: 'unauthorized' }));\n return;\n }\n jsonOk(res, { ok: true, sawAuth: auth });\n return;\n }\n\n if (url.pathname === '/www-auth-bearer') {\n res.statusCode = 401;\n res.setHeader(\n 'WWW-Authenticate',\n 'Bearer error=\"invalid_token\", error_description=\"The access token expired\"',\n );\n res.end();\n return;\n }\n\n res.statusCode = 404;\n res.end('Not Found');\n });\n\n await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));\n const port = (server.address() as AddressInfo).port;\n const baseUrl = `http://127.0.0.1:${port}`;\n\n return {\n port,\n url: (path) => `${baseUrl}${path}`,\n setNextAuthorizeError: (err) => {\n nextAuthorizeError = err;\n },\n approveDevice: () => {\n for (const state of deviceCodes.values()) state.approvedAfter = state.pollCount + 1;\n },\n close: () =>\n new Promise<void>((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n }),\n };\n}\n\nfunction jsonOk(res: ServerResponse, body: unknown): void {\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify(body));\n}\n\nfunction jsonError(res: ServerResponse, status: number, error: string, description?: string): void {\n res.statusCode = status;\n res.setHeader('Content-Type', 'application/json');\n const body: Record<string, string> = { error };\n if (description) body.error_description = description;\n res.end(JSON.stringify(body));\n}\n\nfunction collectBody(req: IncomingMessage, cb: (body: string) => void): void {\n const chunks: Buffer[] = [];\n req.on('data', (c: Buffer) => chunks.push(c));\n req.on('end', () => cb(Buffer.concat(chunks).toString('utf8')));\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Programmatic OAuth2 IdP for E2E tests. Implements every grant the
3
+ * studio supports with the simplest possible logic — no real user
4
+ * accounts, no real key material. Tokens are deterministic strings
5
+ * keyed by `client_id` so assertions can match exactly.
6
+ *
7
+ * POST /token — token endpoint (every grant_type)
8
+ * GET /authorize — auth-code / implicit redirect
9
+ * POST /device_authorize — device flow user-code endpoint
10
+ * GET /protected — resource server, requires Bearer header
11
+ * POST /protected — same, lets tests assert on POST too
12
+ * GET /www-auth-bearer — emits WWW-Authenticate Bearer error (no JSON)
13
+ * POST /digest-protected — Digest 401-retry endpoint
14
+ *
15
+ * Spin up via `await startMockIdp()` and tear down with the returned
16
+ * `close()`. Port is dynamic to avoid collisions in CI.
17
+ */
18
+ interface MockIdp {
19
+ port: number;
20
+ url: (path: string) => string;
21
+ /** Force the next /authorize redirect to include this error param. */
22
+ setNextAuthorizeError: (err: {
23
+ error: string;
24
+ description?: string;
25
+ } | null) => void;
26
+ /** Approve the current device flow (subsequent /token polls succeed). */
27
+ approveDevice: () => void;
28
+ close: () => Promise<void>;
29
+ }
30
+ declare function startMockIdp(): Promise<MockIdp>;
31
+
32
+ export { type MockIdp, startMockIdp };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Programmatic OAuth2 IdP for E2E tests. Implements every grant the
3
+ * studio supports with the simplest possible logic — no real user
4
+ * accounts, no real key material. Tokens are deterministic strings
5
+ * keyed by `client_id` so assertions can match exactly.
6
+ *
7
+ * POST /token — token endpoint (every grant_type)
8
+ * GET /authorize — auth-code / implicit redirect
9
+ * POST /device_authorize — device flow user-code endpoint
10
+ * GET /protected — resource server, requires Bearer header
11
+ * POST /protected — same, lets tests assert on POST too
12
+ * GET /www-auth-bearer — emits WWW-Authenticate Bearer error (no JSON)
13
+ * POST /digest-protected — Digest 401-retry endpoint
14
+ *
15
+ * Spin up via `await startMockIdp()` and tear down with the returned
16
+ * `close()`. Port is dynamic to avoid collisions in CI.
17
+ */
18
+ interface MockIdp {
19
+ port: number;
20
+ url: (path: string) => string;
21
+ /** Force the next /authorize redirect to include this error param. */
22
+ setNextAuthorizeError: (err: {
23
+ error: string;
24
+ description?: string;
25
+ } | null) => void;
26
+ /** Approve the current device flow (subsequent /token polls succeed). */
27
+ approveDevice: () => void;
28
+ close: () => Promise<void>;
29
+ }
30
+ declare function startMockIdp(): Promise<MockIdp>;
31
+
32
+ export { type MockIdp, startMockIdp };