@zitadel/testing 0.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.
- package/LICENSE +21 -0
- package/README.md +256 -0
- package/dist/app-env-D3W0GYhA.d.mts +122 -0
- package/dist/app-env-D3W0GYhA.d.mts.map +1 -0
- package/dist/app-runner.cjs +36 -0
- package/dist/app-runner.cjs.map +1 -0
- package/dist/app-runner.d.mts +1 -0
- package/dist/app-runner.mjs +37 -0
- package/dist/app-runner.mjs.map +1 -0
- package/dist/handshake-BPtWruO8.mjs +69 -0
- package/dist/handshake-BPtWruO8.mjs.map +1 -0
- package/dist/handshake-CggSIoxF.cjs +98 -0
- package/dist/handshake-CggSIoxF.cjs.map +1 -0
- package/dist/index.cjs +13 -0
- package/dist/index.d.mts +93 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/orchestration-C640_1Lg.mjs +38 -0
- package/dist/orchestration-C640_1Lg.mjs.map +1 -0
- package/dist/orchestration-D7QBgQ0l.cjs +73 -0
- package/dist/orchestration-D7QBgQ0l.cjs.map +1 -0
- package/dist/playwright.cjs +156 -0
- package/dist/playwright.cjs.map +1 -0
- package/dist/playwright.d.mts +124 -0
- package/dist/playwright.d.mts.map +1 -0
- package/dist/playwright.mjs +146 -0
- package/dist/playwright.mjs.map +1 -0
- package/dist/src-BIL0PdSd.cjs +2356 -0
- package/dist/src-BIL0PdSd.cjs.map +1 -0
- package/dist/src-DkG0V4A6.mjs +515 -0
- package/dist/src-DkG0V4A6.mjs.map +1 -0
- package/dist/supervisor.cjs +76 -0
- package/dist/supervisor.cjs.map +1 -0
- package/dist/supervisor.d.mts +1 -0
- package/dist/supervisor.mjs +77 -0
- package/dist/supervisor.mjs.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,2356 @@
|
|
|
1
|
+
const require_handshake = require("./handshake-CggSIoxF.cjs");
|
|
2
|
+
require("node:fs");
|
|
3
|
+
require("node:url");
|
|
4
|
+
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
let node_os = require("node:os");
|
|
6
|
+
let node_path = require("node:path");
|
|
7
|
+
let node_child_process = require("node:child_process");
|
|
8
|
+
let node_module = require("node:module");
|
|
9
|
+
let node_net = require("node:net");
|
|
10
|
+
let node_crypto = require("node:crypto");
|
|
11
|
+
//#region ../api/dist/runtime/base-url.mjs
|
|
12
|
+
let proxyPath = "";
|
|
13
|
+
function getProxyPath() {
|
|
14
|
+
return proxyPath;
|
|
15
|
+
}
|
|
16
|
+
function setProxyPath(path) {
|
|
17
|
+
proxyPath = path;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region ../api/dist/runtime/auth.mjs
|
|
21
|
+
/**
|
|
22
|
+
* Module-global bearer token used by the orval-generated client's
|
|
23
|
+
* custom fetch. Set once at command boot (the same lifecycle pattern
|
|
24
|
+
* as `base-url.ts`); every generated request reads from here.
|
|
25
|
+
*
|
|
26
|
+
* Keeping the token here means the CLI doesn't have to thread an
|
|
27
|
+
* `Authorization` header through every generated call site; orval's
|
|
28
|
+
* `mutator` wires `runtime/fetch.ts` in, and that file pulls the token
|
|
29
|
+
* from this module.
|
|
30
|
+
*/
|
|
31
|
+
let apiAuthToken;
|
|
32
|
+
function getApiAuthToken() {
|
|
33
|
+
return apiAuthToken;
|
|
34
|
+
}
|
|
35
|
+
function setApiAuthToken(token) {
|
|
36
|
+
apiAuthToken = token;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region ../api/dist/chunk-CfYAbeIz.mjs
|
|
40
|
+
var __defProp = Object.defineProperty;
|
|
41
|
+
var __exportAll = (all, no_symbols) => {
|
|
42
|
+
let target = {};
|
|
43
|
+
for (var name in all) __defProp(target, name, {
|
|
44
|
+
get: all[name],
|
|
45
|
+
enumerable: true
|
|
46
|
+
});
|
|
47
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
48
|
+
return target;
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region ../api/dist/runtime/fetch.mjs
|
|
52
|
+
/**
|
|
53
|
+
* Framework-neutral failure type the orval-generated client throws on
|
|
54
|
+
* any non-2xx response. Carries the HTTP status, the parsed error body
|
|
55
|
+
* (the spec's `{code, message, details?}` envelope when the server
|
|
56
|
+
* returned one), and the request URL — enough for callers to map to
|
|
57
|
+
* their own error taxonomy without re-implementing the fetch layer.
|
|
58
|
+
*
|
|
59
|
+
* Callers that don't care about the distinction can catch `ApiError`
|
|
60
|
+
* generically; callers that do (the CLI's `toZitadelError`) read
|
|
61
|
+
* `status` and decide.
|
|
62
|
+
*/
|
|
63
|
+
var ApiError = class extends Error {
|
|
64
|
+
status;
|
|
65
|
+
url;
|
|
66
|
+
body;
|
|
67
|
+
constructor(status, url, body, message) {
|
|
68
|
+
super(message);
|
|
69
|
+
this.name = "ApiError";
|
|
70
|
+
this.status = status;
|
|
71
|
+
this.url = url;
|
|
72
|
+
this.body = body;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* The orval `mutator` for the fetch client. Every generated operation
|
|
77
|
+
* routes its request through this function instead of the global
|
|
78
|
+
* `fetch`. We pin three concerns here so generated call sites stay
|
|
79
|
+
* focused on the shape of one HTTP call:
|
|
80
|
+
*
|
|
81
|
+
* - bearer auth — read from `runtime/auth.ts` and attached automatically;
|
|
82
|
+
* - non-2xx → throw — orval's stock client parses the body regardless
|
|
83
|
+
* of status, so callers would have to inspect every response. Throw
|
|
84
|
+
* `ApiError` on `!res.ok` so failures interrupt control flow;
|
|
85
|
+
* - body parsing — return the parsed JSON typed as `T` (the operation's
|
|
86
|
+
* return type, threaded through by orval), or `undefined` for the
|
|
87
|
+
* spec's `204`/`205`/`304` no-body responses.
|
|
88
|
+
*/
|
|
89
|
+
async function customFetch(url, options) {
|
|
90
|
+
const token = getApiAuthToken();
|
|
91
|
+
const headers = new Headers(options.headers);
|
|
92
|
+
if (token && !headers.has("authorization")) headers.set("authorization", `Bearer ${token}`);
|
|
93
|
+
const res = await fetch(url, {
|
|
94
|
+
...options,
|
|
95
|
+
headers
|
|
96
|
+
});
|
|
97
|
+
const rawBody = [
|
|
98
|
+
204,
|
|
99
|
+
205,
|
|
100
|
+
304
|
|
101
|
+
].includes(res.status) ? "" : await res.text();
|
|
102
|
+
const parsed = rawBody ? safeJsonParse(rawBody) : void 0;
|
|
103
|
+
if (!res.ok) {
|
|
104
|
+
const message = `${options.method ?? "GET"} ${url} returned ${res.status}`;
|
|
105
|
+
throw new ApiError(res.status, url, parsed, message);
|
|
106
|
+
}
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* `JSON.parse` wrapped so a non-JSON body (e.g. an HTML 502 from a
|
|
111
|
+
* proxy) becomes `{ raw: "<text>" }` instead of throwing inside
|
|
112
|
+
* `customFetch`. The thrown `ApiError` then carries something useful
|
|
113
|
+
* for the user to see.
|
|
114
|
+
*/
|
|
115
|
+
function safeJsonParse(text) {
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(text);
|
|
118
|
+
} catch {
|
|
119
|
+
return { raw: text };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region ../api/dist/generated/endpoints/zitadelNextGen.mjs
|
|
124
|
+
/**
|
|
125
|
+
* Generated by orval v8.10.0 🍺
|
|
126
|
+
* Do not edit manually.
|
|
127
|
+
* Zitadel NextGen
|
|
128
|
+
* This is the next generation of the Zitadel identity platform.
|
|
129
|
+
* OpenAPI spec version: 0.0.1
|
|
130
|
+
*/
|
|
131
|
+
var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
|
|
132
|
+
activateFlowDefinition: () => activateFlowDefinition,
|
|
133
|
+
authorizeDevice: () => authorizeDevice,
|
|
134
|
+
authorizeGet: () => authorizeGet,
|
|
135
|
+
createAuthAttempt: () => createAuthAttempt,
|
|
136
|
+
createBranding: () => createBranding,
|
|
137
|
+
createFlow: () => createFlow,
|
|
138
|
+
createFlowDefinition: () => createFlowDefinition,
|
|
139
|
+
createHandoff: () => createHandoff,
|
|
140
|
+
createProject: () => createProject,
|
|
141
|
+
createSchema: () => createSchema,
|
|
142
|
+
createSession: () => createSession,
|
|
143
|
+
createTeam: () => createTeam,
|
|
144
|
+
createUser: () => createUser,
|
|
145
|
+
deactivateFlowDefinition: () => deactivateFlowDefinition,
|
|
146
|
+
deleteFlowDefinition: () => deleteFlowDefinition,
|
|
147
|
+
deleteUserByID: () => deleteUserByID,
|
|
148
|
+
endSession: () => endSession,
|
|
149
|
+
exchangeHandoff: () => exchangeHandoff,
|
|
150
|
+
getActivateFlowDefinitionUrl: () => getActivateFlowDefinitionUrl,
|
|
151
|
+
getAuthAttempt: () => getAuthAttempt,
|
|
152
|
+
getAuthorizeDeviceUrl: () => getAuthorizeDeviceUrl,
|
|
153
|
+
getAuthorizeGetUrl: () => getAuthorizeGetUrl,
|
|
154
|
+
getBrandingById: () => getBrandingById,
|
|
155
|
+
getCreateAuthAttemptUrl: () => getCreateAuthAttemptUrl,
|
|
156
|
+
getCreateBrandingUrl: () => getCreateBrandingUrl,
|
|
157
|
+
getCreateFlowDefinitionUrl: () => getCreateFlowDefinitionUrl,
|
|
158
|
+
getCreateFlowUrl: () => getCreateFlowUrl,
|
|
159
|
+
getCreateHandoffUrl: () => getCreateHandoffUrl,
|
|
160
|
+
getCreateProjectUrl: () => getCreateProjectUrl,
|
|
161
|
+
getCreateSchemaUrl: () => getCreateSchemaUrl,
|
|
162
|
+
getCreateSessionUrl: () => getCreateSessionUrl,
|
|
163
|
+
getCreateTeamUrl: () => getCreateTeamUrl,
|
|
164
|
+
getCreateUserUrl: () => getCreateUserUrl,
|
|
165
|
+
getDeactivateFlowDefinitionUrl: () => getDeactivateFlowDefinitionUrl,
|
|
166
|
+
getDeleteFlowDefinitionUrl: () => getDeleteFlowDefinitionUrl,
|
|
167
|
+
getDeleteUserByIDUrl: () => getDeleteUserByIDUrl,
|
|
168
|
+
getEndSessionUrl: () => getEndSessionUrl,
|
|
169
|
+
getExchangeHandoffUrl: () => getExchangeHandoffUrl,
|
|
170
|
+
getFlowDefinition: () => getFlowDefinition,
|
|
171
|
+
getFlowStep: () => getFlowStep,
|
|
172
|
+
getGetAuthAttemptUrl: () => getGetAuthAttemptUrl,
|
|
173
|
+
getGetBrandingByIdUrl: () => getGetBrandingByIdUrl,
|
|
174
|
+
getGetFlowDefinitionUrl: () => getGetFlowDefinitionUrl,
|
|
175
|
+
getGetFlowStepUrl: () => getGetFlowStepUrl,
|
|
176
|
+
getGetHealthUrl: () => getGetHealthUrl,
|
|
177
|
+
getGetKeysUrl: () => getGetKeysUrl,
|
|
178
|
+
getGetLiveUrl: () => getGetLiveUrl,
|
|
179
|
+
getGetMySessionUrl: () => getGetMySessionUrl,
|
|
180
|
+
getGetMyUserUrl: () => getGetMyUserUrl,
|
|
181
|
+
getGetOpenIDConfigurationUrl: () => getGetOpenIDConfigurationUrl,
|
|
182
|
+
getGetProjectUrl: () => getGetProjectUrl,
|
|
183
|
+
getGetReadyUrl: () => getGetReadyUrl,
|
|
184
|
+
getGetSchemaByIdUrl: () => getGetSchemaByIdUrl,
|
|
185
|
+
getGetSessionUrl: () => getGetSessionUrl,
|
|
186
|
+
getGetTeamUrl: () => getGetTeamUrl,
|
|
187
|
+
getGetTokenUrl: () => getGetTokenUrl,
|
|
188
|
+
getGetUserByIDUrl: () => getGetUserByIDUrl,
|
|
189
|
+
getGetUserInfoUrl: () => getGetUserInfoUrl,
|
|
190
|
+
getHealth: () => getHealth,
|
|
191
|
+
getIntrospectUrl: () => getIntrospectUrl,
|
|
192
|
+
getIssueChallengeUrl: () => getIssueChallengeUrl,
|
|
193
|
+
getKeys: () => getKeys,
|
|
194
|
+
getListBrandingUrl: () => getListBrandingUrl,
|
|
195
|
+
getListFlowDefinitionsUrl: () => getListFlowDefinitionsUrl,
|
|
196
|
+
getListSchemasUrl: () => getListSchemasUrl,
|
|
197
|
+
getListSessionsUrl: () => getListSessionsUrl,
|
|
198
|
+
getListUserPasskeysUrl: () => getListUserPasskeysUrl,
|
|
199
|
+
getListUsersUrl: () => getListUsersUrl,
|
|
200
|
+
getLive: () => getLive,
|
|
201
|
+
getMySession: () => getMySession,
|
|
202
|
+
getMyUser: () => getMyUser,
|
|
203
|
+
getOpenIDConfiguration: () => getOpenIDConfiguration,
|
|
204
|
+
getPatchProjectUrl: () => getPatchProjectUrl,
|
|
205
|
+
getProject: () => getProject,
|
|
206
|
+
getQueryProjectsUrl: () => getQueryProjectsUrl,
|
|
207
|
+
getReady: () => getReady,
|
|
208
|
+
getRevokeMySessionUrl: () => getRevokeMySessionUrl,
|
|
209
|
+
getRevokeSessionUrl: () => getRevokeSessionUrl,
|
|
210
|
+
getRevokeTokenUrl: () => getRevokeTokenUrl,
|
|
211
|
+
getSchemaById: () => getSchemaById,
|
|
212
|
+
getSession: () => getSession,
|
|
213
|
+
getSetUserPasswordUrl: () => getSetUserPasswordUrl,
|
|
214
|
+
getSubmitFlowEventUrl: () => getSubmitFlowEventUrl,
|
|
215
|
+
getSubmitFlowStepUrl: () => getSubmitFlowStepUrl,
|
|
216
|
+
getTeam: () => getTeam,
|
|
217
|
+
getToken: () => getToken,
|
|
218
|
+
getUpdateFlowDefinitionUrl: () => getUpdateFlowDefinitionUrl,
|
|
219
|
+
getUserByID: () => getUserByID,
|
|
220
|
+
getUserInfo: () => getUserInfo,
|
|
221
|
+
getVerifyChallengeProofUrl: () => getVerifyChallengeProofUrl,
|
|
222
|
+
introspect: () => introspect,
|
|
223
|
+
issueChallenge: () => issueChallenge,
|
|
224
|
+
listBranding: () => listBranding,
|
|
225
|
+
listFlowDefinitions: () => listFlowDefinitions,
|
|
226
|
+
listSchemas: () => listSchemas,
|
|
227
|
+
listSessions: () => listSessions,
|
|
228
|
+
listUserPasskeys: () => listUserPasskeys,
|
|
229
|
+
listUsers: () => listUsers,
|
|
230
|
+
patchProject: () => patchProject,
|
|
231
|
+
queryProjects: () => queryProjects,
|
|
232
|
+
revokeMySession: () => revokeMySession,
|
|
233
|
+
revokeSession: () => revokeSession,
|
|
234
|
+
revokeToken: () => revokeToken,
|
|
235
|
+
setUserPassword: () => setUserPassword,
|
|
236
|
+
submitFlowEvent: () => submitFlowEvent,
|
|
237
|
+
submitFlowStep: () => submitFlowStep,
|
|
238
|
+
updateFlowDefinition: () => updateFlowDefinition,
|
|
239
|
+
verifyChallengeProof: () => verifyChallengeProof
|
|
240
|
+
});
|
|
241
|
+
const getGetOpenIDConfigurationUrl = () => {
|
|
242
|
+
return `${getProxyPath()}/.well-known/openid-configuration`;
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Retrieve the OpenID Connect configuration
|
|
246
|
+
* @summary Get OpenID Connect configuration
|
|
247
|
+
*/
|
|
248
|
+
const getOpenIDConfiguration = async (options) => {
|
|
249
|
+
return customFetch(getGetOpenIDConfigurationUrl(), {
|
|
250
|
+
...options,
|
|
251
|
+
method: "GET"
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
const getAuthorizeGetUrl = (params) => {
|
|
255
|
+
const normalizedParams = new URLSearchParams();
|
|
256
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
257
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
258
|
+
});
|
|
259
|
+
const stringifiedParams = normalizedParams.toString();
|
|
260
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/auth/authorize?${stringifiedParams}` : `${getProxyPath()}/auth/authorize`;
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* @summary Authorize a user
|
|
264
|
+
*/
|
|
265
|
+
const authorizeGet = async (params, options) => {
|
|
266
|
+
return customFetch(getAuthorizeGetUrl(params), {
|
|
267
|
+
...options,
|
|
268
|
+
method: "GET"
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
const getAuthorizeDeviceUrl = (params) => {
|
|
272
|
+
const normalizedParams = new URLSearchParams();
|
|
273
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
274
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
275
|
+
});
|
|
276
|
+
const stringifiedParams = normalizedParams.toString();
|
|
277
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/auth/device-authorization?${stringifiedParams}` : `${getProxyPath()}/auth/device-authorization`;
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* @summary Authorize a device
|
|
281
|
+
*/
|
|
282
|
+
const authorizeDevice = async (params, options) => {
|
|
283
|
+
return customFetch(getAuthorizeDeviceUrl(params), {
|
|
284
|
+
...options,
|
|
285
|
+
method: "GET"
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
const getEndSessionUrl = (params) => {
|
|
289
|
+
const normalizedParams = new URLSearchParams();
|
|
290
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
291
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
292
|
+
});
|
|
293
|
+
const stringifiedParams = normalizedParams.toString();
|
|
294
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/auth/end-session?${stringifiedParams}` : `${getProxyPath()}/auth/end-session`;
|
|
295
|
+
};
|
|
296
|
+
/**
|
|
297
|
+
* @summary End a session
|
|
298
|
+
*/
|
|
299
|
+
const endSession = async (params, options) => {
|
|
300
|
+
return customFetch(getEndSessionUrl(params), {
|
|
301
|
+
...options,
|
|
302
|
+
method: "GET"
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
const getIntrospectUrl = () => {
|
|
306
|
+
return `${getProxyPath()}/auth/introspect`;
|
|
307
|
+
};
|
|
308
|
+
/**
|
|
309
|
+
* @summary Introspect a token
|
|
310
|
+
*/
|
|
311
|
+
const introspect = async (introspectBody, options) => {
|
|
312
|
+
const formUrlEncoded = new URLSearchParams();
|
|
313
|
+
formUrlEncoded.append(`token`, introspectBody.token);
|
|
314
|
+
if (introspectBody.token_type_hint !== void 0) formUrlEncoded.append(`token_type_hint`, introspectBody.token_type_hint);
|
|
315
|
+
return customFetch(getIntrospectUrl(), {
|
|
316
|
+
...options,
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: {
|
|
319
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
320
|
+
...options?.headers
|
|
321
|
+
},
|
|
322
|
+
body: formUrlEncoded
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
const getGetKeysUrl = () => {
|
|
326
|
+
return `${getProxyPath()}/auth/keys`;
|
|
327
|
+
};
|
|
328
|
+
/**
|
|
329
|
+
* @summary Get public keys
|
|
330
|
+
*/
|
|
331
|
+
const getKeys = async (options) => {
|
|
332
|
+
return customFetch(getGetKeysUrl(), {
|
|
333
|
+
...options,
|
|
334
|
+
method: "GET"
|
|
335
|
+
});
|
|
336
|
+
};
|
|
337
|
+
const getRevokeTokenUrl = () => {
|
|
338
|
+
return `${getProxyPath()}/auth/revoke`;
|
|
339
|
+
};
|
|
340
|
+
/**
|
|
341
|
+
* @summary Revoke an access token or refresh token
|
|
342
|
+
*/
|
|
343
|
+
const revokeToken = async (revokeTokenBody, options) => {
|
|
344
|
+
const formUrlEncoded = new URLSearchParams();
|
|
345
|
+
if (revokeTokenBody.token !== void 0) formUrlEncoded.append(`token`, revokeTokenBody.token);
|
|
346
|
+
if (revokeTokenBody.token_type_hint !== void 0) formUrlEncoded.append(`token_type_hint`, revokeTokenBody.token_type_hint);
|
|
347
|
+
return customFetch(getRevokeTokenUrl(), {
|
|
348
|
+
...options,
|
|
349
|
+
method: "POST",
|
|
350
|
+
headers: {
|
|
351
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
352
|
+
...options?.headers
|
|
353
|
+
},
|
|
354
|
+
body: formUrlEncoded
|
|
355
|
+
});
|
|
356
|
+
};
|
|
357
|
+
const getGetTokenUrl = () => {
|
|
358
|
+
return `${getProxyPath()}/auth/token`;
|
|
359
|
+
};
|
|
360
|
+
/**
|
|
361
|
+
* @summary Get access token
|
|
362
|
+
*/
|
|
363
|
+
const getToken = async (getTokenBody, options) => {
|
|
364
|
+
const formUrlEncoded = new URLSearchParams();
|
|
365
|
+
if (getTokenBody.code !== void 0) formUrlEncoded.append(`code`, getTokenBody.code);
|
|
366
|
+
if (getTokenBody.client_assertion !== void 0) formUrlEncoded.append(`client_assertion`, getTokenBody.client_assertion);
|
|
367
|
+
if (getTokenBody.client_assertion_type !== void 0) formUrlEncoded.append(`client_assertion_type`, getTokenBody.client_assertion_type);
|
|
368
|
+
if (getTokenBody.client_id !== void 0) formUrlEncoded.append(`client_id`, getTokenBody.client_id);
|
|
369
|
+
if (getTokenBody.client_secret !== void 0) formUrlEncoded.append(`client_secret`, getTokenBody.client_secret);
|
|
370
|
+
if (getTokenBody.code_verifier !== void 0) formUrlEncoded.append(`code_verifier`, getTokenBody.code_verifier);
|
|
371
|
+
if (getTokenBody.grant_type !== void 0) formUrlEncoded.append(`grant_type`, getTokenBody.grant_type);
|
|
372
|
+
if (getTokenBody.redirect_uri !== void 0) formUrlEncoded.append(`redirect_uri`, getTokenBody.redirect_uri);
|
|
373
|
+
return customFetch(getGetTokenUrl(), {
|
|
374
|
+
...options,
|
|
375
|
+
method: "POST",
|
|
376
|
+
headers: {
|
|
377
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
378
|
+
...options?.headers
|
|
379
|
+
},
|
|
380
|
+
body: formUrlEncoded
|
|
381
|
+
});
|
|
382
|
+
};
|
|
383
|
+
const getGetUserInfoUrl = () => {
|
|
384
|
+
return `${getProxyPath()}/auth/userinfo`;
|
|
385
|
+
};
|
|
386
|
+
/**
|
|
387
|
+
* @summary Get user info
|
|
388
|
+
*/
|
|
389
|
+
const getUserInfo = async (options) => {
|
|
390
|
+
return customFetch(getGetUserInfoUrl(), {
|
|
391
|
+
...options,
|
|
392
|
+
method: "GET"
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
const getGetHealthUrl = () => {
|
|
396
|
+
return `${getProxyPath()}/healthz`;
|
|
397
|
+
};
|
|
398
|
+
/**
|
|
399
|
+
* Check whether the server is healthy
|
|
400
|
+
* @summary Check server health
|
|
401
|
+
*/
|
|
402
|
+
const getHealth = async (options) => {
|
|
403
|
+
return customFetch(getGetHealthUrl(), {
|
|
404
|
+
...options,
|
|
405
|
+
method: "GET"
|
|
406
|
+
});
|
|
407
|
+
};
|
|
408
|
+
const getGetLiveUrl = () => {
|
|
409
|
+
return `${getProxyPath()}/livez`;
|
|
410
|
+
};
|
|
411
|
+
/**
|
|
412
|
+
* Check whether the server is started
|
|
413
|
+
* @summary Check server liveness
|
|
414
|
+
*/
|
|
415
|
+
const getLive = async (options) => {
|
|
416
|
+
return customFetch(getGetLiveUrl(), {
|
|
417
|
+
...options,
|
|
418
|
+
method: "GET"
|
|
419
|
+
});
|
|
420
|
+
};
|
|
421
|
+
const getGetReadyUrl = () => {
|
|
422
|
+
return `${getProxyPath()}/readyz`;
|
|
423
|
+
};
|
|
424
|
+
/**
|
|
425
|
+
* Check whether the server is ready to accept requests
|
|
426
|
+
* @summary Check server readiness
|
|
427
|
+
*/
|
|
428
|
+
const getReady = async (options) => {
|
|
429
|
+
return customFetch(getGetReadyUrl(), {
|
|
430
|
+
...options,
|
|
431
|
+
method: "GET"
|
|
432
|
+
});
|
|
433
|
+
};
|
|
434
|
+
const getCreateUserUrl = (params) => {
|
|
435
|
+
const normalizedParams = new URLSearchParams();
|
|
436
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
437
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
438
|
+
});
|
|
439
|
+
const stringifiedParams = normalizedParams.toString();
|
|
440
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users?${stringifiedParams}` : `${getProxyPath()}/users`;
|
|
441
|
+
};
|
|
442
|
+
/**
|
|
443
|
+
* @summary Create user
|
|
444
|
+
*/
|
|
445
|
+
const createUser = async (createUserBody, params, options) => {
|
|
446
|
+
return customFetch(getCreateUserUrl(params), {
|
|
447
|
+
...options,
|
|
448
|
+
method: "POST",
|
|
449
|
+
headers: {
|
|
450
|
+
"Content-Type": "application/json",
|
|
451
|
+
...options?.headers
|
|
452
|
+
},
|
|
453
|
+
body: JSON.stringify(createUserBody)
|
|
454
|
+
});
|
|
455
|
+
};
|
|
456
|
+
const getListUsersUrl = (params) => {
|
|
457
|
+
const normalizedParams = new URLSearchParams();
|
|
458
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
459
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
460
|
+
});
|
|
461
|
+
const stringifiedParams = normalizedParams.toString();
|
|
462
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users?${stringifiedParams}` : `${getProxyPath()}/users`;
|
|
463
|
+
};
|
|
464
|
+
/**
|
|
465
|
+
* @summary List users
|
|
466
|
+
*/
|
|
467
|
+
const listUsers = async (params, options) => {
|
|
468
|
+
return customFetch(getListUsersUrl(params), {
|
|
469
|
+
...options,
|
|
470
|
+
method: "GET"
|
|
471
|
+
});
|
|
472
|
+
};
|
|
473
|
+
const getGetUserByIDUrl = (userId, params) => {
|
|
474
|
+
const normalizedParams = new URLSearchParams();
|
|
475
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
476
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
477
|
+
});
|
|
478
|
+
const stringifiedParams = normalizedParams.toString();
|
|
479
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}?${stringifiedParams}` : `${getProxyPath()}/users/${userId}`;
|
|
480
|
+
};
|
|
481
|
+
/**
|
|
482
|
+
* @summary Get user by ID
|
|
483
|
+
*/
|
|
484
|
+
const getUserByID = async (userId, params, options) => {
|
|
485
|
+
return customFetch(getGetUserByIDUrl(userId, params), {
|
|
486
|
+
...options,
|
|
487
|
+
method: "GET"
|
|
488
|
+
});
|
|
489
|
+
};
|
|
490
|
+
const getDeleteUserByIDUrl = (userId, params) => {
|
|
491
|
+
const normalizedParams = new URLSearchParams();
|
|
492
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
493
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
494
|
+
});
|
|
495
|
+
const stringifiedParams = normalizedParams.toString();
|
|
496
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}?${stringifiedParams}` : `${getProxyPath()}/users/${userId}`;
|
|
497
|
+
};
|
|
498
|
+
/**
|
|
499
|
+
* @summary Delete user by ID
|
|
500
|
+
*/
|
|
501
|
+
const deleteUserByID = async (userId, params, options) => {
|
|
502
|
+
return customFetch(getDeleteUserByIDUrl(userId, params), {
|
|
503
|
+
...options,
|
|
504
|
+
method: "DELETE"
|
|
505
|
+
});
|
|
506
|
+
};
|
|
507
|
+
const getListUserPasskeysUrl = (userId, params) => {
|
|
508
|
+
const normalizedParams = new URLSearchParams();
|
|
509
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
510
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
511
|
+
});
|
|
512
|
+
const stringifiedParams = normalizedParams.toString();
|
|
513
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}/passkeys?${stringifiedParams}` : `${getProxyPath()}/users/${userId}/passkeys`;
|
|
514
|
+
};
|
|
515
|
+
/**
|
|
516
|
+
* @summary List user passkeys
|
|
517
|
+
*/
|
|
518
|
+
const listUserPasskeys = async (userId, params, options) => {
|
|
519
|
+
return customFetch(getListUserPasskeysUrl(userId, params), {
|
|
520
|
+
...options,
|
|
521
|
+
method: "GET"
|
|
522
|
+
});
|
|
523
|
+
};
|
|
524
|
+
const getSetUserPasswordUrl = (userId, params) => {
|
|
525
|
+
const normalizedParams = new URLSearchParams();
|
|
526
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
527
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
528
|
+
});
|
|
529
|
+
const stringifiedParams = normalizedParams.toString();
|
|
530
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}/password?${stringifiedParams}` : `${getProxyPath()}/users/${userId}/password`;
|
|
531
|
+
};
|
|
532
|
+
/**
|
|
533
|
+
* @summary Set user password
|
|
534
|
+
*/
|
|
535
|
+
const setUserPassword = async (userId, setUserPasswordBody, params, options) => {
|
|
536
|
+
return customFetch(getSetUserPasswordUrl(userId, params), {
|
|
537
|
+
...options,
|
|
538
|
+
method: "PUT",
|
|
539
|
+
headers: {
|
|
540
|
+
"Content-Type": "application/json",
|
|
541
|
+
...options?.headers
|
|
542
|
+
},
|
|
543
|
+
body: JSON.stringify(setUserPasswordBody)
|
|
544
|
+
});
|
|
545
|
+
};
|
|
546
|
+
const getGetMyUserUrl = () => {
|
|
547
|
+
return `${getProxyPath()}/users/me`;
|
|
548
|
+
};
|
|
549
|
+
/**
|
|
550
|
+
* @summary Get my user information
|
|
551
|
+
*/
|
|
552
|
+
const getMyUser = async (options) => {
|
|
553
|
+
return customFetch(getGetMyUserUrl(), {
|
|
554
|
+
...options,
|
|
555
|
+
method: "GET"
|
|
556
|
+
});
|
|
557
|
+
};
|
|
558
|
+
const getCreateFlowUrl = () => {
|
|
559
|
+
return `${getProxyPath()}/flow`;
|
|
560
|
+
};
|
|
561
|
+
/**
|
|
562
|
+
* Resolves a flow definition based on purpose + audience context and returns
|
|
563
|
+
the first capability step. Creates a new session implicitly unless
|
|
564
|
+
`session_id` is provided (for step-up / reauth on an existing session).
|
|
565
|
+
|
|
566
|
+
The response contains an `id` field — the flow handle. Use it as the path
|
|
567
|
+
parameter for all subsequent `/flow/{id}/submit` and `/flow/{id}/event` calls.
|
|
568
|
+
|
|
569
|
+
The response also sets an encrypted `HttpOnly` cookie (`_zflow`) containing
|
|
570
|
+
the flow's orchestration state (current step, collected data, history).
|
|
571
|
+
The server is stateless between requests — all flow state lives in this
|
|
572
|
+
cookie. The browser sends it automatically on subsequent requests.
|
|
573
|
+
|
|
574
|
+
* @summary Start a new flow
|
|
575
|
+
*/
|
|
576
|
+
const createFlow = async (createFlowBody, options) => {
|
|
577
|
+
return customFetch(getCreateFlowUrl(), {
|
|
578
|
+
...options,
|
|
579
|
+
method: "POST",
|
|
580
|
+
headers: {
|
|
581
|
+
"Content-Type": "application/json",
|
|
582
|
+
...options?.headers
|
|
583
|
+
},
|
|
584
|
+
body: JSON.stringify(createFlowBody)
|
|
585
|
+
});
|
|
586
|
+
};
|
|
587
|
+
const getGetFlowStepUrl = (id) => {
|
|
588
|
+
return `${getProxyPath()}/flow/${id}`;
|
|
589
|
+
};
|
|
590
|
+
/**
|
|
591
|
+
* Returns the current capability step without advancing the state machine.
|
|
592
|
+
Useful for page reloads or re-rendering after a network error.
|
|
593
|
+
|
|
594
|
+
* @summary Get current step (re-render)
|
|
595
|
+
*/
|
|
596
|
+
const getFlowStep = async (id, options) => {
|
|
597
|
+
return customFetch(getGetFlowStepUrl(id), {
|
|
598
|
+
...options,
|
|
599
|
+
method: "GET"
|
|
600
|
+
});
|
|
601
|
+
};
|
|
602
|
+
const getSubmitFlowStepUrl = (id) => {
|
|
603
|
+
return `${getProxyPath()}/flow/${id}/submit`;
|
|
604
|
+
};
|
|
605
|
+
/**
|
|
606
|
+
* Submits user input for the current step. The server validates,
|
|
607
|
+
processes (e.g., verifies a credential), advances the state machine
|
|
608
|
+
through any invisible steps, and returns the next visible step.
|
|
609
|
+
|
|
610
|
+
The response sets an updated encrypted `HttpOnly` cookie (`_zflow`)
|
|
611
|
+
with the new flow state. The server is stateless — all orchestration
|
|
612
|
+
state is carried in this cookie between requests.
|
|
613
|
+
|
|
614
|
+
**Important:** The `id` in the response may differ from the `id` used in
|
|
615
|
+
the request. This happens when a flow pivots (pushes a new flow onto the
|
|
616
|
+
stack) or when a stacked flow completes (auto-pops to the parent flow).
|
|
617
|
+
Always use the `id` from the latest response for the next request.
|
|
618
|
+
|
|
619
|
+
## Flow completion
|
|
620
|
+
|
|
621
|
+
When `step.type` is `complete`, the flow is terminal. The `step.behavior`
|
|
622
|
+
field tells the frontend what to do:
|
|
623
|
+
|
|
624
|
+
| `behavior` | Action |
|
|
625
|
+
|------------- |------------------------------------------------------------|
|
|
626
|
+
| `redirect` | Navigate to `redirect_uri` (OIDC/SAML auth request done). |
|
|
627
|
+
| `show` | Render the step as a success screen (e.g., registration). |
|
|
628
|
+
|
|
629
|
+
A `complete` step is only returned when the **entire flow stack** is done.
|
|
630
|
+
If a stacked flow (e.g., recovery pivoted from login) finishes, the server
|
|
631
|
+
auto-pops to the parent flow and returns the parent's next step — the
|
|
632
|
+
frontend never sees a `complete` for intermediate flows.
|
|
633
|
+
|
|
634
|
+
* @summary Submit step data and advance
|
|
635
|
+
*/
|
|
636
|
+
const submitFlowStep = async (id, submitFlowStepBody, options) => {
|
|
637
|
+
return customFetch(getSubmitFlowStepUrl(id), {
|
|
638
|
+
...options,
|
|
639
|
+
method: "POST",
|
|
640
|
+
headers: {
|
|
641
|
+
"Content-Type": "application/json",
|
|
642
|
+
...options?.headers
|
|
643
|
+
},
|
|
644
|
+
body: JSON.stringify(submitFlowStepBody)
|
|
645
|
+
});
|
|
646
|
+
};
|
|
647
|
+
const getSubmitFlowEventUrl = (id) => {
|
|
648
|
+
return `${getProxyPath()}/flow/${id}/event`;
|
|
649
|
+
};
|
|
650
|
+
/**
|
|
651
|
+
* Submits telemetry or fingerprint data from the frontend.
|
|
652
|
+
Does not advance the state machine. Used for risk evaluation.
|
|
653
|
+
|
|
654
|
+
* @summary Submit client-side event
|
|
655
|
+
*/
|
|
656
|
+
const submitFlowEvent = async (id, submitFlowEventBody, options) => {
|
|
657
|
+
return customFetch(getSubmitFlowEventUrl(id), {
|
|
658
|
+
...options,
|
|
659
|
+
method: "POST",
|
|
660
|
+
headers: {
|
|
661
|
+
"Content-Type": "application/json",
|
|
662
|
+
...options?.headers
|
|
663
|
+
},
|
|
664
|
+
body: JSON.stringify(submitFlowEventBody)
|
|
665
|
+
});
|
|
666
|
+
};
|
|
667
|
+
const getCreateAuthAttemptUrl = () => {
|
|
668
|
+
return `${getProxyPath()}/auth_attempts`;
|
|
669
|
+
};
|
|
670
|
+
/**
|
|
671
|
+
* Starts a new authentication attempt. This is the entry point for the auth_attempts state machine.
|
|
672
|
+
|
|
673
|
+
An attempt is an ephemeral (15-minute TTL) state machine that drives a single authentication round.
|
|
674
|
+
It accepts factor challenges, verifies proofs, and completes into a session or handoff token.
|
|
675
|
+
|
|
676
|
+
Accepts a project_id and challenge_nonce (from POST /bootstrap/challenge). For step-up re-auth,
|
|
677
|
+
also include session_id to add factors to an existing session.
|
|
678
|
+
|
|
679
|
+
* @summary Create a new authentication attempt
|
|
680
|
+
*/
|
|
681
|
+
const createAuthAttempt = async (createAuthAttemptBody, options) => {
|
|
682
|
+
return customFetch(getCreateAuthAttemptUrl(), {
|
|
683
|
+
...options,
|
|
684
|
+
method: "POST",
|
|
685
|
+
headers: {
|
|
686
|
+
"Content-Type": "application/json",
|
|
687
|
+
...options?.headers
|
|
688
|
+
},
|
|
689
|
+
body: JSON.stringify(createAuthAttemptBody)
|
|
690
|
+
});
|
|
691
|
+
};
|
|
692
|
+
const getGetAuthAttemptUrl = (attemptId) => {
|
|
693
|
+
return `${getProxyPath()}/auth_attempts/${attemptId}`;
|
|
694
|
+
};
|
|
695
|
+
/**
|
|
696
|
+
* Polls the current state of an authentication attempt.
|
|
697
|
+
|
|
698
|
+
Returns the attempt's state, available factors for the next challenge,
|
|
699
|
+
challenges issued so far, and any errors preventing progress.
|
|
700
|
+
|
|
701
|
+
Use this for polling during long-running factor verifications (e.g., waiting for
|
|
702
|
+
a federated IdP callback or a device flow).
|
|
703
|
+
|
|
704
|
+
* @summary Get authentication attempt state
|
|
705
|
+
*/
|
|
706
|
+
const getAuthAttempt = async (attemptId, options) => {
|
|
707
|
+
return customFetch(getGetAuthAttemptUrl(attemptId), {
|
|
708
|
+
...options,
|
|
709
|
+
method: "GET"
|
|
710
|
+
});
|
|
711
|
+
};
|
|
712
|
+
const getIssueChallengeUrl = (attemptId) => {
|
|
713
|
+
return `${getProxyPath()}/auth_attempts/${attemptId}/challenges`;
|
|
714
|
+
};
|
|
715
|
+
/**
|
|
716
|
+
* Issues a single-factor verification challenge within an auth attempt.
|
|
717
|
+
|
|
718
|
+
This advances the authentication state machine by requesting a specific factor method
|
|
719
|
+
(password, passkey, TOTP, OTP via SMS, etc.). The server responds with challenge details
|
|
720
|
+
including method, metadata, and any UI hints. The client then verifies the proof
|
|
721
|
+
by calling POST /auth_attempts/{attempt_id}/challenges/{challenge_id}/verify.
|
|
722
|
+
|
|
723
|
+
* @summary Issue a factor challenge
|
|
724
|
+
*/
|
|
725
|
+
const issueChallenge = async (attemptId, issueChallengeBody, options) => {
|
|
726
|
+
return customFetch(getIssueChallengeUrl(attemptId), {
|
|
727
|
+
...options,
|
|
728
|
+
method: "POST",
|
|
729
|
+
headers: {
|
|
730
|
+
"Content-Type": "application/json",
|
|
731
|
+
...options?.headers
|
|
732
|
+
},
|
|
733
|
+
body: JSON.stringify(issueChallengeBody)
|
|
734
|
+
});
|
|
735
|
+
};
|
|
736
|
+
const getVerifyChallengeProofUrl = (attemptId, challengeId) => {
|
|
737
|
+
return `${getProxyPath()}/auth_attempts/${attemptId}/challenges/${challengeId}/verify`;
|
|
738
|
+
};
|
|
739
|
+
/**
|
|
740
|
+
* Submits a proof (credential, code, assertion) to verify a factor challenge.
|
|
741
|
+
|
|
742
|
+
The proof format depends on the challenge method. For example:
|
|
743
|
+
- `password` method: { password: "…" }
|
|
744
|
+
- `totp` method: { totp: { code: "123456" } }
|
|
745
|
+
- `passkey` method: { passkey: { assertion: "…" } }
|
|
746
|
+
- `recovery_code` method: { recovery_code: "…" }
|
|
747
|
+
|
|
748
|
+
On successful verification, the factor is written to the auth attempt.
|
|
749
|
+
The attempt moves to the next pending challenge or completes if all required factors are verified.
|
|
750
|
+
|
|
751
|
+
* @summary Verify a factor proof
|
|
752
|
+
*/
|
|
753
|
+
const verifyChallengeProof = async (attemptId, challengeId, verifyChallengeProofBody, options) => {
|
|
754
|
+
return customFetch(getVerifyChallengeProofUrl(attemptId, challengeId), {
|
|
755
|
+
...options,
|
|
756
|
+
method: "POST",
|
|
757
|
+
headers: {
|
|
758
|
+
"Content-Type": "application/json",
|
|
759
|
+
...options?.headers
|
|
760
|
+
},
|
|
761
|
+
body: JSON.stringify(verifyChallengeProofBody)
|
|
762
|
+
});
|
|
763
|
+
};
|
|
764
|
+
const getCreateHandoffUrl = (attemptId) => {
|
|
765
|
+
return `${getProxyPath()}/auth_attempts/${attemptId}/handoff`;
|
|
766
|
+
};
|
|
767
|
+
/**
|
|
768
|
+
* Completes the authentication attempt and mints a `handoff_token`.
|
|
769
|
+
|
|
770
|
+
Call this after all required factors have been verified and the attempt is in `completed` state.
|
|
771
|
+
The handoff token is short-lived (≤60 seconds) and must be exchanged at
|
|
772
|
+
POST /sessions/exchange to receive the final session and session_token.
|
|
773
|
+
|
|
774
|
+
The handoff token is:
|
|
775
|
+
- Single-use (atomic exchange, no retry)
|
|
776
|
+
- Audience-bound (requires matching project key for exchange)
|
|
777
|
+
- Idempotency-safe within a 5-minute window (see conventions)
|
|
778
|
+
|
|
779
|
+
* @summary Complete authentication and create handoff token
|
|
780
|
+
*/
|
|
781
|
+
const createHandoff = async (attemptId, options) => {
|
|
782
|
+
return customFetch(getCreateHandoffUrl(attemptId), {
|
|
783
|
+
...options,
|
|
784
|
+
method: "POST"
|
|
785
|
+
});
|
|
786
|
+
};
|
|
787
|
+
const getCreateProjectUrl = () => {
|
|
788
|
+
return `${getProxyPath()}/projects`;
|
|
789
|
+
};
|
|
790
|
+
/**
|
|
791
|
+
* @summary Create project
|
|
792
|
+
*/
|
|
793
|
+
const createProject = async (createProjectBody, options) => {
|
|
794
|
+
return customFetch(getCreateProjectUrl(), {
|
|
795
|
+
...options,
|
|
796
|
+
method: "POST",
|
|
797
|
+
headers: {
|
|
798
|
+
"Content-Type": "application/json",
|
|
799
|
+
...options?.headers
|
|
800
|
+
},
|
|
801
|
+
body: JSON.stringify(createProjectBody)
|
|
802
|
+
});
|
|
803
|
+
};
|
|
804
|
+
const getQueryProjectsUrl = () => {
|
|
805
|
+
return `${getProxyPath()}/projects/query`;
|
|
806
|
+
};
|
|
807
|
+
/**
|
|
808
|
+
* @summary Query projects
|
|
809
|
+
*/
|
|
810
|
+
const queryProjects = async (queryProjectsBody, options) => {
|
|
811
|
+
return customFetch(getQueryProjectsUrl(), {
|
|
812
|
+
...options,
|
|
813
|
+
method: "POST",
|
|
814
|
+
headers: {
|
|
815
|
+
"Content-Type": "application/json",
|
|
816
|
+
...options?.headers
|
|
817
|
+
},
|
|
818
|
+
body: JSON.stringify(queryProjectsBody)
|
|
819
|
+
});
|
|
820
|
+
};
|
|
821
|
+
const getGetProjectUrl = (projectId) => {
|
|
822
|
+
return `${getProxyPath()}/projects/${projectId}`;
|
|
823
|
+
};
|
|
824
|
+
/**
|
|
825
|
+
* Returns the current state of a project.
|
|
826
|
+
|
|
827
|
+
* @summary Get project
|
|
828
|
+
*/
|
|
829
|
+
const getProject = async (projectId, options) => {
|
|
830
|
+
return customFetch(getGetProjectUrl(projectId), {
|
|
831
|
+
...options,
|
|
832
|
+
method: "GET"
|
|
833
|
+
});
|
|
834
|
+
};
|
|
835
|
+
const getPatchProjectUrl = (projectId) => {
|
|
836
|
+
return `${getProxyPath()}/projects/${projectId}`;
|
|
837
|
+
};
|
|
838
|
+
/**
|
|
839
|
+
* Updates the state of a project.
|
|
840
|
+
|
|
841
|
+
* @summary Update project
|
|
842
|
+
*/
|
|
843
|
+
const patchProject = async (projectId, patchProjectBody, options) => {
|
|
844
|
+
return customFetch(getPatchProjectUrl(projectId), {
|
|
845
|
+
...options,
|
|
846
|
+
method: "PATCH",
|
|
847
|
+
headers: {
|
|
848
|
+
"Content-Type": "application/json",
|
|
849
|
+
...options?.headers
|
|
850
|
+
},
|
|
851
|
+
body: JSON.stringify(patchProjectBody)
|
|
852
|
+
});
|
|
853
|
+
};
|
|
854
|
+
const getCreateSessionUrl = () => {
|
|
855
|
+
return `${getProxyPath()}/sessions`;
|
|
856
|
+
};
|
|
857
|
+
/**
|
|
858
|
+
* Creates an anonymous session shell with no user and no factors (`state: building`).
|
|
859
|
+
|
|
860
|
+
This is optional — an `auth_attempt` will create a session implicitly if none is provided.
|
|
861
|
+
Use this explicitly when you want to:
|
|
862
|
+
- Pre-allocate a `session_id` before the user is known, so device/telemetry signals
|
|
863
|
+
can be correlated with the eventual authenticated session from the start.
|
|
864
|
+
- Track anonymous state (bot detection, device fingerprint) that survives until authentication.
|
|
865
|
+
|
|
866
|
+
The returned `session_token` authorises GET and DELETE on this session.
|
|
867
|
+
It is superseded when a handoff exchange completes — clients must replace it at that point.
|
|
868
|
+
|
|
869
|
+
Anonymous sessions expire aggressively (10-minute TTL). The TTL resets to the configured
|
|
870
|
+
full session TTL when the first authentication factor is written via a completing `auth_attempt`.
|
|
871
|
+
|
|
872
|
+
* @summary Create an anonymous session shell
|
|
873
|
+
*/
|
|
874
|
+
const createSession = async (createSessionBody, options) => {
|
|
875
|
+
return customFetch(getCreateSessionUrl(), {
|
|
876
|
+
...options,
|
|
877
|
+
method: "POST",
|
|
878
|
+
headers: {
|
|
879
|
+
"Content-Type": "application/json",
|
|
880
|
+
...options?.headers
|
|
881
|
+
},
|
|
882
|
+
body: JSON.stringify(createSessionBody)
|
|
883
|
+
});
|
|
884
|
+
};
|
|
885
|
+
const getListSessionsUrl = (params) => {
|
|
886
|
+
const normalizedParams = new URLSearchParams();
|
|
887
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
888
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
889
|
+
});
|
|
890
|
+
const stringifiedParams = normalizedParams.toString();
|
|
891
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions?${stringifiedParams}` : `${getProxyPath()}/sessions`;
|
|
892
|
+
};
|
|
893
|
+
/**
|
|
894
|
+
* Returns a paginated list of sessions for a project.
|
|
895
|
+
Requires a project service key (OAuth2 client credentials).
|
|
896
|
+
|
|
897
|
+
* @summary List sessions
|
|
898
|
+
*/
|
|
899
|
+
const listSessions = async (params, options) => {
|
|
900
|
+
return customFetch(getListSessionsUrl(params), {
|
|
901
|
+
...options,
|
|
902
|
+
method: "GET"
|
|
903
|
+
});
|
|
904
|
+
};
|
|
905
|
+
const getExchangeHandoffUrl = (params) => {
|
|
906
|
+
const normalizedParams = new URLSearchParams();
|
|
907
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
908
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
909
|
+
});
|
|
910
|
+
const stringifiedParams = normalizedParams.toString();
|
|
911
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions/exchange?${stringifiedParams}` : `${getProxyPath()}/sessions/exchange`;
|
|
912
|
+
};
|
|
913
|
+
/**
|
|
914
|
+
* Consumes a one-time `handoff_token` minted by `POST /auth_attempts/{id}/handoff`
|
|
915
|
+
and returns the resulting session and a `session_token`.
|
|
916
|
+
|
|
917
|
+
The server resolves the originating `auth_attempt` from the token and then:
|
|
918
|
+
|
|
919
|
+
| Originating auth_attempt | Outcome |
|
|
920
|
+
|---|---|
|
|
921
|
+
| No `session_id` | A new authenticated session is **created**. |
|
|
922
|
+
| `session_id` points to an anonymous shell | Existing session is **upgraded** — user and factors written in, TTL reset to full session TTL. |
|
|
923
|
+
| `session_id` points to an active session (step-up) | Existing session is **upgraded** — new factors merged, `assurance_levels[]` expanded. |
|
|
924
|
+
|
|
925
|
+
The response shape is identical in all three cases.
|
|
926
|
+
|
|
927
|
+
The `session_token` supersedes any previously issued `session_token` for the same session.
|
|
928
|
+
Clients must replace their stored token at this point.
|
|
929
|
+
|
|
930
|
+
Requires a project service key (OAuth2 client credentials).
|
|
931
|
+
|
|
932
|
+
* @summary Exchange handoff token for a session
|
|
933
|
+
*/
|
|
934
|
+
const exchangeHandoff = async (exchangeHandoffBody, params, options) => {
|
|
935
|
+
return customFetch(getExchangeHandoffUrl(params), {
|
|
936
|
+
...options,
|
|
937
|
+
method: "POST",
|
|
938
|
+
headers: {
|
|
939
|
+
"Content-Type": "application/json",
|
|
940
|
+
...options?.headers
|
|
941
|
+
},
|
|
942
|
+
body: JSON.stringify(exchangeHandoffBody)
|
|
943
|
+
});
|
|
944
|
+
};
|
|
945
|
+
const getGetSessionUrl = (sessionId, params) => {
|
|
946
|
+
const normalizedParams = new URLSearchParams();
|
|
947
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
948
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
949
|
+
});
|
|
950
|
+
const stringifiedParams = normalizedParams.toString();
|
|
951
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions/${sessionId}?${stringifiedParams}` : `${getProxyPath()}/sessions/${sessionId}`;
|
|
952
|
+
};
|
|
953
|
+
/**
|
|
954
|
+
* Returns the current state of a session including its factors and all currently
|
|
955
|
+
satisfied assurance levels.
|
|
956
|
+
|
|
957
|
+
`assurance_levels[]` may shrink over time as factor freshness windows expire,
|
|
958
|
+
without the session itself expiring. Use step-up authentication (a new `auth_attempt`
|
|
959
|
+
against the same `session_id`) to restore a dropped assurance level.
|
|
960
|
+
|
|
961
|
+
* @summary Get session state
|
|
962
|
+
*/
|
|
963
|
+
const getSession = async (sessionId, params, options) => {
|
|
964
|
+
return customFetch(getGetSessionUrl(sessionId, params), {
|
|
965
|
+
...options,
|
|
966
|
+
method: "GET"
|
|
967
|
+
});
|
|
968
|
+
};
|
|
969
|
+
const getRevokeSessionUrl = (sessionId, params) => {
|
|
970
|
+
const normalizedParams = new URLSearchParams();
|
|
971
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
972
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
973
|
+
});
|
|
974
|
+
const stringifiedParams = normalizedParams.toString();
|
|
975
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions/${sessionId}?${stringifiedParams}` : `${getProxyPath()}/sessions/${sessionId}`;
|
|
976
|
+
};
|
|
977
|
+
/**
|
|
978
|
+
* Revokes the session immediately (`state: revoked`).
|
|
979
|
+
|
|
980
|
+
This is the operator revoke path and requires the `session.delete` scope on a
|
|
981
|
+
project-bound credential. End-user logout with the `__nextgen_session` cookie is
|
|
982
|
+
`DELETE /sessions/me` (`nextgenSession` scheme).
|
|
983
|
+
|
|
984
|
+
After revocation, any tokens derived from this session are invalidated.
|
|
985
|
+
|
|
986
|
+
* @summary Revoke session
|
|
987
|
+
*/
|
|
988
|
+
const revokeSession = async (sessionId, params, options) => {
|
|
989
|
+
return customFetch(getRevokeSessionUrl(sessionId, params), {
|
|
990
|
+
...options,
|
|
991
|
+
method: "DELETE"
|
|
992
|
+
});
|
|
993
|
+
};
|
|
994
|
+
const getGetMySessionUrl = () => {
|
|
995
|
+
return `${getProxyPath()}/sessions/me`;
|
|
996
|
+
};
|
|
997
|
+
/**
|
|
998
|
+
* Returns the current state of the current session including its factors and all currently
|
|
999
|
+
satisfied assurance levels.
|
|
1000
|
+
|
|
1001
|
+
`assurance_levels[]` may shrink over time as factor freshness windows expire,
|
|
1002
|
+
without the session itself expiring. Use step-up authentication (a new `auth_attempt`
|
|
1003
|
+
against the same `session_id`) to restore a dropped assurance level.
|
|
1004
|
+
|
|
1005
|
+
* @summary Get my session state
|
|
1006
|
+
*/
|
|
1007
|
+
const getMySession = async (options) => {
|
|
1008
|
+
return customFetch(getGetMySessionUrl(), {
|
|
1009
|
+
...options,
|
|
1010
|
+
method: "GET"
|
|
1011
|
+
});
|
|
1012
|
+
};
|
|
1013
|
+
const getRevokeMySessionUrl = () => {
|
|
1014
|
+
return `${getProxyPath()}/sessions/me`;
|
|
1015
|
+
};
|
|
1016
|
+
/**
|
|
1017
|
+
* Revokes the session immediately (`state: revoked`). This is the logout operation.
|
|
1018
|
+
|
|
1019
|
+
The __nextgen_session cookie issued at creation (or superseded by a handoff exchange) is required.
|
|
1020
|
+
After revocation, any tokens derived from this session are invalidated including the cookie itself, which is cleared in the response.
|
|
1021
|
+
|
|
1022
|
+
* @summary Revoke my session
|
|
1023
|
+
*/
|
|
1024
|
+
const revokeMySession = async (options) => {
|
|
1025
|
+
return customFetch(getRevokeMySessionUrl(), {
|
|
1026
|
+
...options,
|
|
1027
|
+
method: "DELETE"
|
|
1028
|
+
});
|
|
1029
|
+
};
|
|
1030
|
+
const getCreateSchemaUrl = (params) => {
|
|
1031
|
+
const normalizedParams = new URLSearchParams();
|
|
1032
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1033
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1034
|
+
});
|
|
1035
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1036
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/schemas?${stringifiedParams}` : `${getProxyPath()}/schemas`;
|
|
1037
|
+
};
|
|
1038
|
+
/**
|
|
1039
|
+
* Create a new schema. The schema definition must include a unique $id field,
|
|
1040
|
+
which will be used to identify the schema in future requests. The $id must
|
|
1041
|
+
be a valid URI and should ideally point to the location where the schema
|
|
1042
|
+
can be accessed.
|
|
1043
|
+
|
|
1044
|
+
The schema can either be a concrete schema, e.g. a user schema, or a
|
|
1045
|
+
schema-url which will be resolved by the server.
|
|
1046
|
+
|
|
1047
|
+
* @summary Create new schema
|
|
1048
|
+
*/
|
|
1049
|
+
const createSchema = async (createSchemaBody, params, options) => {
|
|
1050
|
+
return customFetch(getCreateSchemaUrl(params), {
|
|
1051
|
+
...options,
|
|
1052
|
+
method: "POST",
|
|
1053
|
+
headers: {
|
|
1054
|
+
"Content-Type": "application/json",
|
|
1055
|
+
...options?.headers
|
|
1056
|
+
},
|
|
1057
|
+
body: JSON.stringify(createSchemaBody)
|
|
1058
|
+
});
|
|
1059
|
+
};
|
|
1060
|
+
const getListSchemasUrl = (params) => {
|
|
1061
|
+
const normalizedParams = new URLSearchParams();
|
|
1062
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1063
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1064
|
+
});
|
|
1065
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1066
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/schemas?${stringifiedParams}` : `${getProxyPath()}/schemas`;
|
|
1067
|
+
};
|
|
1068
|
+
/**
|
|
1069
|
+
* Retrieve a list of all schemas available in the system. This endpoint
|
|
1070
|
+
supports pagination and filtering based on schema attributes.
|
|
1071
|
+
|
|
1072
|
+
* @summary List all schemas
|
|
1073
|
+
*/
|
|
1074
|
+
const listSchemas = async (params, options) => {
|
|
1075
|
+
return customFetch(getListSchemasUrl(params), {
|
|
1076
|
+
...options,
|
|
1077
|
+
method: "GET"
|
|
1078
|
+
});
|
|
1079
|
+
};
|
|
1080
|
+
const getGetSchemaByIdUrl = (id, params) => {
|
|
1081
|
+
const normalizedParams = new URLSearchParams();
|
|
1082
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1083
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1084
|
+
});
|
|
1085
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1086
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/schemas/${id}?${stringifiedParams}` : `${getProxyPath()}/schemas/${id}`;
|
|
1087
|
+
};
|
|
1088
|
+
/**
|
|
1089
|
+
* Get a schema by its ID. This will return the default revision of the schema.
|
|
1090
|
+
* @summary Get schema by ID
|
|
1091
|
+
*/
|
|
1092
|
+
const getSchemaById = async (id, params, options) => {
|
|
1093
|
+
return customFetch(getGetSchemaByIdUrl(id, params), {
|
|
1094
|
+
...options,
|
|
1095
|
+
method: "GET"
|
|
1096
|
+
});
|
|
1097
|
+
};
|
|
1098
|
+
const getCreateFlowDefinitionUrl = () => {
|
|
1099
|
+
return `${getProxyPath()}/flow_definitions`;
|
|
1100
|
+
};
|
|
1101
|
+
/**
|
|
1102
|
+
* Creates a new flow definition.
|
|
1103
|
+
Flow definitions are templates that define the sequence of steps (capabilities)
|
|
1104
|
+
for a particular user journey (e.g., registration, login, password reset).
|
|
1105
|
+
|
|
1106
|
+
Flow definitions are created based on the flow definition schema, which includes the flow's purpose, audience, and the steps involved.
|
|
1107
|
+
|
|
1108
|
+
* @summary Create a new flow definition
|
|
1109
|
+
*/
|
|
1110
|
+
const createFlowDefinition = async (createFlowDefinitionBody, options) => {
|
|
1111
|
+
return customFetch(getCreateFlowDefinitionUrl(), {
|
|
1112
|
+
...options,
|
|
1113
|
+
method: "POST",
|
|
1114
|
+
headers: {
|
|
1115
|
+
"Content-Type": "application/json",
|
|
1116
|
+
...options?.headers
|
|
1117
|
+
},
|
|
1118
|
+
body: JSON.stringify(createFlowDefinitionBody)
|
|
1119
|
+
});
|
|
1120
|
+
};
|
|
1121
|
+
const getListFlowDefinitionsUrl = (params) => {
|
|
1122
|
+
const normalizedParams = new URLSearchParams();
|
|
1123
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1124
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1125
|
+
});
|
|
1126
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1127
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions?${stringifiedParams}` : `${getProxyPath()}/flow_definitions`;
|
|
1128
|
+
};
|
|
1129
|
+
/**
|
|
1130
|
+
* Retrieves a list of all flow definitions.
|
|
1131
|
+
This endpoint can be used to view existing flow definitions and their configurations.
|
|
1132
|
+
|
|
1133
|
+
* @summary List flow definitions
|
|
1134
|
+
*/
|
|
1135
|
+
const listFlowDefinitions = async (params, options) => {
|
|
1136
|
+
return customFetch(getListFlowDefinitionsUrl(params), {
|
|
1137
|
+
...options,
|
|
1138
|
+
method: "GET"
|
|
1139
|
+
});
|
|
1140
|
+
};
|
|
1141
|
+
const getGetFlowDefinitionUrl = (id, params) => {
|
|
1142
|
+
const normalizedParams = new URLSearchParams();
|
|
1143
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1144
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1145
|
+
});
|
|
1146
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1147
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions/${id}?${stringifiedParams}` : `${getProxyPath()}/flow_definitions/${id}`;
|
|
1148
|
+
};
|
|
1149
|
+
/**
|
|
1150
|
+
* Get a flow definition by id
|
|
1151
|
+
* @summary Get a flow definition by id
|
|
1152
|
+
*/
|
|
1153
|
+
const getFlowDefinition = async (id, params, options) => {
|
|
1154
|
+
return customFetch(getGetFlowDefinitionUrl(id, params), {
|
|
1155
|
+
...options,
|
|
1156
|
+
method: "GET"
|
|
1157
|
+
});
|
|
1158
|
+
};
|
|
1159
|
+
const getUpdateFlowDefinitionUrl = (id, params) => {
|
|
1160
|
+
const normalizedParams = new URLSearchParams();
|
|
1161
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1162
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1163
|
+
});
|
|
1164
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1165
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions/${id}?${stringifiedParams}` : `${getProxyPath()}/flow_definitions/${id}`;
|
|
1166
|
+
};
|
|
1167
|
+
/**
|
|
1168
|
+
* Update a flow definition by id. This endpoint replaces the existing flow definition.
|
|
1169
|
+
If `flow_definition.status` is omitted, the current status is preserved
|
|
1170
|
+
|
|
1171
|
+
* @summary Update a flow definition by id
|
|
1172
|
+
*/
|
|
1173
|
+
const updateFlowDefinition = async (id, updateFlowDefinitionBody, params, options) => {
|
|
1174
|
+
return customFetch(getUpdateFlowDefinitionUrl(id, params), {
|
|
1175
|
+
...options,
|
|
1176
|
+
method: "PUT",
|
|
1177
|
+
headers: {
|
|
1178
|
+
"Content-Type": "application/json",
|
|
1179
|
+
...options?.headers
|
|
1180
|
+
},
|
|
1181
|
+
body: JSON.stringify(updateFlowDefinitionBody)
|
|
1182
|
+
});
|
|
1183
|
+
};
|
|
1184
|
+
const getDeleteFlowDefinitionUrl = (id, params) => {
|
|
1185
|
+
const normalizedParams = new URLSearchParams();
|
|
1186
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1187
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1188
|
+
});
|
|
1189
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1190
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions/${id}?${stringifiedParams}` : `${getProxyPath()}/flow_definitions/${id}`;
|
|
1191
|
+
};
|
|
1192
|
+
/**
|
|
1193
|
+
* Delete a flow definition by id.
|
|
1194
|
+
If the flow definition is currently being used by a flow, the deletion will fail.
|
|
1195
|
+
If the flow definition is the last active flow definition for a given purpose, the deletion will fail to prevent disruption of new flows being started for that purpose.
|
|
1196
|
+
|
|
1197
|
+
* @summary Delete a flow definition by id
|
|
1198
|
+
*/
|
|
1199
|
+
const deleteFlowDefinition = async (id, params, options) => {
|
|
1200
|
+
return customFetch(getDeleteFlowDefinitionUrl(id, params), {
|
|
1201
|
+
...options,
|
|
1202
|
+
method: "DELETE"
|
|
1203
|
+
});
|
|
1204
|
+
};
|
|
1205
|
+
const getActivateFlowDefinitionUrl = (id, params) => {
|
|
1206
|
+
const normalizedParams = new URLSearchParams();
|
|
1207
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1208
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1209
|
+
});
|
|
1210
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1211
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions/${id}/activate?${stringifiedParams}` : `${getProxyPath()}/flow_definitions/${id}/activate`;
|
|
1212
|
+
};
|
|
1213
|
+
/**
|
|
1214
|
+
* Activate a flow definition by transitioning it from a `draft` state to an `active` state.
|
|
1215
|
+
Alternatively, the status of a flow definition can also be set via the `POST /flow_definitions` and `PUT /flow_definitions/{id}` endpoints by setting the `status` attribute in the flow definition payload.
|
|
1216
|
+
|
|
1217
|
+
* @summary Activate a flow definition by ID.
|
|
1218
|
+
*/
|
|
1219
|
+
const activateFlowDefinition = async (id, params, options) => {
|
|
1220
|
+
return customFetch(getActivateFlowDefinitionUrl(id, params), {
|
|
1221
|
+
...options,
|
|
1222
|
+
method: "POST"
|
|
1223
|
+
});
|
|
1224
|
+
};
|
|
1225
|
+
const getDeactivateFlowDefinitionUrl = (id, params) => {
|
|
1226
|
+
const normalizedParams = new URLSearchParams();
|
|
1227
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1228
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1229
|
+
});
|
|
1230
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1231
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions/${id}/deactivate?${stringifiedParams}` : `${getProxyPath()}/flow_definitions/${id}/deactivate`;
|
|
1232
|
+
};
|
|
1233
|
+
/**
|
|
1234
|
+
* Deactivates a flow definition in the `active` state by transitioning it to the `draft` state.
|
|
1235
|
+
Flow definitions in `draft` state cannot be used to start new flows. Existing flows that use the deactivated flow definition must gracefully handle this.
|
|
1236
|
+
Alternatively, the status of a flow definition can also be set via the `POST /flow_definitions` and `PUT /flow_definitions/{id}` endpoints by setting the `status` attribute in the flow definition payload.
|
|
1237
|
+
|
|
1238
|
+
* @summary Deactivate a flow definition by ID.
|
|
1239
|
+
*/
|
|
1240
|
+
const deactivateFlowDefinition = async (id, params, options) => {
|
|
1241
|
+
return customFetch(getDeactivateFlowDefinitionUrl(id, params), {
|
|
1242
|
+
...options,
|
|
1243
|
+
method: "POST"
|
|
1244
|
+
});
|
|
1245
|
+
};
|
|
1246
|
+
const getCreateTeamUrl = (params) => {
|
|
1247
|
+
const normalizedParams = new URLSearchParams();
|
|
1248
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1249
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1250
|
+
});
|
|
1251
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1252
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams?${stringifiedParams}` : `${getProxyPath()}/teams`;
|
|
1253
|
+
};
|
|
1254
|
+
/**
|
|
1255
|
+
* @summary Create team
|
|
1256
|
+
*/
|
|
1257
|
+
const createTeam = async (createTeamBody, params, options) => {
|
|
1258
|
+
return customFetch(getCreateTeamUrl(params), {
|
|
1259
|
+
...options,
|
|
1260
|
+
method: "POST",
|
|
1261
|
+
headers: {
|
|
1262
|
+
"Content-Type": "application/json",
|
|
1263
|
+
...options?.headers
|
|
1264
|
+
},
|
|
1265
|
+
body: JSON.stringify(createTeamBody)
|
|
1266
|
+
});
|
|
1267
|
+
};
|
|
1268
|
+
const getGetTeamUrl = (teamId, params) => {
|
|
1269
|
+
const normalizedParams = new URLSearchParams();
|
|
1270
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1271
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1272
|
+
});
|
|
1273
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1274
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/${teamId}?${stringifiedParams}` : `${getProxyPath()}/teams/${teamId}`;
|
|
1275
|
+
};
|
|
1276
|
+
/**
|
|
1277
|
+
* Returns the current state of a team.
|
|
1278
|
+
|
|
1279
|
+
* @summary Get team
|
|
1280
|
+
*/
|
|
1281
|
+
const getTeam = async (teamId, params, options) => {
|
|
1282
|
+
return customFetch(getGetTeamUrl(teamId, params), {
|
|
1283
|
+
...options,
|
|
1284
|
+
method: "GET"
|
|
1285
|
+
});
|
|
1286
|
+
};
|
|
1287
|
+
const getCreateBrandingUrl = (params) => {
|
|
1288
|
+
const normalizedParams = new URLSearchParams();
|
|
1289
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1290
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1291
|
+
});
|
|
1292
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1293
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/branding?${stringifiedParams}` : `${getProxyPath()}/branding`;
|
|
1294
|
+
};
|
|
1295
|
+
/**
|
|
1296
|
+
* Publishes a new immutable branding revision for the project. Branding
|
|
1297
|
+
revisions cannot be updated or deleted; every edit publishes a new
|
|
1298
|
+
revision, and flow responses resolve the latest revision per project
|
|
1299
|
+
(see ADR 040).
|
|
1300
|
+
|
|
1301
|
+
The `liquid_template` is validated lexically on save (size, encoding,
|
|
1302
|
+
banned patterns such as `<script>` tags, inline event handlers, and the
|
|
1303
|
+
`| raw` filter). Authoritative LiquidJS validation runs at authoring
|
|
1304
|
+
time via `zitadel plan` / `zitadel apply`.
|
|
1305
|
+
|
|
1306
|
+
* @summary Publish a new branding revision
|
|
1307
|
+
*/
|
|
1308
|
+
const createBranding = async (createBrandingBody, params, options) => {
|
|
1309
|
+
return customFetch(getCreateBrandingUrl(params), {
|
|
1310
|
+
...options,
|
|
1311
|
+
method: "POST",
|
|
1312
|
+
headers: {
|
|
1313
|
+
"Content-Type": "application/json",
|
|
1314
|
+
...options?.headers
|
|
1315
|
+
},
|
|
1316
|
+
body: JSON.stringify(createBrandingBody)
|
|
1317
|
+
});
|
|
1318
|
+
};
|
|
1319
|
+
const getListBrandingUrl = (params) => {
|
|
1320
|
+
const normalizedParams = new URLSearchParams();
|
|
1321
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1322
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1323
|
+
});
|
|
1324
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1325
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/branding?${stringifiedParams}` : `${getProxyPath()}/branding`;
|
|
1326
|
+
};
|
|
1327
|
+
/**
|
|
1328
|
+
* Lists branding revisions for the project, newest first, capped at the
|
|
1329
|
+
100 most recent. The first entry is the revision flow responses
|
|
1330
|
+
currently resolve. Deliberately unpaginated in v1 — list endpoints
|
|
1331
|
+
gain a real query mechanism together (ADR 031); advertising pagination
|
|
1332
|
+
parameters the server ignores would be worse than none.
|
|
1333
|
+
|
|
1334
|
+
* @summary List branding revisions
|
|
1335
|
+
*/
|
|
1336
|
+
const listBranding = async (params, options) => {
|
|
1337
|
+
return customFetch(getListBrandingUrl(params), {
|
|
1338
|
+
...options,
|
|
1339
|
+
method: "GET"
|
|
1340
|
+
});
|
|
1341
|
+
};
|
|
1342
|
+
const getGetBrandingByIdUrl = (id, params) => {
|
|
1343
|
+
const normalizedParams = new URLSearchParams();
|
|
1344
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1345
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1346
|
+
});
|
|
1347
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1348
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/branding/${id}?${stringifiedParams}` : `${getProxyPath()}/branding/${id}`;
|
|
1349
|
+
};
|
|
1350
|
+
/**
|
|
1351
|
+
* Retrieves a single branding revision, including its stored configuration.
|
|
1352
|
+
* @summary Get a branding revision by ID
|
|
1353
|
+
*/
|
|
1354
|
+
const getBrandingById = async (id, params, options) => {
|
|
1355
|
+
return customFetch(getGetBrandingByIdUrl(id, params), {
|
|
1356
|
+
...options,
|
|
1357
|
+
method: "GET"
|
|
1358
|
+
});
|
|
1359
|
+
};
|
|
1360
|
+
//#endregion
|
|
1361
|
+
//#region ../api/dist/runtime/api-factory.mjs
|
|
1362
|
+
/**
|
|
1363
|
+
* Factory for the typed Zitadel client every consumer reaches for.
|
|
1364
|
+
*
|
|
1365
|
+
* Wraps the orval-generated endpoints in a {@link Proxy} so each
|
|
1366
|
+
* generated function sees the right base URL and (optionally) the
|
|
1367
|
+
* right bearer token at call time — without the caller having to
|
|
1368
|
+
* thread either through every operation, and without exposing the
|
|
1369
|
+
* module-globals (`setProxyPath` / `setApiAuthToken`) as part of the
|
|
1370
|
+
* public API.
|
|
1371
|
+
*
|
|
1372
|
+
* Per-instance isolation: multiple clients can coexist in one process
|
|
1373
|
+
* (different servers, different tokens). Their Proxy `get` traps set
|
|
1374
|
+
* the globals synchronously before invoking the underlying generated
|
|
1375
|
+
* function, which reads them at the top of `customFetch` before any
|
|
1376
|
+
* `await`. Two clients running interleaved within a single JS turn
|
|
1377
|
+
* are safe; two clients running in parallel across awaits could
|
|
1378
|
+
* clobber each other — not a pattern any current consumer uses.
|
|
1379
|
+
*/
|
|
1380
|
+
/**
|
|
1381
|
+
* Build a typed Zitadel client pre-bound to a base URL and (optionally)
|
|
1382
|
+
* a bearer token. Every method on the returned object mirrors a
|
|
1383
|
+
* generated orval function:
|
|
1384
|
+
*
|
|
1385
|
+
* const client = createZitadelClient({ baseUrl, token });
|
|
1386
|
+
* await client.createProject({ previewOrigins: [] });
|
|
1387
|
+
* await client.createSchema(body, { project_id });
|
|
1388
|
+
*/
|
|
1389
|
+
function createZitadelClient(opts) {
|
|
1390
|
+
let baseUrl = opts.baseUrl;
|
|
1391
|
+
while (baseUrl.endsWith("/")) baseUrl = baseUrl.slice(0, -1);
|
|
1392
|
+
return new Proxy(zitadelNextGen_exports, { get(target, prop, receiver) {
|
|
1393
|
+
const value = Reflect.get(target, prop, receiver);
|
|
1394
|
+
if (typeof value !== "function") return value;
|
|
1395
|
+
return (...args) => {
|
|
1396
|
+
setProxyPath(baseUrl);
|
|
1397
|
+
setApiAuthToken(opts.token);
|
|
1398
|
+
return value(...args);
|
|
1399
|
+
};
|
|
1400
|
+
} });
|
|
1401
|
+
}
|
|
1402
|
+
//#endregion
|
|
1403
|
+
//#region ../config/dist/defaults-CXTnFMHj.mjs
|
|
1404
|
+
var default_human_user_default = {
|
|
1405
|
+
title: "DefaultHumanUserSchema",
|
|
1406
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1407
|
+
metaSchema: "${SERVER_URL}/user-schema.json",
|
|
1408
|
+
$id: "${USER_SCHEMA_URL}",
|
|
1409
|
+
objectType: "human-user",
|
|
1410
|
+
kind: "user-schema",
|
|
1411
|
+
type: "object",
|
|
1412
|
+
description: "The default editable schema for human users.",
|
|
1413
|
+
"x-auth-methods": {
|
|
1414
|
+
"password": {
|
|
1415
|
+
"enabled": true,
|
|
1416
|
+
"position": 1
|
|
1417
|
+
},
|
|
1418
|
+
"passkey": {
|
|
1419
|
+
"enabled": true,
|
|
1420
|
+
"position": 2
|
|
1421
|
+
}
|
|
1422
|
+
},
|
|
1423
|
+
required: ["email"],
|
|
1424
|
+
properties: { "email": {
|
|
1425
|
+
"type": "string",
|
|
1426
|
+
"format": "email",
|
|
1427
|
+
"x-unique": "project",
|
|
1428
|
+
"description": "The user's email address."
|
|
1429
|
+
} }
|
|
1430
|
+
};
|
|
1431
|
+
var default_login_default = {
|
|
1432
|
+
$schema: "../meta/flow-definition.json",
|
|
1433
|
+
name: "default-login",
|
|
1434
|
+
status: "active",
|
|
1435
|
+
user_schema: "${USER_SCHEMA_URL}",
|
|
1436
|
+
purposes: {
|
|
1437
|
+
"login": "identifier",
|
|
1438
|
+
"register": "register"
|
|
1439
|
+
},
|
|
1440
|
+
steps: [
|
|
1441
|
+
{
|
|
1442
|
+
"name": "identifier",
|
|
1443
|
+
"fields": ["email"],
|
|
1444
|
+
"actions": [{
|
|
1445
|
+
"name": "submit",
|
|
1446
|
+
"kind": "submit",
|
|
1447
|
+
"primary": true,
|
|
1448
|
+
"text_key": "identifier.action.continue"
|
|
1449
|
+
}, {
|
|
1450
|
+
"name": "passkey",
|
|
1451
|
+
"kind": "passkey",
|
|
1452
|
+
"primary": false,
|
|
1453
|
+
"text_key": "identifier.action.passkey"
|
|
1454
|
+
}],
|
|
1455
|
+
"transitions": {
|
|
1456
|
+
"submit": { "target": "password" },
|
|
1457
|
+
"passkey": { "target": "done" },
|
|
1458
|
+
"user_not_found": { "target": "register" }
|
|
1459
|
+
}
|
|
1460
|
+
},
|
|
1461
|
+
{
|
|
1462
|
+
"name": "password",
|
|
1463
|
+
"fields": ["x-auth-methods#password"],
|
|
1464
|
+
"actions": [{
|
|
1465
|
+
"name": "submit",
|
|
1466
|
+
"kind": "submit",
|
|
1467
|
+
"primary": true,
|
|
1468
|
+
"text_key": "password.action.signin"
|
|
1469
|
+
}, {
|
|
1470
|
+
"name": "passkey",
|
|
1471
|
+
"kind": "passkey",
|
|
1472
|
+
"primary": false,
|
|
1473
|
+
"text_key": "password.action.passkey"
|
|
1474
|
+
}],
|
|
1475
|
+
"transitions": {
|
|
1476
|
+
"submit": { "target": "done" },
|
|
1477
|
+
"passkey": { "target": "done" }
|
|
1478
|
+
}
|
|
1479
|
+
},
|
|
1480
|
+
{
|
|
1481
|
+
"name": "register",
|
|
1482
|
+
"fields": ["email"],
|
|
1483
|
+
"actions": [{
|
|
1484
|
+
"name": "submit",
|
|
1485
|
+
"kind": "submit",
|
|
1486
|
+
"primary": true,
|
|
1487
|
+
"text_key": "register.action.password"
|
|
1488
|
+
}, {
|
|
1489
|
+
"name": "passkey_register",
|
|
1490
|
+
"kind": "passkey_register",
|
|
1491
|
+
"primary": false,
|
|
1492
|
+
"text_key": "register.action.passkey"
|
|
1493
|
+
}],
|
|
1494
|
+
"transitions": {
|
|
1495
|
+
"submit": { "target": "register-password" },
|
|
1496
|
+
"passkey_register": { "target": "done" },
|
|
1497
|
+
"user_already_exists": { "target": "password" }
|
|
1498
|
+
}
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
"name": "register-password",
|
|
1502
|
+
"fields": ["x-auth-methods#password"],
|
|
1503
|
+
"actions": [{
|
|
1504
|
+
"name": "submit",
|
|
1505
|
+
"kind": "submit",
|
|
1506
|
+
"primary": true,
|
|
1507
|
+
"text_key": "register-password.action.submit"
|
|
1508
|
+
}],
|
|
1509
|
+
"on_success": "create_user",
|
|
1510
|
+
"transitions": {
|
|
1511
|
+
"submit": { "target": "done" },
|
|
1512
|
+
"user_already_exists": { "target": "password" }
|
|
1513
|
+
}
|
|
1514
|
+
},
|
|
1515
|
+
{
|
|
1516
|
+
"name": "done",
|
|
1517
|
+
"complete": "show"
|
|
1518
|
+
}
|
|
1519
|
+
]
|
|
1520
|
+
};
|
|
1521
|
+
var human_user_default = {
|
|
1522
|
+
title: "DefaultHumanUserSchema",
|
|
1523
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1524
|
+
metaSchema: "${SERVER_URL}/user-schema.json",
|
|
1525
|
+
$id: "${USER_SCHEMA_URL}",
|
|
1526
|
+
objectType: "human-user",
|
|
1527
|
+
kind: "user-schema",
|
|
1528
|
+
type: "object",
|
|
1529
|
+
description: "The default editable schema for human users (passkey-first sign-in).",
|
|
1530
|
+
"x-auth-methods": {
|
|
1531
|
+
"passkey": {
|
|
1532
|
+
"enabled": true,
|
|
1533
|
+
"position": 1
|
|
1534
|
+
},
|
|
1535
|
+
"password": {
|
|
1536
|
+
"enabled": true,
|
|
1537
|
+
"position": 2
|
|
1538
|
+
}
|
|
1539
|
+
},
|
|
1540
|
+
required: ["email"],
|
|
1541
|
+
properties: { "email": {
|
|
1542
|
+
"type": "string",
|
|
1543
|
+
"format": "email",
|
|
1544
|
+
"x-unique": "project",
|
|
1545
|
+
"description": "The user's email address."
|
|
1546
|
+
} }
|
|
1547
|
+
};
|
|
1548
|
+
var login_default = {
|
|
1549
|
+
$schema: "../meta/flow-definition.json",
|
|
1550
|
+
name: "default-login",
|
|
1551
|
+
status: "active",
|
|
1552
|
+
user_schema: "${USER_SCHEMA_URL}",
|
|
1553
|
+
purposes: {
|
|
1554
|
+
"login": "passkey-first",
|
|
1555
|
+
"register": "register"
|
|
1556
|
+
},
|
|
1557
|
+
steps: [
|
|
1558
|
+
{
|
|
1559
|
+
"name": "passkey-first",
|
|
1560
|
+
"fields": [],
|
|
1561
|
+
"actions": [{
|
|
1562
|
+
"name": "passkey",
|
|
1563
|
+
"kind": "passkey",
|
|
1564
|
+
"primary": true,
|
|
1565
|
+
"text_key": "passkey-first.action.passkey"
|
|
1566
|
+
}, {
|
|
1567
|
+
"name": "email_fallback",
|
|
1568
|
+
"kind": "navigate",
|
|
1569
|
+
"primary": false,
|
|
1570
|
+
"text_key": "passkey-first.action.email_fallback"
|
|
1571
|
+
}],
|
|
1572
|
+
"transitions": {
|
|
1573
|
+
"passkey": { "target": "done" },
|
|
1574
|
+
"email_fallback": { "target": "identifier" },
|
|
1575
|
+
"user_not_found": { "target": "register" }
|
|
1576
|
+
}
|
|
1577
|
+
},
|
|
1578
|
+
{
|
|
1579
|
+
"name": "identifier",
|
|
1580
|
+
"fields": ["email"],
|
|
1581
|
+
"actions": [{
|
|
1582
|
+
"name": "submit",
|
|
1583
|
+
"kind": "submit",
|
|
1584
|
+
"primary": true,
|
|
1585
|
+
"text_key": "identifier.action.continue"
|
|
1586
|
+
}, {
|
|
1587
|
+
"name": "passkey",
|
|
1588
|
+
"kind": "passkey",
|
|
1589
|
+
"primary": false,
|
|
1590
|
+
"text_key": "identifier.action.passkey"
|
|
1591
|
+
}],
|
|
1592
|
+
"transitions": {
|
|
1593
|
+
"submit": { "target": "password" },
|
|
1594
|
+
"passkey": { "target": "done" },
|
|
1595
|
+
"user_not_found": { "target": "register" }
|
|
1596
|
+
}
|
|
1597
|
+
},
|
|
1598
|
+
{
|
|
1599
|
+
"name": "password",
|
|
1600
|
+
"fields": ["x-auth-methods#password"],
|
|
1601
|
+
"actions": [{
|
|
1602
|
+
"name": "submit",
|
|
1603
|
+
"kind": "submit",
|
|
1604
|
+
"primary": true,
|
|
1605
|
+
"text_key": "password.action.signin"
|
|
1606
|
+
}, {
|
|
1607
|
+
"name": "passkey",
|
|
1608
|
+
"kind": "passkey",
|
|
1609
|
+
"primary": false,
|
|
1610
|
+
"text_key": "password.action.passkey"
|
|
1611
|
+
}],
|
|
1612
|
+
"transitions": {
|
|
1613
|
+
"submit": { "target": "done" },
|
|
1614
|
+
"passkey": { "target": "done" }
|
|
1615
|
+
}
|
|
1616
|
+
},
|
|
1617
|
+
{
|
|
1618
|
+
"name": "register",
|
|
1619
|
+
"fields": ["email"],
|
|
1620
|
+
"actions": [{
|
|
1621
|
+
"name": "passkey_register",
|
|
1622
|
+
"kind": "passkey_register",
|
|
1623
|
+
"primary": true,
|
|
1624
|
+
"text_key": "register.action.passkey"
|
|
1625
|
+
}, {
|
|
1626
|
+
"name": "submit",
|
|
1627
|
+
"kind": "submit",
|
|
1628
|
+
"primary": false,
|
|
1629
|
+
"text_key": "register.action.password"
|
|
1630
|
+
}],
|
|
1631
|
+
"transitions": {
|
|
1632
|
+
"passkey_register": { "target": "done" },
|
|
1633
|
+
"submit": { "target": "register-password" },
|
|
1634
|
+
"user_already_exists": { "target": "password" }
|
|
1635
|
+
}
|
|
1636
|
+
},
|
|
1637
|
+
{
|
|
1638
|
+
"name": "register-password",
|
|
1639
|
+
"fields": ["x-auth-methods#password"],
|
|
1640
|
+
"actions": [{
|
|
1641
|
+
"name": "submit",
|
|
1642
|
+
"kind": "submit",
|
|
1643
|
+
"primary": true,
|
|
1644
|
+
"text_key": "register-password.action.submit"
|
|
1645
|
+
}],
|
|
1646
|
+
"on_success": "create_user",
|
|
1647
|
+
"transitions": {
|
|
1648
|
+
"submit": { "target": "done" },
|
|
1649
|
+
"user_already_exists": { "target": "password" }
|
|
1650
|
+
}
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
"name": "done",
|
|
1654
|
+
"complete": "show"
|
|
1655
|
+
}
|
|
1656
|
+
]
|
|
1657
|
+
};
|
|
1658
|
+
const DEFAULT_BUILTIN_SCHEMA_BASE = "https://nextgen.com/api/schemas";
|
|
1659
|
+
const DEFAULT_FLOW_SCHEMA_URI = "https://nextgen.com/flow-definition.json";
|
|
1660
|
+
/**
|
|
1661
|
+
* Named schema+flow bundles `zitadel setup` can scaffold (#448: the prompt
|
|
1662
|
+
* fires before any `.zitadel/` file is written; each preset maps to a
|
|
1663
|
+
* pre-defined bundle the CLI copies on first setup). `password-first` is
|
|
1664
|
+
* today's default; `passkey-first` puts a passkey ceremony on the login
|
|
1665
|
+
* entry step with an email→password fallback path.
|
|
1666
|
+
*/
|
|
1667
|
+
const SETUP_PRESETS = ["password-first", "passkey-first"];
|
|
1668
|
+
const PRESET_TEMPLATES = {
|
|
1669
|
+
"password-first": {
|
|
1670
|
+
schema: default_human_user_default,
|
|
1671
|
+
flow: default_login_default
|
|
1672
|
+
},
|
|
1673
|
+
"passkey-first": {
|
|
1674
|
+
schema: human_user_default,
|
|
1675
|
+
flow: login_default
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
function presetTemplates(preset) {
|
|
1679
|
+
if (!Object.hasOwn(PRESET_TEMPLATES, preset)) throw new Error(`unknown setup preset ${JSON.stringify(preset)} (known presets: ${SETUP_PRESETS.join(", ")})`);
|
|
1680
|
+
return PRESET_TEMPLATES[preset];
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* The second setup axis (#448): *who* signs in, orthogonal to the sign-in
|
|
1684
|
+
* preset (*how* they sign in). The sign-in preset owns the flow shape and
|
|
1685
|
+
* auth methods; the use case owns which profile fields the schema collects.
|
|
1686
|
+
* Composing the two keeps us at one field catalog + one flow-per-preset
|
|
1687
|
+
* instead of a bundle per (use case × preset) pair. `minimal` is the
|
|
1688
|
+
* default and never blocks non-interactive runs.
|
|
1689
|
+
*/
|
|
1690
|
+
const SETUP_USE_CASES = [
|
|
1691
|
+
"minimal",
|
|
1692
|
+
"consumer",
|
|
1693
|
+
"business"
|
|
1694
|
+
];
|
|
1695
|
+
/**
|
|
1696
|
+
* Fields each use case collects, in register/display order. `email` is
|
|
1697
|
+
* always first and stays the only required property; the rest are optional
|
|
1698
|
+
* profile attributes gathered on the flow's register step. `givenName`/
|
|
1699
|
+
* `familyName` are the attributes the backend reads for a user's identity
|
|
1700
|
+
* (`internal/domain/user.go`); `companyName` is stored as a plain user
|
|
1701
|
+
* attribute today — there is no org/team model behind it yet.
|
|
1702
|
+
*/
|
|
1703
|
+
const USE_CASE_FIELDS = {
|
|
1704
|
+
minimal: ["email"],
|
|
1705
|
+
consumer: [
|
|
1706
|
+
"email",
|
|
1707
|
+
"givenName",
|
|
1708
|
+
"familyName"
|
|
1709
|
+
],
|
|
1710
|
+
business: [
|
|
1711
|
+
"email",
|
|
1712
|
+
"givenName",
|
|
1713
|
+
"familyName",
|
|
1714
|
+
"companyName"
|
|
1715
|
+
]
|
|
1716
|
+
};
|
|
1717
|
+
/**
|
|
1718
|
+
* JSON-Schema bodies for the optional profile fields the CLI composes in per
|
|
1719
|
+
* use case. The shipped templates are an email-only baseline (shared verbatim
|
|
1720
|
+
* with the Go server fallback), so this catalog owns every field beyond
|
|
1721
|
+
* `email`. Authored in the templates' style — no `x-claim`, since the backend
|
|
1722
|
+
* maps identity by attribute name and the user-property meta-schema defines no
|
|
1723
|
+
* claim keyword. `givenName`/`familyName` are the attributes the backend reads
|
|
1724
|
+
* for a user's identity (`internal/domain/user.go`).
|
|
1725
|
+
*/
|
|
1726
|
+
const EXTRA_FIELD_BODIES = {
|
|
1727
|
+
givenName: {
|
|
1728
|
+
type: "string",
|
|
1729
|
+
maxLength: 50,
|
|
1730
|
+
description: "The user's given (first) name."
|
|
1731
|
+
},
|
|
1732
|
+
familyName: {
|
|
1733
|
+
type: "string",
|
|
1734
|
+
maxLength: 50,
|
|
1735
|
+
description: "The user's family (last) name."
|
|
1736
|
+
},
|
|
1737
|
+
companyName: {
|
|
1738
|
+
type: "string",
|
|
1739
|
+
maxLength: 200,
|
|
1740
|
+
description: "The user's company name."
|
|
1741
|
+
}
|
|
1742
|
+
};
|
|
1743
|
+
function useCaseFields(useCase) {
|
|
1744
|
+
if (!Object.hasOwn(USE_CASE_FIELDS, useCase)) throw new Error(`unknown setup use case ${JSON.stringify(useCase)} (known use cases: ${SETUP_USE_CASES.join(", ")})`);
|
|
1745
|
+
return USE_CASE_FIELDS[useCase];
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* The body for one use-case field: reuse the template's authored body when it
|
|
1749
|
+
* has one (so composed schemas match the shipped defaults for shared fields),
|
|
1750
|
+
* else the extra catalog, else a generic editable string so composition stays
|
|
1751
|
+
* total for any field a future use case introduces.
|
|
1752
|
+
*/
|
|
1753
|
+
function fieldBody(field, templateProps) {
|
|
1754
|
+
if (Object.hasOwn(templateProps, field)) return { ...templateProps[field] };
|
|
1755
|
+
if (Object.hasOwn(EXTRA_FIELD_BODIES, field)) return { ...EXTRA_FIELD_BODIES[field] };
|
|
1756
|
+
return {
|
|
1757
|
+
type: "string",
|
|
1758
|
+
description: `The user's ${field}.`
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
/**
|
|
1762
|
+
* Narrow a rendered schema to the chosen use case: keep the preset-owned
|
|
1763
|
+
* header and `x-auth-methods`, but replace `properties`/`required` with the
|
|
1764
|
+
* use case's field set. `email` is the sole required property.
|
|
1765
|
+
*/
|
|
1766
|
+
function applyUseCaseToSchema(schema, useCase) {
|
|
1767
|
+
const templateProps = schema.properties ?? {};
|
|
1768
|
+
const properties = {};
|
|
1769
|
+
for (const field of useCaseFields(useCase)) properties[field] = fieldBody(field, templateProps);
|
|
1770
|
+
return {
|
|
1771
|
+
...schema,
|
|
1772
|
+
required: ["email"],
|
|
1773
|
+
properties
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* Derive the flow's register-step fields from the use case rather than
|
|
1778
|
+
* copying a hard-coded list into every bundle (#448): the register step
|
|
1779
|
+
* collects exactly what the schema defines. Other steps are untouched.
|
|
1780
|
+
*/
|
|
1781
|
+
function applyUseCaseToFlow(flow, useCase) {
|
|
1782
|
+
const fields = [...useCaseFields(useCase)];
|
|
1783
|
+
const steps = flow.steps.map((step) => step.name === "register" ? {
|
|
1784
|
+
...step,
|
|
1785
|
+
fields
|
|
1786
|
+
} : step);
|
|
1787
|
+
return {
|
|
1788
|
+
...flow,
|
|
1789
|
+
steps
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
function defaultHumanUserSchemaUrl(builtinSchemaBase = DEFAULT_BUILTIN_SCHEMA_BASE) {
|
|
1793
|
+
return `${trimTrailingSlash(builtinSchemaBase)}/default-human-user.json`;
|
|
1794
|
+
}
|
|
1795
|
+
function getDefaultHumanUserSchema(options = {}) {
|
|
1796
|
+
const builtinSchemaBase = trimTrailingSlash(options.builtinSchemaBase ?? "https://nextgen.com/api/schemas");
|
|
1797
|
+
return applyUseCaseToSchema(renderTemplate(presetTemplates(options.preset ?? "password-first").schema, {
|
|
1798
|
+
SERVER_URL: builtinSchemaBase,
|
|
1799
|
+
USER_SCHEMA_URL: options.userSchemaUrl ?? defaultHumanUserSchemaUrl(builtinSchemaBase)
|
|
1800
|
+
}), options.useCase ?? "minimal");
|
|
1801
|
+
}
|
|
1802
|
+
function getDefaultLoginFlow(options = {}) {
|
|
1803
|
+
const builtinSchemaBase = trimTrailingSlash(options.builtinSchemaBase ?? "https://nextgen.com/api/schemas");
|
|
1804
|
+
return applyUseCaseToFlow(renderTemplate(presetTemplates(options.preset ?? "password-first").flow, {
|
|
1805
|
+
SERVER_URL: builtinSchemaBase,
|
|
1806
|
+
USER_SCHEMA_URL: options.userSchemaUrl ?? defaultHumanUserSchemaUrl(builtinSchemaBase)
|
|
1807
|
+
}), options.useCase ?? "minimal");
|
|
1808
|
+
}
|
|
1809
|
+
function renderTemplate(value, replacements) {
|
|
1810
|
+
if (Array.isArray(value)) return value.map((item) => renderTemplate(item, replacements));
|
|
1811
|
+
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, renderTemplate(item, replacements)]));
|
|
1812
|
+
if (typeof value === "string") return value.replaceAll(/\$\{([A-Z_]+)\}/g, (match, name) => {
|
|
1813
|
+
return replacements[name] ?? match;
|
|
1814
|
+
});
|
|
1815
|
+
return value;
|
|
1816
|
+
}
|
|
1817
|
+
function trimTrailingSlash(value) {
|
|
1818
|
+
let trimmed = value;
|
|
1819
|
+
while (trimmed.endsWith("/")) trimmed = trimmed.slice(0, -1);
|
|
1820
|
+
return trimmed;
|
|
1821
|
+
}
|
|
1822
|
+
//#endregion
|
|
1823
|
+
//#region src/bootstrap.ts
|
|
1824
|
+
const DEFAULT_PROJECT_NAME = "zitadel-testing";
|
|
1825
|
+
/**
|
|
1826
|
+
* Server-side half of `zitadel setup`, without any file scaffolding:
|
|
1827
|
+
* `POST /projects` is unauthenticated and mints the projectSecret used as the
|
|
1828
|
+
* bearer for everything else; the schema is uploaded without `$id` so the
|
|
1829
|
+
* server assigns an opaque id, which the flow must then reference.
|
|
1830
|
+
*/
|
|
1831
|
+
async function bootstrapProject(options) {
|
|
1832
|
+
const { baseUrl } = options;
|
|
1833
|
+
const project = await createZitadelClient({ baseUrl }).createProject({
|
|
1834
|
+
name: options.projectName ?? DEFAULT_PROJECT_NAME,
|
|
1835
|
+
previewOrigins: options.appOrigins ?? [],
|
|
1836
|
+
seedDefaults: false
|
|
1837
|
+
});
|
|
1838
|
+
const projectId = requireString(project.id, "project id");
|
|
1839
|
+
const projectSecret = requireString(project.projectSecret, "project secret");
|
|
1840
|
+
const previewSecret = typeof project.previewSecret === "string" ? project.previewSecret : void 0;
|
|
1841
|
+
const client = createZitadelClient({
|
|
1842
|
+
baseUrl,
|
|
1843
|
+
token: projectSecret
|
|
1844
|
+
});
|
|
1845
|
+
const { $id: _templateId, ...schemaBody } = getDefaultHumanUserSchema({
|
|
1846
|
+
preset: options.preset,
|
|
1847
|
+
useCase: options.useCase
|
|
1848
|
+
});
|
|
1849
|
+
const schemaId = requireString((await client.createSchema(schemaBody, { project_id: projectId })).id, "schema id");
|
|
1850
|
+
const flowBody = getDefaultLoginFlow({
|
|
1851
|
+
userSchemaUrl: schemaId,
|
|
1852
|
+
preset: options.preset,
|
|
1853
|
+
useCase: options.useCase
|
|
1854
|
+
});
|
|
1855
|
+
return {
|
|
1856
|
+
projectId,
|
|
1857
|
+
projectSecret,
|
|
1858
|
+
previewSecret,
|
|
1859
|
+
schemaId,
|
|
1860
|
+
flowId: requireString((await client.createFlowDefinition({
|
|
1861
|
+
project_id: projectId,
|
|
1862
|
+
schema_uri: DEFAULT_FLOW_SCHEMA_URI,
|
|
1863
|
+
flow_definition: flowBody
|
|
1864
|
+
})).id, "flow definition id")
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
function requireString(value, label) {
|
|
1868
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
1869
|
+
throw new Error(`Missing ${label} in server response.`);
|
|
1870
|
+
}
|
|
1871
|
+
//#endregion
|
|
1872
|
+
//#region src/cli.ts
|
|
1873
|
+
const DEFAULT_TIMEOUT_MS = 12e4;
|
|
1874
|
+
function resolveCliBin() {
|
|
1875
|
+
const require = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
1876
|
+
const pkgPath = require.resolve("@zitadel/cli/package.json");
|
|
1877
|
+
const rel = require(pkgPath).bin?.zitadel;
|
|
1878
|
+
if (!rel) throw new Error("@zitadel/cli does not declare a `zitadel` bin entry");
|
|
1879
|
+
return (0, node_path.join)((0, node_path.dirname)(pkgPath), rel);
|
|
1880
|
+
}
|
|
1881
|
+
function runCli(options) {
|
|
1882
|
+
const bin = options.bin ?? resolveCliBin();
|
|
1883
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1884
|
+
return new Promise((resolve, reject) => {
|
|
1885
|
+
const child = (0, node_child_process.spawn)(process.execPath, [bin, ...options.args], {
|
|
1886
|
+
env: {
|
|
1887
|
+
...process.env,
|
|
1888
|
+
...options.env
|
|
1889
|
+
},
|
|
1890
|
+
stdio: [
|
|
1891
|
+
"ignore",
|
|
1892
|
+
"pipe",
|
|
1893
|
+
"pipe"
|
|
1894
|
+
]
|
|
1895
|
+
});
|
|
1896
|
+
let stdout = "";
|
|
1897
|
+
let stderr = "";
|
|
1898
|
+
child.stdout.setEncoding("utf8");
|
|
1899
|
+
child.stdout.on("data", (chunk) => {
|
|
1900
|
+
stdout += chunk;
|
|
1901
|
+
});
|
|
1902
|
+
child.stderr.setEncoding("utf8");
|
|
1903
|
+
child.stderr.on("data", (chunk) => {
|
|
1904
|
+
stderr += chunk;
|
|
1905
|
+
});
|
|
1906
|
+
const timer = setTimeout(() => {
|
|
1907
|
+
child.kill("SIGKILL");
|
|
1908
|
+
reject(/* @__PURE__ */ new Error(`zitadel ${options.args[0] ?? ""} timed out after ${timeoutMs}ms\n${tail(stderr)}`));
|
|
1909
|
+
}, timeoutMs);
|
|
1910
|
+
timer.unref();
|
|
1911
|
+
child.on("error", (error) => {
|
|
1912
|
+
clearTimeout(timer);
|
|
1913
|
+
reject(error);
|
|
1914
|
+
});
|
|
1915
|
+
child.on("close", (code) => {
|
|
1916
|
+
clearTimeout(timer);
|
|
1917
|
+
resolve({
|
|
1918
|
+
exitCode: code ?? -1,
|
|
1919
|
+
stdout,
|
|
1920
|
+
stderr
|
|
1921
|
+
});
|
|
1922
|
+
});
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
function tail(text, lines = 20) {
|
|
1926
|
+
return text.split("\n").slice(-lines).join("\n").trim();
|
|
1927
|
+
}
|
|
1928
|
+
//#endregion
|
|
1929
|
+
//#region src/envelope.ts
|
|
1930
|
+
/**
|
|
1931
|
+
* Render an error envelope's remediation fields for humans — the CLI's
|
|
1932
|
+
* `hint`/`next_commands` are the actionable part of a failure (e.g. "Reinstall
|
|
1933
|
+
* @zitadel/cli so npm can install @zitadel/server"), so surface them instead
|
|
1934
|
+
* of a raw stdout dump. Returns undefined when the envelope has no message.
|
|
1935
|
+
*/
|
|
1936
|
+
function describeEnvelopeError(envelope) {
|
|
1937
|
+
if (typeof envelope.message !== "string" || envelope.message.length === 0) return;
|
|
1938
|
+
const lines = [envelope.code ? `${envelope.code}: ${envelope.message}` : envelope.message];
|
|
1939
|
+
if (envelope.hint) lines.push(`hint: ${envelope.hint}`);
|
|
1940
|
+
if (envelope.next_commands && envelope.next_commands.length > 0) lines.push(`next: ${envelope.next_commands.join(" | ")}`);
|
|
1941
|
+
return lines.join("\n");
|
|
1942
|
+
}
|
|
1943
|
+
function parseCliEnvelope(stdout, context) {
|
|
1944
|
+
const start = stdout.indexOf("{");
|
|
1945
|
+
const end = stdout.lastIndexOf("}");
|
|
1946
|
+
if (start === -1 || end <= start) throw new Error(`${context}: expected a JSON envelope on stdout, got:\n${stdout.trim() || "(empty)"}`);
|
|
1947
|
+
let parsed;
|
|
1948
|
+
try {
|
|
1949
|
+
parsed = JSON.parse(stdout.slice(start, end + 1));
|
|
1950
|
+
} catch (error) {
|
|
1951
|
+
throw new Error(`${context}: failed to parse JSON envelope: ${error.message}\n${stdout.trim()}`, { cause: error });
|
|
1952
|
+
}
|
|
1953
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.status !== "string") throw new Error(`${context}: stdout JSON is not a CLI envelope:\n${stdout.trim()}`);
|
|
1954
|
+
return parsed;
|
|
1955
|
+
}
|
|
1956
|
+
//#endregion
|
|
1957
|
+
//#region src/ports.ts
|
|
1958
|
+
/**
|
|
1959
|
+
* Ask the OS for a free TCP port. The port is released before returning, so a
|
|
1960
|
+
* racing process could grab it; the CLI's own preflight surfaces that as
|
|
1961
|
+
* E_PORT_IN_USE, which is loud rather than corrupting.
|
|
1962
|
+
*/
|
|
1963
|
+
function getFreePort() {
|
|
1964
|
+
return new Promise((resolve, reject) => {
|
|
1965
|
+
const server = (0, node_net.createServer)();
|
|
1966
|
+
server.unref();
|
|
1967
|
+
server.on("error", reject);
|
|
1968
|
+
server.listen(0, "127.0.0.1", () => {
|
|
1969
|
+
const address = server.address();
|
|
1970
|
+
if (address === null || typeof address === "string") {
|
|
1971
|
+
server.close();
|
|
1972
|
+
reject(/* @__PURE__ */ new Error("could not determine a free port"));
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
const { port } = address;
|
|
1976
|
+
server.close((err) => {
|
|
1977
|
+
if (err) {
|
|
1978
|
+
reject(err);
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
resolve(port);
|
|
1982
|
+
});
|
|
1983
|
+
});
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
//#endregion
|
|
1987
|
+
//#region src/lifecycle.ts
|
|
1988
|
+
/**
|
|
1989
|
+
* Boot an ephemeral local server by shelling out to `zitadel start` and parse
|
|
1990
|
+
* its JSON envelope. The CLI owns the subtle parts (port preflight, health
|
|
1991
|
+
* wait, process-group stop, embedded-Postgres reaping), so this module stays a
|
|
1992
|
+
* thin adapter; swapping it for direct library calls later must not change the
|
|
1993
|
+
* shape returned here.
|
|
1994
|
+
*/
|
|
1995
|
+
async function bootLocalServer(options = {}) {
|
|
1996
|
+
const ownsDir = options.dir === void 0;
|
|
1997
|
+
const dir = options.dir ?? await (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), "zitadel-testing-"));
|
|
1998
|
+
const port = options.port ?? await getFreePort();
|
|
1999
|
+
const env = {};
|
|
2000
|
+
if (options.serverBinary) env.ZITADEL_SERVER_BINARY = options.serverBinary;
|
|
2001
|
+
const result = await runCli({
|
|
2002
|
+
args: [
|
|
2003
|
+
"start",
|
|
2004
|
+
"--port",
|
|
2005
|
+
String(port),
|
|
2006
|
+
"--non-interactive",
|
|
2007
|
+
"--json",
|
|
2008
|
+
"-c",
|
|
2009
|
+
dir
|
|
2010
|
+
],
|
|
2011
|
+
bin: options.cliBin,
|
|
2012
|
+
env,
|
|
2013
|
+
timeoutMs: options.timeoutMs
|
|
2014
|
+
});
|
|
2015
|
+
if (result.exitCode !== 0) throw new Error(`zitadel start exited with code ${result.exitCode}.\n${failureDetail(result)}\nstate dir kept for inspection: ${dir}`);
|
|
2016
|
+
const stopViaCli = async () => {
|
|
2017
|
+
const stopResult = await runCli({
|
|
2018
|
+
args: [
|
|
2019
|
+
"stop",
|
|
2020
|
+
"--non-interactive",
|
|
2021
|
+
"--json",
|
|
2022
|
+
"-c",
|
|
2023
|
+
dir
|
|
2024
|
+
],
|
|
2025
|
+
bin: options.cliBin,
|
|
2026
|
+
env,
|
|
2027
|
+
timeoutMs: options.timeoutMs
|
|
2028
|
+
});
|
|
2029
|
+
if (stopResult.exitCode !== 0) throw new Error(`zitadel stop exited with code ${stopResult.exitCode}.\n${failureDetail(stopResult)}\nstate dir kept for inspection: ${dir}`);
|
|
2030
|
+
};
|
|
2031
|
+
let envelope;
|
|
2032
|
+
try {
|
|
2033
|
+
envelope = parseCliEnvelope(result.stdout, "zitadel start");
|
|
2034
|
+
if (envelope.status !== "ok") throw new Error(`zitadel start reported status "${envelope.status}":\n${describeEnvelopeError(envelope) ?? tail(result.stdout)}`);
|
|
2035
|
+
} catch (error) {
|
|
2036
|
+
const startError = new Error(`zitadel start produced unusable output.\nreason: ${error instanceof Error ? error.message : String(error)}\nstdout: ${tail(result.stdout) || "(empty)"}\nstderr: ${tail(result.stderr) || "(empty)"}\nstate dir kept for inspection: ${dir}`, { cause: error });
|
|
2037
|
+
try {
|
|
2038
|
+
await stopViaCli();
|
|
2039
|
+
} catch (stopError) {
|
|
2040
|
+
throw new AggregateError([startError, stopError], `${startError.message}\nStopping the possibly-running instance also failed: ${stopError instanceof Error ? stopError.message : String(stopError)}`);
|
|
2041
|
+
}
|
|
2042
|
+
throw startError;
|
|
2043
|
+
}
|
|
2044
|
+
const { runtime, urls } = envelope.data;
|
|
2045
|
+
const runStop = async () => {
|
|
2046
|
+
await stopViaCli();
|
|
2047
|
+
if (ownsDir && !options.keep) await (0, node_fs_promises.rm)(dir, {
|
|
2048
|
+
recursive: true,
|
|
2049
|
+
force: true
|
|
2050
|
+
});
|
|
2051
|
+
};
|
|
2052
|
+
let stopPromise;
|
|
2053
|
+
const stop = () => {
|
|
2054
|
+
stopPromise ??= runStop().catch((error) => {
|
|
2055
|
+
stopPromise = void 0;
|
|
2056
|
+
throw error;
|
|
2057
|
+
});
|
|
2058
|
+
return stopPromise;
|
|
2059
|
+
};
|
|
2060
|
+
return {
|
|
2061
|
+
baseUrl: urls.api,
|
|
2062
|
+
runtime: {
|
|
2063
|
+
port: runtime.port,
|
|
2064
|
+
pid: runtime.pid,
|
|
2065
|
+
dir,
|
|
2066
|
+
logPath: runtime.log_path
|
|
2067
|
+
},
|
|
2068
|
+
stop
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* A failed CLI run usually still prints an error envelope; its
|
|
2073
|
+
* message/hint/next_commands beat raw output tails (e.g. a fresh install
|
|
2074
|
+
* missing @zitadel/server gets "Reinstall @zitadel/cli" instead of a stack).
|
|
2075
|
+
*/
|
|
2076
|
+
function failureDetail(result) {
|
|
2077
|
+
try {
|
|
2078
|
+
const described = describeEnvelopeError(parseCliEnvelope(result.stdout, "zitadel"));
|
|
2079
|
+
if (described) return described;
|
|
2080
|
+
} catch {}
|
|
2081
|
+
return `stdout: ${tail(result.stdout) || "(empty)"}\nstderr: ${tail(result.stderr) || "(empty)"}`;
|
|
2082
|
+
}
|
|
2083
|
+
//#endregion
|
|
2084
|
+
//#region src/seed.ts
|
|
2085
|
+
/**
|
|
2086
|
+
* A unique unused email + password. Nothing is created on the instance —
|
|
2087
|
+
* this is the input for registration-flow specs, which must prove the flow
|
|
2088
|
+
* creates the user.
|
|
2089
|
+
*/
|
|
2090
|
+
function identity() {
|
|
2091
|
+
return {
|
|
2092
|
+
email: `e2e-${(0, node_crypto.randomUUID)().slice(0, 8)}@example.com`,
|
|
2093
|
+
password: `Pw!${(0, node_crypto.randomUUID)()}`
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* Create a user that can immediately complete the password login flow:
|
|
2098
|
+
* `POST /users` (the body must carry `$schema: <schema id>`) followed by
|
|
2099
|
+
* `PUT /users/{id}/password` with `isChangeRequired: false`.
|
|
2100
|
+
*
|
|
2101
|
+
* Defaults mint a unique email per call (email is x-unique per project), which
|
|
2102
|
+
* is what makes per-test seeding parallel-safe on a shared instance.
|
|
2103
|
+
*/
|
|
2104
|
+
async function seedUser(client, context, input = {}) {
|
|
2105
|
+
const fresh = identity();
|
|
2106
|
+
const email = input.email ?? fresh.email;
|
|
2107
|
+
const password = input.password ?? fresh.password;
|
|
2108
|
+
const id = requireString((await client.createUser({
|
|
2109
|
+
...input.attributes,
|
|
2110
|
+
$schema: context.schemaId,
|
|
2111
|
+
email
|
|
2112
|
+
}, { project_id: context.projectId })).id, "user id");
|
|
2113
|
+
await client.setUserPassword(id, {
|
|
2114
|
+
password,
|
|
2115
|
+
isChangeRequired: false
|
|
2116
|
+
}, { project_id: context.projectId });
|
|
2117
|
+
return {
|
|
2118
|
+
id,
|
|
2119
|
+
email,
|
|
2120
|
+
password
|
|
2121
|
+
};
|
|
2122
|
+
}
|
|
2123
|
+
/**
|
|
2124
|
+
* Seed `count` users sequentially. The template makes fixture data
|
|
2125
|
+
* deterministic per index (stable emails/names keep screenshot diffs about
|
|
2126
|
+
* code, not reshuffled data — the `console:dev-real` pattern); untemplated
|
|
2127
|
+
* fields fall back to the unique defaults. Name-like attributes need a
|
|
2128
|
+
* schema that declares them (`useCase: "consumer"` or wider).
|
|
2129
|
+
*/
|
|
2130
|
+
async function seedUsers(client, context, count, template = {}) {
|
|
2131
|
+
const users = [];
|
|
2132
|
+
for (let index = 0; index < count; index += 1) users.push(await seedUser(client, context, {
|
|
2133
|
+
email: template.email?.(index),
|
|
2134
|
+
password: template.password?.(index),
|
|
2135
|
+
attributes: template.attributes?.(index)
|
|
2136
|
+
}));
|
|
2137
|
+
return users;
|
|
2138
|
+
}
|
|
2139
|
+
//#endregion
|
|
2140
|
+
//#region src/session.ts
|
|
2141
|
+
/** Mirrors the server's session cookie (internal/api/session.go). */
|
|
2142
|
+
const SESSION_COOKIE_NAME = "__nextgen_session";
|
|
2143
|
+
const MAX_FLOW_STEPS = 6;
|
|
2144
|
+
/**
|
|
2145
|
+
* Drive the real login flow headlessly for a seeded password user and
|
|
2146
|
+
* exchange the terminal handoff for a session: exactly what `<zitadel-login>`
|
|
2147
|
+
* does, minus the rendering. Supports flows whose steps only ask for the
|
|
2148
|
+
* user's email and password (the shipped `password-first` presets); any step
|
|
2149
|
+
* demanding more — a challenge, an unknown field — fails loudly by design.
|
|
2150
|
+
*
|
|
2151
|
+
* Flow calls use raw fetch instead of the typed client because the flow is
|
|
2152
|
+
* stateless through the sealed `_zflow` cookie (internal/api/flow.go): every
|
|
2153
|
+
* response re-seals the flow state into Set-Cookie, and submits are rejected
|
|
2154
|
+
* without it. Browsers round-trip it implicitly; here a one-cookie jar does.
|
|
2155
|
+
*/
|
|
2156
|
+
async function mintSession(client, handle, context, user, options = {}) {
|
|
2157
|
+
const values = {
|
|
2158
|
+
email: user.email,
|
|
2159
|
+
password: user.password
|
|
2160
|
+
};
|
|
2161
|
+
const jar = new FlowCookieJar();
|
|
2162
|
+
const origin = options.origin;
|
|
2163
|
+
let response = await flowFetch(handle, jar, origin, "/flow", {
|
|
2164
|
+
project_id: context.projectId,
|
|
2165
|
+
purpose: "login",
|
|
2166
|
+
...options.flowDefinitionName ? { flow_definition_name: options.flowDefinitionName } : {}
|
|
2167
|
+
});
|
|
2168
|
+
for (let hop = 0; hop < MAX_FLOW_STEPS; hop += 1) {
|
|
2169
|
+
if (response.handoff_token) {
|
|
2170
|
+
const exchanged = await client.exchangeHandoff({ handoff_token: response.handoff_token }, { project_id: context.projectId });
|
|
2171
|
+
return {
|
|
2172
|
+
user,
|
|
2173
|
+
sessionToken: exchanged.session_token,
|
|
2174
|
+
expiresAt: exchanged.session.expires_at,
|
|
2175
|
+
cookie: {
|
|
2176
|
+
name: SESSION_COOKIE_NAME,
|
|
2177
|
+
value: exchanged.session_token,
|
|
2178
|
+
httpOnly: true,
|
|
2179
|
+
secure: true,
|
|
2180
|
+
sameSite: "Lax",
|
|
2181
|
+
path: "/"
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
response = await flowFetch(handle, jar, origin, `/flow/${encodeURIComponent(response.id)}/submit`, {
|
|
2186
|
+
session_token: response.session_token,
|
|
2187
|
+
action: "submit",
|
|
2188
|
+
fields: collectFields(response, values)
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
throw new Error(`seed.session: flow did not complete within ${MAX_FLOW_STEPS} steps (last step: ${describeStep(response)}).`);
|
|
2192
|
+
}
|
|
2193
|
+
/** One-cookie jar for the sealed `_zflow` flow-state cookie. */
|
|
2194
|
+
var FlowCookieJar = class {
|
|
2195
|
+
cookie;
|
|
2196
|
+
absorb(response) {
|
|
2197
|
+
for (const raw of response.headers.getSetCookie()) {
|
|
2198
|
+
const [pair] = raw.split(";", 1);
|
|
2199
|
+
if (pair?.startsWith("_zflow=")) this.cookie = pair;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
header() {
|
|
2203
|
+
return this.cookie ? { cookie: this.cookie } : {};
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
2206
|
+
async function flowFetch(handle, jar, origin, path, body) {
|
|
2207
|
+
const response = await fetch(`${handle.baseUrl}${path}`, {
|
|
2208
|
+
method: "POST",
|
|
2209
|
+
headers: {
|
|
2210
|
+
"content-type": "application/json",
|
|
2211
|
+
authorization: `Bearer ${handle.projectSecret}`,
|
|
2212
|
+
...origin ? { origin } : {},
|
|
2213
|
+
...jar.header()
|
|
2214
|
+
},
|
|
2215
|
+
body: JSON.stringify(body)
|
|
2216
|
+
});
|
|
2217
|
+
jar.absorb(response);
|
|
2218
|
+
const parsed = await response.json().catch(() => void 0);
|
|
2219
|
+
if (!response.ok || !parsed) {
|
|
2220
|
+
const detail = parsed && typeof parsed === "object" ? ` — ${JSON.stringify(parsed)}` : "";
|
|
2221
|
+
const hint = !origin && /origin/i.test(detail) ? "\nNo Origin header was sent: pass `origin` to seedSession() (the Playwright fixtures pass the suite's baseURL) or `appOrigins` to startLocalZitadel()." : "";
|
|
2222
|
+
throw new Error(`seed.session: POST ${path} returned ${response.status}${detail}${hint}`);
|
|
2223
|
+
}
|
|
2224
|
+
return parsed;
|
|
2225
|
+
}
|
|
2226
|
+
/**
|
|
2227
|
+
* Fill exactly the fields the current step declares — the orchestrator's
|
|
2228
|
+
* convention — from the known email/password values. An unknown required
|
|
2229
|
+
* field means this flow needs more than a password login can provide.
|
|
2230
|
+
*/
|
|
2231
|
+
function collectFields(response, values) {
|
|
2232
|
+
const fields = {};
|
|
2233
|
+
for (const field of response.step.fields ?? []) {
|
|
2234
|
+
const value = values[fieldKey(field)];
|
|
2235
|
+
if (value === void 0) throw new Error(`seed.session supports password flows only; step ${describeStep(response)} declares field "${field.name}", which the kit cannot fill. Log in through the UI for flows with additional factors.`);
|
|
2236
|
+
fields[field.name] = value;
|
|
2237
|
+
}
|
|
2238
|
+
return fields;
|
|
2239
|
+
}
|
|
2240
|
+
/**
|
|
2241
|
+
* Steps name credential fields with schema pointers (e.g.
|
|
2242
|
+
* `x-auth-methods#password`); match on the trailing segment so the value map
|
|
2243
|
+
* stays the plain `{ email, password }` a caller thinks in.
|
|
2244
|
+
*/
|
|
2245
|
+
function fieldKey(field) {
|
|
2246
|
+
const name = field.name;
|
|
2247
|
+
return (name.split(/[#/.]/).at(-1) ?? name).toLowerCase();
|
|
2248
|
+
}
|
|
2249
|
+
function describeStep(response) {
|
|
2250
|
+
const name = response.step.name ?? "(unnamed)";
|
|
2251
|
+
const declared = (response.step.fields ?? []).map((field) => field.name).join(", ");
|
|
2252
|
+
return `"${name}"${declared ? ` [fields: ${declared}]` : ""}`;
|
|
2253
|
+
}
|
|
2254
|
+
//#endregion
|
|
2255
|
+
//#region src/index.ts
|
|
2256
|
+
/**
|
|
2257
|
+
* Attach to an already-bootstrapped instance/project. Lifecycle-free on
|
|
2258
|
+
* purpose: this is the entry point for Playwright workers (via the handshake
|
|
2259
|
+
* file) and, later, for seeding remote instances.
|
|
2260
|
+
*/
|
|
2261
|
+
function connectZitadel(handle) {
|
|
2262
|
+
const api = createZitadelClient({
|
|
2263
|
+
baseUrl: handle.baseUrl,
|
|
2264
|
+
token: handle.projectSecret
|
|
2265
|
+
});
|
|
2266
|
+
const context = {
|
|
2267
|
+
projectId: handle.projectId,
|
|
2268
|
+
schemaId: handle.schemaId
|
|
2269
|
+
};
|
|
2270
|
+
return {
|
|
2271
|
+
handle,
|
|
2272
|
+
api,
|
|
2273
|
+
appEnv: require_handshake.applyAppEnvTemplate(require_handshake.nextAppEnv, handle),
|
|
2274
|
+
seedUser: (input) => seedUser(api, context, input),
|
|
2275
|
+
seedUsers: (count, template) => seedUsers(api, context, count, template),
|
|
2276
|
+
identity,
|
|
2277
|
+
seedSession: async (input = {}) => {
|
|
2278
|
+
const { user: existing, flowDefinitionName, origin, ...userInput } = input;
|
|
2279
|
+
return mintSession(api, handle, context, existing ?? await seedUser(api, context, userInput), {
|
|
2280
|
+
flowDefinitionName,
|
|
2281
|
+
origin: origin ?? handle.appOrigin
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Boot an ephemeral local instance (binary runtime + embedded Postgres, no
|
|
2288
|
+
* Docker) and bootstrap a project + default schema + login flow on it. The
|
|
2289
|
+
* result can seed loginable password users immediately.
|
|
2290
|
+
*/
|
|
2291
|
+
async function startLocalZitadel(options = {}) {
|
|
2292
|
+
const server = await bootLocalServer(options);
|
|
2293
|
+
let bootstrapped;
|
|
2294
|
+
try {
|
|
2295
|
+
bootstrapped = await bootstrapProject({
|
|
2296
|
+
baseUrl: server.baseUrl,
|
|
2297
|
+
projectName: options.projectName,
|
|
2298
|
+
appOrigins: options.appOrigins,
|
|
2299
|
+
preset: options.preset,
|
|
2300
|
+
useCase: options.useCase
|
|
2301
|
+
});
|
|
2302
|
+
} catch (error) {
|
|
2303
|
+
try {
|
|
2304
|
+
await server.stop();
|
|
2305
|
+
} catch (stopError) {
|
|
2306
|
+
throw new AggregateError([error, stopError], "bootstrap failed, and stopping the booted instance also failed");
|
|
2307
|
+
}
|
|
2308
|
+
throw error;
|
|
2309
|
+
}
|
|
2310
|
+
return {
|
|
2311
|
+
...connectZitadel({
|
|
2312
|
+
baseUrl: server.baseUrl,
|
|
2313
|
+
projectId: bootstrapped.projectId,
|
|
2314
|
+
projectSecret: bootstrapped.projectSecret,
|
|
2315
|
+
schemaId: bootstrapped.schemaId,
|
|
2316
|
+
previewSecret: bootstrapped.previewSecret,
|
|
2317
|
+
appOrigin: options.appOrigins?.[0]
|
|
2318
|
+
}),
|
|
2319
|
+
runtime: server.runtime,
|
|
2320
|
+
stop: server.stop,
|
|
2321
|
+
[Symbol.asyncDispose]: server.stop
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
//#endregion
|
|
2325
|
+
Object.defineProperty(exports, "SESSION_COOKIE_NAME", {
|
|
2326
|
+
enumerable: true,
|
|
2327
|
+
get: function() {
|
|
2328
|
+
return SESSION_COOKIE_NAME;
|
|
2329
|
+
}
|
|
2330
|
+
});
|
|
2331
|
+
Object.defineProperty(exports, "bootLocalServer", {
|
|
2332
|
+
enumerable: true,
|
|
2333
|
+
get: function() {
|
|
2334
|
+
return bootLocalServer;
|
|
2335
|
+
}
|
|
2336
|
+
});
|
|
2337
|
+
Object.defineProperty(exports, "bootstrapProject", {
|
|
2338
|
+
enumerable: true,
|
|
2339
|
+
get: function() {
|
|
2340
|
+
return bootstrapProject;
|
|
2341
|
+
}
|
|
2342
|
+
});
|
|
2343
|
+
Object.defineProperty(exports, "connectZitadel", {
|
|
2344
|
+
enumerable: true,
|
|
2345
|
+
get: function() {
|
|
2346
|
+
return connectZitadel;
|
|
2347
|
+
}
|
|
2348
|
+
});
|
|
2349
|
+
Object.defineProperty(exports, "startLocalZitadel", {
|
|
2350
|
+
enumerable: true,
|
|
2351
|
+
get: function() {
|
|
2352
|
+
return startLocalZitadel;
|
|
2353
|
+
}
|
|
2354
|
+
});
|
|
2355
|
+
|
|
2356
|
+
//# sourceMappingURL=src-BIL0PdSd.cjs.map
|