@zitadel/components 0.1.0-alpha.9 → 1.0.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -72
- package/dist/atoms/index.d.mts +2 -2
- package/dist/atoms/index.mjs +2 -2
- package/dist/atoms-DSxjEHca.mjs +2268 -0
- package/dist/atoms-DSxjEHca.mjs.map +1 -0
- package/dist/default-Dnjq9iBt.mjs +6 -0
- package/dist/default-Dnjq9iBt.mjs.map +1 -0
- package/dist/index-BPgWdWx7.d.mts +14642 -0
- package/dist/index-BPgWdWx7.d.mts.map +1 -0
- package/dist/index-BlxTxXK8.d.mts +449 -0
- package/dist/index-BlxTxXK8.d.mts.map +1 -0
- package/dist/index-CaScNIBy.d.mts +1597 -0
- package/dist/index-CaScNIBy.d.mts.map +1 -0
- package/dist/index.d.mts +22 -8
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/jsx.d.ts +67 -0
- package/dist/manifests.d.mts.map +1 -1
- package/dist/manifests.mjs +4 -2
- package/dist/manifests.mjs.map +1 -1
- package/dist/orchestrator/index.d.mts +3 -3
- package/dist/orchestrator/index.mjs +3 -3
- package/dist/orchestrator-ixQm2RIC.mjs +4998 -0
- package/dist/orchestrator-ixQm2RIC.mjs.map +1 -0
- package/dist/standalone.mjs +9140 -5849
- package/dist/tokens/index.d.mts +1 -1
- package/dist/tokens/index.mjs +1 -1
- package/dist/tokens-T4N9VJz3.mjs +452 -0
- package/dist/tokens-T4N9VJz3.mjs.map +1 -0
- package/package.json +13 -12
- package/dist/atoms-B25wiDqb.mjs +0 -1464
- package/dist/atoms-B25wiDqb.mjs.map +0 -1
- package/dist/default-Cpl78hQN.mjs +0 -6
- package/dist/default-Cpl78hQN.mjs.map +0 -1
- package/dist/index-DB_JTtln.d.mts +0 -1000
- package/dist/index-DB_JTtln.d.mts.map +0 -1
- package/dist/index-DVDCrfkv.d.mts +0 -7396
- package/dist/index-DVDCrfkv.d.mts.map +0 -1
- package/dist/index-EJengPPu.d.mts +0 -249
- package/dist/index-EJengPPu.d.mts.map +0 -1
- package/dist/orchestrator-CW9zixuw.mjs +0 -3362
- package/dist/orchestrator-CW9zixuw.mjs.map +0 -1
- package/dist/tokens-BQ_augxi.mjs +0 -244
- package/dist/tokens-BQ_augxi.mjs.map +0 -1
|
@@ -1,3362 +0,0 @@
|
|
|
1
|
-
import { _ as __decorate, b as t, v as focusVisibleStyles, x as emit, y as baseHostStyles } from "./atoms-B25wiDqb.mjs";
|
|
2
|
-
import { r as tokensCss } from "./tokens-BQ_augxi.mjs";
|
|
3
|
-
import { t as default_default } from "./default-Cpl78hQN.mjs";
|
|
4
|
-
import { manifestRegistry } from "./manifests.mjs";
|
|
5
|
-
import { LitElement, css, html, nothing } from "lit";
|
|
6
|
-
import { customElement, property, state } from "lit/decorators.js";
|
|
7
|
-
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
8
|
-
import { Liquid } from "liquidjs";
|
|
9
|
-
import DOMPurify from "dompurify";
|
|
10
|
-
//#region ../api/dist/runtime/auth.mjs
|
|
11
|
-
/**
|
|
12
|
-
* Module-global bearer token used by the orval-generated client's
|
|
13
|
-
* custom fetch. Set once at command boot (the same lifecycle pattern
|
|
14
|
-
* as `base-url.ts`); every generated request reads from here.
|
|
15
|
-
*
|
|
16
|
-
* Keeping the token here means the CLI doesn't have to thread an
|
|
17
|
-
* `Authorization` header through every generated call site; orval's
|
|
18
|
-
* `mutator` wires `runtime/fetch.ts` in, and that file pulls the token
|
|
19
|
-
* from this module.
|
|
20
|
-
*/
|
|
21
|
-
let apiAuthToken;
|
|
22
|
-
function getApiAuthToken() {
|
|
23
|
-
return apiAuthToken;
|
|
24
|
-
}
|
|
25
|
-
function setApiAuthToken(token) {
|
|
26
|
-
apiAuthToken = token;
|
|
27
|
-
}
|
|
28
|
-
//#endregion
|
|
29
|
-
//#region ../api/dist/runtime/fetch.mjs
|
|
30
|
-
/**
|
|
31
|
-
* Framework-neutral failure type the orval-generated client throws on
|
|
32
|
-
* any non-2xx response. Carries the HTTP status, the parsed error body
|
|
33
|
-
* (the spec's `{code, message, details?}` envelope when the server
|
|
34
|
-
* returned one), and the request URL — enough for callers to map to
|
|
35
|
-
* their own error taxonomy without re-implementing the fetch layer.
|
|
36
|
-
*
|
|
37
|
-
* Callers that don't care about the distinction can catch `ApiError`
|
|
38
|
-
* generically; callers that do (the CLI's `toZitadelError`) read
|
|
39
|
-
* `status` and decide.
|
|
40
|
-
*/
|
|
41
|
-
var ApiError = class extends Error {
|
|
42
|
-
status;
|
|
43
|
-
url;
|
|
44
|
-
body;
|
|
45
|
-
constructor(status, url, body, message) {
|
|
46
|
-
super(message);
|
|
47
|
-
this.name = "ApiError";
|
|
48
|
-
this.status = status;
|
|
49
|
-
this.url = url;
|
|
50
|
-
this.body = body;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
/**
|
|
54
|
-
* The orval `mutator` for the fetch client. Every generated operation
|
|
55
|
-
* routes its request through this function instead of the global
|
|
56
|
-
* `fetch`. We pin three concerns here so generated call sites stay
|
|
57
|
-
* focused on the shape of one HTTP call:
|
|
58
|
-
*
|
|
59
|
-
* - bearer auth — read from `runtime/auth.ts` and attached automatically;
|
|
60
|
-
* - non-2xx → throw — orval's stock client parses the body regardless
|
|
61
|
-
* of status, so callers would have to inspect every response. Throw
|
|
62
|
-
* `ApiError` on `!res.ok` so failures interrupt control flow;
|
|
63
|
-
* - body parsing — return the parsed JSON typed as `T` (the operation's
|
|
64
|
-
* return type, threaded through by orval), or `undefined` for the
|
|
65
|
-
* spec's `204`/`205`/`304` no-body responses.
|
|
66
|
-
*/
|
|
67
|
-
async function customFetch(url, options) {
|
|
68
|
-
const token = getApiAuthToken();
|
|
69
|
-
const headers = new Headers(options.headers);
|
|
70
|
-
if (token && !headers.has("authorization")) headers.set("authorization", `Bearer ${token}`);
|
|
71
|
-
const res = await fetch(url, {
|
|
72
|
-
...options,
|
|
73
|
-
headers
|
|
74
|
-
});
|
|
75
|
-
const rawBody = [
|
|
76
|
-
204,
|
|
77
|
-
205,
|
|
78
|
-
304
|
|
79
|
-
].includes(res.status) ? "" : await res.text();
|
|
80
|
-
const parsed = rawBody ? safeJsonParse(rawBody) : void 0;
|
|
81
|
-
if (!res.ok) {
|
|
82
|
-
const message = `${options.method ?? "GET"} ${url} returned ${res.status}`;
|
|
83
|
-
throw new ApiError(res.status, url, parsed, message);
|
|
84
|
-
}
|
|
85
|
-
return parsed;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* `JSON.parse` wrapped so a non-JSON body (e.g. an HTML 502 from a
|
|
89
|
-
* proxy) becomes `{ raw: "<text>" }` instead of throwing inside
|
|
90
|
-
* `customFetch`. The thrown `ApiError` then carries something useful
|
|
91
|
-
* for the user to see.
|
|
92
|
-
*/
|
|
93
|
-
function safeJsonParse(text) {
|
|
94
|
-
try {
|
|
95
|
-
return JSON.parse(text);
|
|
96
|
-
} catch {
|
|
97
|
-
return { raw: text };
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
//#endregion
|
|
101
|
-
//#region src/orchestrator/api-client.ts
|
|
102
|
-
const apiRequestInit = { credentials: "include" };
|
|
103
|
-
async function startFlow(api, input) {
|
|
104
|
-
return api.createFlow(input, apiRequestInit);
|
|
105
|
-
}
|
|
106
|
-
async function submitStep(api, id, body) {
|
|
107
|
-
try {
|
|
108
|
-
return await api.submitFlowStep(id, body, apiRequestInit);
|
|
109
|
-
} catch (error) {
|
|
110
|
-
if (error instanceof ApiError && error.status === 400 && isFlowResponse(error.body)) return error.body;
|
|
111
|
-
throw error;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
function isFlowResponse(body) {
|
|
115
|
-
return typeof body === "object" && body !== null && "step" in body;
|
|
116
|
-
}
|
|
117
|
-
async function getCurrentStep(api, id) {
|
|
118
|
-
return api.getFlowStep(id, apiRequestInit);
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Exchange a terminal-flow `handoff_token` for an authenticated session.
|
|
122
|
-
* The server sets the `__nextgen_session` HttpOnly cookie on success.
|
|
123
|
-
*
|
|
124
|
-
* Uses the generated `exchangeHandoff` client which handles the
|
|
125
|
-
* `project_id` query parameter via {@link ExchangeHandoffParams}.
|
|
126
|
-
*/
|
|
127
|
-
async function exchangeSession(api, body, params) {
|
|
128
|
-
return api.exchangeHandoff(body, params, apiRequestInit);
|
|
129
|
-
}
|
|
130
|
-
//#endregion
|
|
131
|
-
//#region ../api/dist/runtime/base-url.mjs
|
|
132
|
-
let proxyPath = "";
|
|
133
|
-
function getProxyPath() {
|
|
134
|
-
return proxyPath;
|
|
135
|
-
}
|
|
136
|
-
function setProxyPath(path) {
|
|
137
|
-
proxyPath = path;
|
|
138
|
-
}
|
|
139
|
-
//#endregion
|
|
140
|
-
//#region ../api/dist/chunk-CfYAbeIz.mjs
|
|
141
|
-
var __defProp = Object.defineProperty;
|
|
142
|
-
var __exportAll = (all, no_symbols) => {
|
|
143
|
-
let target = {};
|
|
144
|
-
for (var name in all) __defProp(target, name, {
|
|
145
|
-
get: all[name],
|
|
146
|
-
enumerable: true
|
|
147
|
-
});
|
|
148
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
149
|
-
return target;
|
|
150
|
-
};
|
|
151
|
-
//#endregion
|
|
152
|
-
//#region ../api/dist/generated/endpoints/zitadelNextGen.mjs
|
|
153
|
-
/**
|
|
154
|
-
* Generated by orval v8.10.0 🍺
|
|
155
|
-
* Do not edit manually.
|
|
156
|
-
* Zitadel NextGen
|
|
157
|
-
* This is the next generation of the Zitadel identity platform.
|
|
158
|
-
* OpenAPI spec version: 0.0.1
|
|
159
|
-
*/
|
|
160
|
-
var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
|
|
161
|
-
authorizeDevice: () => authorizeDevice,
|
|
162
|
-
authorizeGet: () => authorizeGet,
|
|
163
|
-
createAuthAttempt: () => createAuthAttempt,
|
|
164
|
-
createFlow: () => createFlow,
|
|
165
|
-
createFlowDefinition: () => createFlowDefinition,
|
|
166
|
-
createHandoff: () => createHandoff,
|
|
167
|
-
createProject: () => createProject,
|
|
168
|
-
createSchema: () => createSchema,
|
|
169
|
-
createSession: () => createSession,
|
|
170
|
-
createTeam: () => createTeam,
|
|
171
|
-
createUser: () => createUser,
|
|
172
|
-
deleteFlowDefinition: () => deleteFlowDefinition,
|
|
173
|
-
endSession: () => endSession,
|
|
174
|
-
exchangeHandoff: () => exchangeHandoff,
|
|
175
|
-
getAuthAttempt: () => getAuthAttempt,
|
|
176
|
-
getAuthorizeDeviceUrl: () => getAuthorizeDeviceUrl,
|
|
177
|
-
getAuthorizeGetUrl: () => getAuthorizeGetUrl,
|
|
178
|
-
getCreateAuthAttemptUrl: () => getCreateAuthAttemptUrl,
|
|
179
|
-
getCreateFlowDefinitionUrl: () => getCreateFlowDefinitionUrl,
|
|
180
|
-
getCreateFlowUrl: () => getCreateFlowUrl,
|
|
181
|
-
getCreateHandoffUrl: () => getCreateHandoffUrl,
|
|
182
|
-
getCreateProjectUrl: () => getCreateProjectUrl,
|
|
183
|
-
getCreateSchemaUrl: () => getCreateSchemaUrl,
|
|
184
|
-
getCreateSessionUrl: () => getCreateSessionUrl,
|
|
185
|
-
getCreateTeamUrl: () => getCreateTeamUrl,
|
|
186
|
-
getCreateUserUrl: () => getCreateUserUrl,
|
|
187
|
-
getDeleteFlowDefinitionUrl: () => getDeleteFlowDefinitionUrl,
|
|
188
|
-
getEndSessionUrl: () => getEndSessionUrl,
|
|
189
|
-
getExchangeHandoffUrl: () => getExchangeHandoffUrl,
|
|
190
|
-
getFlowDefinition: () => getFlowDefinition,
|
|
191
|
-
getFlowStep: () => getFlowStep,
|
|
192
|
-
getGetAuthAttemptUrl: () => getGetAuthAttemptUrl,
|
|
193
|
-
getGetFlowDefinitionUrl: () => getGetFlowDefinitionUrl,
|
|
194
|
-
getGetFlowStepUrl: () => getGetFlowStepUrl,
|
|
195
|
-
getGetHealthUrl: () => getGetHealthUrl,
|
|
196
|
-
getGetKeysUrl: () => getGetKeysUrl,
|
|
197
|
-
getGetLiveUrl: () => getGetLiveUrl,
|
|
198
|
-
getGetMySessionUrl: () => getGetMySessionUrl,
|
|
199
|
-
getGetMyUserUrl: () => getGetMyUserUrl,
|
|
200
|
-
getGetOpenIDConfigurationUrl: () => getGetOpenIDConfigurationUrl,
|
|
201
|
-
getGetProjectUrl: () => getGetProjectUrl,
|
|
202
|
-
getGetReadyUrl: () => getGetReadyUrl,
|
|
203
|
-
getGetSchemaByIdUrl: () => getGetSchemaByIdUrl,
|
|
204
|
-
getGetSessionUrl: () => getGetSessionUrl,
|
|
205
|
-
getGetTeamUrl: () => getGetTeamUrl,
|
|
206
|
-
getGetTokenUrl: () => getGetTokenUrl,
|
|
207
|
-
getGetUserByIDUrl: () => getGetUserByIDUrl,
|
|
208
|
-
getGetUserInfoUrl: () => getGetUserInfoUrl,
|
|
209
|
-
getHealth: () => getHealth,
|
|
210
|
-
getIntrospectUrl: () => getIntrospectUrl,
|
|
211
|
-
getIssueChallengeUrl: () => getIssueChallengeUrl,
|
|
212
|
-
getKeys: () => getKeys,
|
|
213
|
-
getListFlowDefinitionsUrl: () => getListFlowDefinitionsUrl,
|
|
214
|
-
getListSessionsUrl: () => getListSessionsUrl,
|
|
215
|
-
getListUsersUrl: () => getListUsersUrl,
|
|
216
|
-
getLive: () => getLive,
|
|
217
|
-
getMySession: () => getMySession,
|
|
218
|
-
getMyUser: () => getMyUser,
|
|
219
|
-
getOpenIDConfiguration: () => getOpenIDConfiguration,
|
|
220
|
-
getProject: () => getProject,
|
|
221
|
-
getReady: () => getReady,
|
|
222
|
-
getRevokeMySessionUrl: () => getRevokeMySessionUrl,
|
|
223
|
-
getRevokeSessionUrl: () => getRevokeSessionUrl,
|
|
224
|
-
getRevokeTokenUrl: () => getRevokeTokenUrl,
|
|
225
|
-
getSchemaById: () => getSchemaById,
|
|
226
|
-
getSession: () => getSession,
|
|
227
|
-
getSetUserPasswordUrl: () => getSetUserPasswordUrl,
|
|
228
|
-
getSubmitFlowEventUrl: () => getSubmitFlowEventUrl,
|
|
229
|
-
getSubmitFlowStepUrl: () => getSubmitFlowStepUrl,
|
|
230
|
-
getTeam: () => getTeam,
|
|
231
|
-
getToken: () => getToken,
|
|
232
|
-
getUpdateFlowDefinitionUrl: () => getUpdateFlowDefinitionUrl,
|
|
233
|
-
getUserByID: () => getUserByID,
|
|
234
|
-
getUserInfo: () => getUserInfo,
|
|
235
|
-
getVerifyChallengeProofUrl: () => getVerifyChallengeProofUrl,
|
|
236
|
-
introspect: () => introspect,
|
|
237
|
-
issueChallenge: () => issueChallenge,
|
|
238
|
-
listFlowDefinitions: () => listFlowDefinitions,
|
|
239
|
-
listSessions: () => listSessions,
|
|
240
|
-
listUsers: () => listUsers,
|
|
241
|
-
revokeMySession: () => revokeMySession,
|
|
242
|
-
revokeSession: () => revokeSession,
|
|
243
|
-
revokeToken: () => revokeToken,
|
|
244
|
-
setUserPassword: () => setUserPassword,
|
|
245
|
-
submitFlowEvent: () => submitFlowEvent,
|
|
246
|
-
submitFlowStep: () => submitFlowStep,
|
|
247
|
-
updateFlowDefinition: () => updateFlowDefinition,
|
|
248
|
-
verifyChallengeProof: () => verifyChallengeProof
|
|
249
|
-
});
|
|
250
|
-
const getGetOpenIDConfigurationUrl = () => {
|
|
251
|
-
return `${getProxyPath()}/.well-known/openid-configuration`;
|
|
252
|
-
};
|
|
253
|
-
/**
|
|
254
|
-
* Retrieve the OpenID Connect configuration
|
|
255
|
-
* @summary Get OpenID Connect configuration
|
|
256
|
-
*/
|
|
257
|
-
const getOpenIDConfiguration = async (options) => {
|
|
258
|
-
return customFetch(getGetOpenIDConfigurationUrl(), {
|
|
259
|
-
...options,
|
|
260
|
-
method: "GET"
|
|
261
|
-
});
|
|
262
|
-
};
|
|
263
|
-
const getAuthorizeGetUrl = (params) => {
|
|
264
|
-
const normalizedParams = new URLSearchParams();
|
|
265
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
266
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
267
|
-
});
|
|
268
|
-
const stringifiedParams = normalizedParams.toString();
|
|
269
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/auth/authorize?${stringifiedParams}` : `${getProxyPath()}/auth/authorize`;
|
|
270
|
-
};
|
|
271
|
-
/**
|
|
272
|
-
* @summary Authorize a user
|
|
273
|
-
*/
|
|
274
|
-
const authorizeGet = async (params, options) => {
|
|
275
|
-
return customFetch(getAuthorizeGetUrl(params), {
|
|
276
|
-
...options,
|
|
277
|
-
method: "GET"
|
|
278
|
-
});
|
|
279
|
-
};
|
|
280
|
-
const getAuthorizeDeviceUrl = (params) => {
|
|
281
|
-
const normalizedParams = new URLSearchParams();
|
|
282
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
283
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
284
|
-
});
|
|
285
|
-
const stringifiedParams = normalizedParams.toString();
|
|
286
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/auth/device-authorization?${stringifiedParams}` : `${getProxyPath()}/auth/device-authorization`;
|
|
287
|
-
};
|
|
288
|
-
/**
|
|
289
|
-
* @summary Authorize a device
|
|
290
|
-
*/
|
|
291
|
-
const authorizeDevice = async (params, options) => {
|
|
292
|
-
return customFetch(getAuthorizeDeviceUrl(params), {
|
|
293
|
-
...options,
|
|
294
|
-
method: "GET"
|
|
295
|
-
});
|
|
296
|
-
};
|
|
297
|
-
const getEndSessionUrl = (params) => {
|
|
298
|
-
const normalizedParams = new URLSearchParams();
|
|
299
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
300
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
301
|
-
});
|
|
302
|
-
const stringifiedParams = normalizedParams.toString();
|
|
303
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/auth/end-session?${stringifiedParams}` : `${getProxyPath()}/auth/end-session`;
|
|
304
|
-
};
|
|
305
|
-
/**
|
|
306
|
-
* @summary End a session
|
|
307
|
-
*/
|
|
308
|
-
const endSession = async (params, options) => {
|
|
309
|
-
return customFetch(getEndSessionUrl(params), {
|
|
310
|
-
...options,
|
|
311
|
-
method: "GET"
|
|
312
|
-
});
|
|
313
|
-
};
|
|
314
|
-
const getIntrospectUrl = () => {
|
|
315
|
-
return `${getProxyPath()}/auth/introspect`;
|
|
316
|
-
};
|
|
317
|
-
/**
|
|
318
|
-
* @summary Introspect a token
|
|
319
|
-
*/
|
|
320
|
-
const introspect = async (introspectBody, options) => {
|
|
321
|
-
const formUrlEncoded = new URLSearchParams();
|
|
322
|
-
formUrlEncoded.append(`token`, introspectBody.token);
|
|
323
|
-
if (introspectBody.token_type_hint !== void 0) formUrlEncoded.append(`token_type_hint`, introspectBody.token_type_hint);
|
|
324
|
-
return customFetch(getIntrospectUrl(), {
|
|
325
|
-
...options,
|
|
326
|
-
method: "POST",
|
|
327
|
-
headers: {
|
|
328
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
329
|
-
...options?.headers
|
|
330
|
-
},
|
|
331
|
-
body: formUrlEncoded
|
|
332
|
-
});
|
|
333
|
-
};
|
|
334
|
-
const getGetKeysUrl = () => {
|
|
335
|
-
return `${getProxyPath()}/auth/keys`;
|
|
336
|
-
};
|
|
337
|
-
/**
|
|
338
|
-
* @summary Get public keys
|
|
339
|
-
*/
|
|
340
|
-
const getKeys = async (options) => {
|
|
341
|
-
return customFetch(getGetKeysUrl(), {
|
|
342
|
-
...options,
|
|
343
|
-
method: "GET"
|
|
344
|
-
});
|
|
345
|
-
};
|
|
346
|
-
const getRevokeTokenUrl = () => {
|
|
347
|
-
return `${getProxyPath()}/auth/revoke`;
|
|
348
|
-
};
|
|
349
|
-
/**
|
|
350
|
-
* @summary Revoke an access token or refresh token
|
|
351
|
-
*/
|
|
352
|
-
const revokeToken = async (revokeTokenBody, options) => {
|
|
353
|
-
const formUrlEncoded = new URLSearchParams();
|
|
354
|
-
if (revokeTokenBody.token !== void 0) formUrlEncoded.append(`token`, revokeTokenBody.token);
|
|
355
|
-
if (revokeTokenBody.token_type_hint !== void 0) formUrlEncoded.append(`token_type_hint`, revokeTokenBody.token_type_hint);
|
|
356
|
-
return customFetch(getRevokeTokenUrl(), {
|
|
357
|
-
...options,
|
|
358
|
-
method: "POST",
|
|
359
|
-
headers: {
|
|
360
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
361
|
-
...options?.headers
|
|
362
|
-
},
|
|
363
|
-
body: formUrlEncoded
|
|
364
|
-
});
|
|
365
|
-
};
|
|
366
|
-
const getGetTokenUrl = () => {
|
|
367
|
-
return `${getProxyPath()}/auth/token`;
|
|
368
|
-
};
|
|
369
|
-
/**
|
|
370
|
-
* @summary Get accesstoken
|
|
371
|
-
*/
|
|
372
|
-
const getToken = async (getTokenBody, options) => {
|
|
373
|
-
const formUrlEncoded = new URLSearchParams();
|
|
374
|
-
if (getTokenBody.code !== void 0) formUrlEncoded.append(`code`, getTokenBody.code);
|
|
375
|
-
if (getTokenBody.client_assertion !== void 0) formUrlEncoded.append(`client_assertion`, getTokenBody.client_assertion);
|
|
376
|
-
if (getTokenBody.client_assertion_type !== void 0) formUrlEncoded.append(`client_assertion_type`, getTokenBody.client_assertion_type);
|
|
377
|
-
if (getTokenBody.client_id !== void 0) formUrlEncoded.append(`client_id`, getTokenBody.client_id);
|
|
378
|
-
if (getTokenBody.client_secret !== void 0) formUrlEncoded.append(`client_secret`, getTokenBody.client_secret);
|
|
379
|
-
if (getTokenBody.code_verifier !== void 0) formUrlEncoded.append(`code_verifier`, getTokenBody.code_verifier);
|
|
380
|
-
if (getTokenBody.grant_type !== void 0) formUrlEncoded.append(`grant_type`, getTokenBody.grant_type);
|
|
381
|
-
if (getTokenBody.redirect_uri !== void 0) formUrlEncoded.append(`redirect_uri`, getTokenBody.redirect_uri);
|
|
382
|
-
return customFetch(getGetTokenUrl(), {
|
|
383
|
-
...options,
|
|
384
|
-
method: "POST",
|
|
385
|
-
headers: {
|
|
386
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
387
|
-
...options?.headers
|
|
388
|
-
},
|
|
389
|
-
body: formUrlEncoded
|
|
390
|
-
});
|
|
391
|
-
};
|
|
392
|
-
const getGetUserInfoUrl = () => {
|
|
393
|
-
return `${getProxyPath()}/auth/userinfo`;
|
|
394
|
-
};
|
|
395
|
-
/**
|
|
396
|
-
* @summary Get user info
|
|
397
|
-
*/
|
|
398
|
-
const getUserInfo = async (options) => {
|
|
399
|
-
return customFetch(getGetUserInfoUrl(), {
|
|
400
|
-
...options,
|
|
401
|
-
method: "GET"
|
|
402
|
-
});
|
|
403
|
-
};
|
|
404
|
-
const getGetHealthUrl = () => {
|
|
405
|
-
return `${getProxyPath()}/healthz`;
|
|
406
|
-
};
|
|
407
|
-
/**
|
|
408
|
-
* Check whether the server is healthy
|
|
409
|
-
* @summary Check server health
|
|
410
|
-
*/
|
|
411
|
-
const getHealth = async (options) => {
|
|
412
|
-
return customFetch(getGetHealthUrl(), {
|
|
413
|
-
...options,
|
|
414
|
-
method: "GET"
|
|
415
|
-
});
|
|
416
|
-
};
|
|
417
|
-
const getGetLiveUrl = () => {
|
|
418
|
-
return `${getProxyPath()}/livez`;
|
|
419
|
-
};
|
|
420
|
-
/**
|
|
421
|
-
* Check whether the server is started
|
|
422
|
-
* @summary Check server liveness
|
|
423
|
-
*/
|
|
424
|
-
const getLive = async (options) => {
|
|
425
|
-
return customFetch(getGetLiveUrl(), {
|
|
426
|
-
...options,
|
|
427
|
-
method: "GET"
|
|
428
|
-
});
|
|
429
|
-
};
|
|
430
|
-
const getGetReadyUrl = () => {
|
|
431
|
-
return `${getProxyPath()}/readyz`;
|
|
432
|
-
};
|
|
433
|
-
/**
|
|
434
|
-
* Check whether the server is ready to accept requests
|
|
435
|
-
* @summary Check server readiness
|
|
436
|
-
*/
|
|
437
|
-
const getReady = async (options) => {
|
|
438
|
-
return customFetch(getGetReadyUrl(), {
|
|
439
|
-
...options,
|
|
440
|
-
method: "GET"
|
|
441
|
-
});
|
|
442
|
-
};
|
|
443
|
-
const getCreateUserUrl = (params) => {
|
|
444
|
-
const normalizedParams = new URLSearchParams();
|
|
445
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
446
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
447
|
-
});
|
|
448
|
-
const stringifiedParams = normalizedParams.toString();
|
|
449
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/users?${stringifiedParams}` : `${getProxyPath()}/users`;
|
|
450
|
-
};
|
|
451
|
-
/**
|
|
452
|
-
* @summary Create user
|
|
453
|
-
*/
|
|
454
|
-
const createUser = async (createUserBody, params, options) => {
|
|
455
|
-
return customFetch(getCreateUserUrl(params), {
|
|
456
|
-
...options,
|
|
457
|
-
method: "POST",
|
|
458
|
-
headers: {
|
|
459
|
-
"Content-Type": "application/json",
|
|
460
|
-
...options?.headers
|
|
461
|
-
},
|
|
462
|
-
body: JSON.stringify(createUserBody)
|
|
463
|
-
});
|
|
464
|
-
};
|
|
465
|
-
const getListUsersUrl = (params) => {
|
|
466
|
-
const normalizedParams = new URLSearchParams();
|
|
467
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
468
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
469
|
-
});
|
|
470
|
-
const stringifiedParams = normalizedParams.toString();
|
|
471
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/users?${stringifiedParams}` : `${getProxyPath()}/users`;
|
|
472
|
-
};
|
|
473
|
-
/**
|
|
474
|
-
* @summary List users
|
|
475
|
-
*/
|
|
476
|
-
const listUsers = async (params, options) => {
|
|
477
|
-
return customFetch(getListUsersUrl(params), {
|
|
478
|
-
...options,
|
|
479
|
-
method: "GET"
|
|
480
|
-
});
|
|
481
|
-
};
|
|
482
|
-
const getGetUserByIDUrl = (userId, params) => {
|
|
483
|
-
const normalizedParams = new URLSearchParams();
|
|
484
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
485
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
486
|
-
});
|
|
487
|
-
const stringifiedParams = normalizedParams.toString();
|
|
488
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}?${stringifiedParams}` : `${getProxyPath()}/users/${userId}`;
|
|
489
|
-
};
|
|
490
|
-
/**
|
|
491
|
-
* @summary Get user by ID
|
|
492
|
-
*/
|
|
493
|
-
const getUserByID = async (userId, params, options) => {
|
|
494
|
-
return customFetch(getGetUserByIDUrl(userId, params), {
|
|
495
|
-
...options,
|
|
496
|
-
method: "GET"
|
|
497
|
-
});
|
|
498
|
-
};
|
|
499
|
-
const getSetUserPasswordUrl = (userId, params) => {
|
|
500
|
-
const normalizedParams = new URLSearchParams();
|
|
501
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
502
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
503
|
-
});
|
|
504
|
-
const stringifiedParams = normalizedParams.toString();
|
|
505
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}/password?${stringifiedParams}` : `${getProxyPath()}/users/${userId}/password`;
|
|
506
|
-
};
|
|
507
|
-
/**
|
|
508
|
-
* @summary Set user password
|
|
509
|
-
*/
|
|
510
|
-
const setUserPassword = async (userId, setUserPasswordBody, params, options) => {
|
|
511
|
-
return customFetch(getSetUserPasswordUrl(userId, params), {
|
|
512
|
-
...options,
|
|
513
|
-
method: "PUT",
|
|
514
|
-
headers: {
|
|
515
|
-
"Content-Type": "application/json",
|
|
516
|
-
...options?.headers
|
|
517
|
-
},
|
|
518
|
-
body: JSON.stringify(setUserPasswordBody)
|
|
519
|
-
});
|
|
520
|
-
};
|
|
521
|
-
const getGetMyUserUrl = () => {
|
|
522
|
-
return `${getProxyPath()}/users/me`;
|
|
523
|
-
};
|
|
524
|
-
/**
|
|
525
|
-
* @summary Get my user information
|
|
526
|
-
*/
|
|
527
|
-
const getMyUser = async (options) => {
|
|
528
|
-
return customFetch(getGetMyUserUrl(), {
|
|
529
|
-
...options,
|
|
530
|
-
method: "GET"
|
|
531
|
-
});
|
|
532
|
-
};
|
|
533
|
-
const getCreateFlowUrl = () => {
|
|
534
|
-
return `${getProxyPath()}/flow`;
|
|
535
|
-
};
|
|
536
|
-
/**
|
|
537
|
-
* Resolves a flow definition based on purpose + audience context and returns
|
|
538
|
-
the first capability step. Creates a new session implicitly unless
|
|
539
|
-
`session_id` is provided (for step-up / reauth on an existing session).
|
|
540
|
-
|
|
541
|
-
The response contains an `id` field — the flow handle. Use it as the path
|
|
542
|
-
parameter for all subsequent `/flow/{id}/submit` and `/flow/{id}/event` calls.
|
|
543
|
-
|
|
544
|
-
The response also sets an encrypted `HttpOnly` cookie (`_zflow`) containing
|
|
545
|
-
the flow's orchestration state (current step, collected data, history).
|
|
546
|
-
The server is stateless between requests — all flow state lives in this
|
|
547
|
-
cookie. The browser sends it automatically on subsequent requests.
|
|
548
|
-
|
|
549
|
-
* @summary Start a new flow
|
|
550
|
-
*/
|
|
551
|
-
const createFlow = async (createFlowBody, options) => {
|
|
552
|
-
return customFetch(getCreateFlowUrl(), {
|
|
553
|
-
...options,
|
|
554
|
-
method: "POST",
|
|
555
|
-
headers: {
|
|
556
|
-
"Content-Type": "application/json",
|
|
557
|
-
...options?.headers
|
|
558
|
-
},
|
|
559
|
-
body: JSON.stringify(createFlowBody)
|
|
560
|
-
});
|
|
561
|
-
};
|
|
562
|
-
const getGetFlowStepUrl = (id) => {
|
|
563
|
-
return `${getProxyPath()}/flow/${id}`;
|
|
564
|
-
};
|
|
565
|
-
/**
|
|
566
|
-
* Returns the current capability step without advancing the state machine.
|
|
567
|
-
Useful for page reloads or re-rendering after a network error.
|
|
568
|
-
|
|
569
|
-
* @summary Get current step (re-render)
|
|
570
|
-
*/
|
|
571
|
-
const getFlowStep = async (id, options) => {
|
|
572
|
-
return customFetch(getGetFlowStepUrl(id), {
|
|
573
|
-
...options,
|
|
574
|
-
method: "GET"
|
|
575
|
-
});
|
|
576
|
-
};
|
|
577
|
-
const getSubmitFlowStepUrl = (id) => {
|
|
578
|
-
return `${getProxyPath()}/flow/${id}/submit`;
|
|
579
|
-
};
|
|
580
|
-
/**
|
|
581
|
-
* Submits user input for the current step. The server validates,
|
|
582
|
-
processes (e.g., verifies a credential), advances the state machine
|
|
583
|
-
through any invisible steps, and returns the next visible step.
|
|
584
|
-
|
|
585
|
-
The response sets an updated encrypted `HttpOnly` cookie (`_zflow`)
|
|
586
|
-
with the new flow state. The server is stateless — all orchestration
|
|
587
|
-
state is carried in this cookie between requests.
|
|
588
|
-
|
|
589
|
-
**Important:** The `id` in the response may differ from the `id` used in
|
|
590
|
-
the request. This happens when a flow pivots (pushes a new flow onto the
|
|
591
|
-
stack) or when a stacked flow completes (auto-pops to the parent flow).
|
|
592
|
-
Always use the `id` from the latest response for the next request.
|
|
593
|
-
|
|
594
|
-
## Flow completion
|
|
595
|
-
|
|
596
|
-
When `step.type` is `complete`, the flow is terminal. The `step.behavior`
|
|
597
|
-
field tells the frontend what to do:
|
|
598
|
-
|
|
599
|
-
| `behavior` | Action |
|
|
600
|
-
|------------- |------------------------------------------------------------|
|
|
601
|
-
| `redirect` | Navigate to `redirect_uri` (OIDC/SAML auth request done). |
|
|
602
|
-
| `show` | Render the step as a success screen (e.g., registration). |
|
|
603
|
-
|
|
604
|
-
A `complete` step is only returned when the **entire flow stack** is done.
|
|
605
|
-
If a stacked flow (e.g., recovery pivoted from login) finishes, the server
|
|
606
|
-
auto-pops to the parent flow and returns the parent's next step — the
|
|
607
|
-
frontend never sees a `complete` for intermediate flows.
|
|
608
|
-
|
|
609
|
-
* @summary Submit step data and advance
|
|
610
|
-
*/
|
|
611
|
-
const submitFlowStep = async (id, submitFlowStepBody, options) => {
|
|
612
|
-
return customFetch(getSubmitFlowStepUrl(id), {
|
|
613
|
-
...options,
|
|
614
|
-
method: "POST",
|
|
615
|
-
headers: {
|
|
616
|
-
"Content-Type": "application/json",
|
|
617
|
-
...options?.headers
|
|
618
|
-
},
|
|
619
|
-
body: JSON.stringify(submitFlowStepBody)
|
|
620
|
-
});
|
|
621
|
-
};
|
|
622
|
-
const getSubmitFlowEventUrl = (id) => {
|
|
623
|
-
return `${getProxyPath()}/flow/${id}/event`;
|
|
624
|
-
};
|
|
625
|
-
/**
|
|
626
|
-
* Submits telemetry or fingerprint data from the frontend.
|
|
627
|
-
Does not advance the state machine. Used for risk evaluation.
|
|
628
|
-
|
|
629
|
-
* @summary Submit client-side event
|
|
630
|
-
*/
|
|
631
|
-
const submitFlowEvent = async (id, submitFlowEventBody, options) => {
|
|
632
|
-
return customFetch(getSubmitFlowEventUrl(id), {
|
|
633
|
-
...options,
|
|
634
|
-
method: "POST",
|
|
635
|
-
headers: {
|
|
636
|
-
"Content-Type": "application/json",
|
|
637
|
-
...options?.headers
|
|
638
|
-
},
|
|
639
|
-
body: JSON.stringify(submitFlowEventBody)
|
|
640
|
-
});
|
|
641
|
-
};
|
|
642
|
-
const getCreateAuthAttemptUrl = () => {
|
|
643
|
-
return `${getProxyPath()}/auth_attempts`;
|
|
644
|
-
};
|
|
645
|
-
/**
|
|
646
|
-
* Starts a new authentication attempt. This is the entry point for the auth_attempts state machine.
|
|
647
|
-
|
|
648
|
-
An attempt is an ephemeral (15-minute TTL) state machine that drives a single authentication round.
|
|
649
|
-
It accepts factor challenges, verifies proofs, and completes into a session or handoff token.
|
|
650
|
-
|
|
651
|
-
Accepts a project_id and challenge_nonce (from POST /bootstrap/challenge). For step-up re-auth,
|
|
652
|
-
also include session_id to add factors to an existing session.
|
|
653
|
-
|
|
654
|
-
* @summary Create a new authentication attempt
|
|
655
|
-
*/
|
|
656
|
-
const createAuthAttempt = async (createAuthAttemptBody, options) => {
|
|
657
|
-
return customFetch(getCreateAuthAttemptUrl(), {
|
|
658
|
-
...options,
|
|
659
|
-
method: "POST",
|
|
660
|
-
headers: {
|
|
661
|
-
"Content-Type": "application/json",
|
|
662
|
-
...options?.headers
|
|
663
|
-
},
|
|
664
|
-
body: JSON.stringify(createAuthAttemptBody)
|
|
665
|
-
});
|
|
666
|
-
};
|
|
667
|
-
const getGetAuthAttemptUrl = (attemptId) => {
|
|
668
|
-
return `${getProxyPath()}/auth_attempts/${attemptId}`;
|
|
669
|
-
};
|
|
670
|
-
/**
|
|
671
|
-
* Polls the current state of an authentication attempt.
|
|
672
|
-
|
|
673
|
-
Returns the attempt's state, available factors for the next challenge,
|
|
674
|
-
challenges issued so far, and any errors preventing progress.
|
|
675
|
-
|
|
676
|
-
Use this for polling during long-running factor verifications (e.g., waiting for
|
|
677
|
-
a federated IdP callback or a device flow).
|
|
678
|
-
|
|
679
|
-
* @summary Get authentication attempt state
|
|
680
|
-
*/
|
|
681
|
-
const getAuthAttempt = async (attemptId, options) => {
|
|
682
|
-
return customFetch(getGetAuthAttemptUrl(attemptId), {
|
|
683
|
-
...options,
|
|
684
|
-
method: "GET"
|
|
685
|
-
});
|
|
686
|
-
};
|
|
687
|
-
const getIssueChallengeUrl = (attemptId) => {
|
|
688
|
-
return `${getProxyPath()}/auth_attempts/${attemptId}/challenges`;
|
|
689
|
-
};
|
|
690
|
-
/**
|
|
691
|
-
* Issues a single-factor verification challenge within an auth attempt.
|
|
692
|
-
|
|
693
|
-
This advances the authentication state machine by requesting a specific factor method
|
|
694
|
-
(password, passkey, TOTP, OTP via SMS, etc.). The server responds with challenge details
|
|
695
|
-
including method, metadata, and any UI hints. The client then verifies the proof
|
|
696
|
-
by calling POST /auth_attempts/{attempt_id}/challenges/{challenge_id}/verify.
|
|
697
|
-
|
|
698
|
-
* @summary Issue a factor challenge
|
|
699
|
-
*/
|
|
700
|
-
const issueChallenge = async (attemptId, issueChallengeBody, options) => {
|
|
701
|
-
return customFetch(getIssueChallengeUrl(attemptId), {
|
|
702
|
-
...options,
|
|
703
|
-
method: "POST",
|
|
704
|
-
headers: {
|
|
705
|
-
"Content-Type": "application/json",
|
|
706
|
-
...options?.headers
|
|
707
|
-
},
|
|
708
|
-
body: JSON.stringify(issueChallengeBody)
|
|
709
|
-
});
|
|
710
|
-
};
|
|
711
|
-
const getVerifyChallengeProofUrl = (attemptId, challengeId) => {
|
|
712
|
-
return `${getProxyPath()}/auth_attempts/${attemptId}/challenges/${challengeId}/verify`;
|
|
713
|
-
};
|
|
714
|
-
/**
|
|
715
|
-
* Submits a proof (credential, code, assertion) to verify a factor challenge.
|
|
716
|
-
|
|
717
|
-
The proof format depends on the challenge method. For example:
|
|
718
|
-
- `password` method: { password: "…" }
|
|
719
|
-
- `totp` method: { totp: { code: "123456" } }
|
|
720
|
-
- `passkey` method: { passkey: { assertion: "…" } }
|
|
721
|
-
- `recovery_code` method: { recovery_code: "…" }
|
|
722
|
-
|
|
723
|
-
On successful verification, the factor is written to the auth attempt.
|
|
724
|
-
The attempt moves to the next pending challenge or completes if all required factors are verified.
|
|
725
|
-
|
|
726
|
-
* @summary Verify a factor proof
|
|
727
|
-
*/
|
|
728
|
-
const verifyChallengeProof = async (attemptId, challengeId, verifyChallengeProofBody, options) => {
|
|
729
|
-
return customFetch(getVerifyChallengeProofUrl(attemptId, challengeId), {
|
|
730
|
-
...options,
|
|
731
|
-
method: "POST",
|
|
732
|
-
headers: {
|
|
733
|
-
"Content-Type": "application/json",
|
|
734
|
-
...options?.headers
|
|
735
|
-
},
|
|
736
|
-
body: JSON.stringify(verifyChallengeProofBody)
|
|
737
|
-
});
|
|
738
|
-
};
|
|
739
|
-
const getCreateHandoffUrl = (attemptId) => {
|
|
740
|
-
return `${getProxyPath()}/auth_attempts/${attemptId}/handoff`;
|
|
741
|
-
};
|
|
742
|
-
/**
|
|
743
|
-
* Completes the authentication attempt and mints a `handoff_token`.
|
|
744
|
-
|
|
745
|
-
Call this after all required factors have been verified and the attempt is in `completed` state.
|
|
746
|
-
The handoff token is short-lived (≤60 seconds) and must be exchanged at
|
|
747
|
-
POST /sessions/exchange to receive the final session and session_token.
|
|
748
|
-
|
|
749
|
-
The handoff token is:
|
|
750
|
-
- Single-use (atomic exchange, no retry)
|
|
751
|
-
- Audience-bound (requires matching project key for exchange)
|
|
752
|
-
- Idempotency-safe within a 5-minute window (see conventions)
|
|
753
|
-
|
|
754
|
-
* @summary Complete authentication and create handoff token
|
|
755
|
-
*/
|
|
756
|
-
const createHandoff = async (attemptId, options) => {
|
|
757
|
-
return customFetch(getCreateHandoffUrl(attemptId), {
|
|
758
|
-
...options,
|
|
759
|
-
method: "POST"
|
|
760
|
-
});
|
|
761
|
-
};
|
|
762
|
-
const getCreateProjectUrl = () => {
|
|
763
|
-
return `${getProxyPath()}/projects`;
|
|
764
|
-
};
|
|
765
|
-
/**
|
|
766
|
-
* @summary Create project
|
|
767
|
-
*/
|
|
768
|
-
const createProject = async (createProjectBody, options) => {
|
|
769
|
-
return customFetch(getCreateProjectUrl(), {
|
|
770
|
-
...options,
|
|
771
|
-
method: "POST",
|
|
772
|
-
headers: {
|
|
773
|
-
"Content-Type": "application/json",
|
|
774
|
-
...options?.headers
|
|
775
|
-
},
|
|
776
|
-
body: JSON.stringify(createProjectBody)
|
|
777
|
-
});
|
|
778
|
-
};
|
|
779
|
-
const getGetProjectUrl = (projectId) => {
|
|
780
|
-
return `${getProxyPath()}/projects/${projectId}`;
|
|
781
|
-
};
|
|
782
|
-
/**
|
|
783
|
-
* Returns the current state of a project.
|
|
784
|
-
|
|
785
|
-
* @summary Get project
|
|
786
|
-
*/
|
|
787
|
-
const getProject = async (projectId, options) => {
|
|
788
|
-
return customFetch(getGetProjectUrl(projectId), {
|
|
789
|
-
...options,
|
|
790
|
-
method: "GET"
|
|
791
|
-
});
|
|
792
|
-
};
|
|
793
|
-
const getCreateSessionUrl = () => {
|
|
794
|
-
return `${getProxyPath()}/sessions`;
|
|
795
|
-
};
|
|
796
|
-
/**
|
|
797
|
-
* Creates an anonymous session shell with no user and no factors (`state: building`).
|
|
798
|
-
|
|
799
|
-
This is optional — an `auth_attempt` will create a session implicitly if none is provided.
|
|
800
|
-
Use this explicitly when you want to:
|
|
801
|
-
- Pre-allocate a `session_id` before the user is known, so device/telemetry signals
|
|
802
|
-
can be correlated with the eventual authenticated session from the start.
|
|
803
|
-
- Track anonymous state (bot detection, device fingerprint) that survives until authentication.
|
|
804
|
-
|
|
805
|
-
The returned `session_token` authorises GET and DELETE on this session.
|
|
806
|
-
It is superseded when a handoff exchange completes — clients must replace it at that point.
|
|
807
|
-
|
|
808
|
-
Anonymous sessions expire aggressively (10-minute TTL). The TTL resets to the configured
|
|
809
|
-
full session TTL when the first authentication factor is written via a completing `auth_attempt`.
|
|
810
|
-
|
|
811
|
-
* @summary Create an anonymous session shell
|
|
812
|
-
*/
|
|
813
|
-
const createSession = async (createSessionBody, options) => {
|
|
814
|
-
return customFetch(getCreateSessionUrl(), {
|
|
815
|
-
...options,
|
|
816
|
-
method: "POST",
|
|
817
|
-
headers: {
|
|
818
|
-
"Content-Type": "application/json",
|
|
819
|
-
...options?.headers
|
|
820
|
-
},
|
|
821
|
-
body: JSON.stringify(createSessionBody)
|
|
822
|
-
});
|
|
823
|
-
};
|
|
824
|
-
const getListSessionsUrl = (params) => {
|
|
825
|
-
const normalizedParams = new URLSearchParams();
|
|
826
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
827
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
828
|
-
});
|
|
829
|
-
const stringifiedParams = normalizedParams.toString();
|
|
830
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions?${stringifiedParams}` : `${getProxyPath()}/sessions`;
|
|
831
|
-
};
|
|
832
|
-
/**
|
|
833
|
-
* Returns a paginated list of sessions for a project.
|
|
834
|
-
Requires a project service key (OAuth2 client credentials).
|
|
835
|
-
|
|
836
|
-
* @summary List sessions
|
|
837
|
-
*/
|
|
838
|
-
const listSessions = async (params, options) => {
|
|
839
|
-
return customFetch(getListSessionsUrl(params), {
|
|
840
|
-
...options,
|
|
841
|
-
method: "GET"
|
|
842
|
-
});
|
|
843
|
-
};
|
|
844
|
-
const getExchangeHandoffUrl = (params) => {
|
|
845
|
-
const normalizedParams = new URLSearchParams();
|
|
846
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
847
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
848
|
-
});
|
|
849
|
-
const stringifiedParams = normalizedParams.toString();
|
|
850
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions/exchange?${stringifiedParams}` : `${getProxyPath()}/sessions/exchange`;
|
|
851
|
-
};
|
|
852
|
-
/**
|
|
853
|
-
* Consumes a one-time `handoff_token` minted by `POST /auth_attempts/{id}/handoff`
|
|
854
|
-
and returns the resulting session and a `session_token`.
|
|
855
|
-
|
|
856
|
-
The server resolves the originating `auth_attempt` from the token and then:
|
|
857
|
-
|
|
858
|
-
| Originating auth_attempt | Outcome |
|
|
859
|
-
|---|---|
|
|
860
|
-
| No `session_id` | A new authenticated session is **created**. |
|
|
861
|
-
| `session_id` points to an anonymous shell | Existing session is **upgraded** — user and factors written in, TTL reset to full session TTL. |
|
|
862
|
-
| `session_id` points to an active session (step-up) | Existing session is **upgraded** — new factors merged, `assurance_levels[]` expanded. |
|
|
863
|
-
|
|
864
|
-
The response shape is identical in all three cases.
|
|
865
|
-
|
|
866
|
-
The `session_token` supersedes any previously issued `session_token` for the same session.
|
|
867
|
-
Clients must replace their stored token at this point.
|
|
868
|
-
|
|
869
|
-
Requires a project service key (OAuth2 client credentials).
|
|
870
|
-
|
|
871
|
-
* @summary Exchange handoff token for a session
|
|
872
|
-
*/
|
|
873
|
-
const exchangeHandoff = async (exchangeHandoffBody, params, options) => {
|
|
874
|
-
return customFetch(getExchangeHandoffUrl(params), {
|
|
875
|
-
...options,
|
|
876
|
-
method: "POST",
|
|
877
|
-
headers: {
|
|
878
|
-
"Content-Type": "application/json",
|
|
879
|
-
...options?.headers
|
|
880
|
-
},
|
|
881
|
-
body: JSON.stringify(exchangeHandoffBody)
|
|
882
|
-
});
|
|
883
|
-
};
|
|
884
|
-
const getGetSessionUrl = (sessionId, params) => {
|
|
885
|
-
const normalizedParams = new URLSearchParams();
|
|
886
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
887
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
888
|
-
});
|
|
889
|
-
const stringifiedParams = normalizedParams.toString();
|
|
890
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions/${sessionId}?${stringifiedParams}` : `${getProxyPath()}/sessions/${sessionId}`;
|
|
891
|
-
};
|
|
892
|
-
/**
|
|
893
|
-
* Returns the current state of a session including its factors and all currently
|
|
894
|
-
satisfied assurance levels.
|
|
895
|
-
|
|
896
|
-
`assurance_levels[]` may shrink over time as factor freshness windows expire,
|
|
897
|
-
without the session itself expiring. Use step-up authentication (a new `auth_attempt`
|
|
898
|
-
against the same `session_id`) to restore a dropped assurance level.
|
|
899
|
-
|
|
900
|
-
* @summary Get session state
|
|
901
|
-
*/
|
|
902
|
-
const getSession = async (sessionId, params, options) => {
|
|
903
|
-
return customFetch(getGetSessionUrl(sessionId, params), {
|
|
904
|
-
...options,
|
|
905
|
-
method: "GET"
|
|
906
|
-
});
|
|
907
|
-
};
|
|
908
|
-
const getRevokeSessionUrl = (sessionId, params) => {
|
|
909
|
-
const normalizedParams = new URLSearchParams();
|
|
910
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
911
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
912
|
-
});
|
|
913
|
-
const stringifiedParams = normalizedParams.toString();
|
|
914
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions/${sessionId}?${stringifiedParams}` : `${getProxyPath()}/sessions/${sessionId}`;
|
|
915
|
-
};
|
|
916
|
-
/**
|
|
917
|
-
* Revokes the session immediately (`state: revoked`). This is the logout operation.
|
|
918
|
-
|
|
919
|
-
The session_token issued at creation (or superseded by a handoff exchange) is required.
|
|
920
|
-
After revocation, any tokens derived from this session are invalidated.
|
|
921
|
-
|
|
922
|
-
* @summary Revoke session
|
|
923
|
-
*/
|
|
924
|
-
const revokeSession = async (sessionId, params, options) => {
|
|
925
|
-
return customFetch(getRevokeSessionUrl(sessionId, params), {
|
|
926
|
-
...options,
|
|
927
|
-
method: "DELETE"
|
|
928
|
-
});
|
|
929
|
-
};
|
|
930
|
-
const getGetMySessionUrl = () => {
|
|
931
|
-
return `${getProxyPath()}/sessions/me`;
|
|
932
|
-
};
|
|
933
|
-
/**
|
|
934
|
-
* Returns the current state of the current session including its factors and all currently
|
|
935
|
-
satisfied assurance levels.
|
|
936
|
-
|
|
937
|
-
`assurance_levels[]` may shrink over time as factor freshness windows expire,
|
|
938
|
-
without the session itself expiring. Use step-up authentication (a new `auth_attempt`
|
|
939
|
-
against the same `session_id`) to restore a dropped assurance level.
|
|
940
|
-
|
|
941
|
-
* @summary Get my session state
|
|
942
|
-
*/
|
|
943
|
-
const getMySession = async (options) => {
|
|
944
|
-
return customFetch(getGetMySessionUrl(), {
|
|
945
|
-
...options,
|
|
946
|
-
method: "GET"
|
|
947
|
-
});
|
|
948
|
-
};
|
|
949
|
-
const getRevokeMySessionUrl = () => {
|
|
950
|
-
return `${getProxyPath()}/sessions/me`;
|
|
951
|
-
};
|
|
952
|
-
/**
|
|
953
|
-
* Revokes the session immediately (`state: revoked`). This is the logout operation.
|
|
954
|
-
|
|
955
|
-
The __nextgen_session cookie issued at creation (or superseded by a handoff exchange) is required.
|
|
956
|
-
After revocation, any tokens derived from this session are invalidated including the cookie itself, which is cleared in the response.
|
|
957
|
-
|
|
958
|
-
* @summary Revoke my session
|
|
959
|
-
*/
|
|
960
|
-
const revokeMySession = async (options) => {
|
|
961
|
-
return customFetch(getRevokeMySessionUrl(), {
|
|
962
|
-
...options,
|
|
963
|
-
method: "DELETE"
|
|
964
|
-
});
|
|
965
|
-
};
|
|
966
|
-
const getCreateSchemaUrl = (params) => {
|
|
967
|
-
const normalizedParams = new URLSearchParams();
|
|
968
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
969
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
970
|
-
});
|
|
971
|
-
const stringifiedParams = normalizedParams.toString();
|
|
972
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/schemas?${stringifiedParams}` : `${getProxyPath()}/schemas`;
|
|
973
|
-
};
|
|
974
|
-
/**
|
|
975
|
-
* Create a new schema. The schema definition must include a unique $id field,
|
|
976
|
-
which will be used to identify the schema in future requests. The $id must
|
|
977
|
-
be a valid URI and should ideally point to the location where the schema
|
|
978
|
-
can be accessed.
|
|
979
|
-
|
|
980
|
-
The schema can either be a concrete schema, e.g. a user schema, or a
|
|
981
|
-
schema-url which will be resolved by the server.
|
|
982
|
-
|
|
983
|
-
* @summary Create new schema
|
|
984
|
-
*/
|
|
985
|
-
const createSchema = async (createSchemaBody, params, options) => {
|
|
986
|
-
return customFetch(getCreateSchemaUrl(params), {
|
|
987
|
-
...options,
|
|
988
|
-
method: "POST",
|
|
989
|
-
headers: {
|
|
990
|
-
"Content-Type": "application/json",
|
|
991
|
-
...options?.headers
|
|
992
|
-
},
|
|
993
|
-
body: JSON.stringify(createSchemaBody)
|
|
994
|
-
});
|
|
995
|
-
};
|
|
996
|
-
const getGetSchemaByIdUrl = (id, params) => {
|
|
997
|
-
const normalizedParams = new URLSearchParams();
|
|
998
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
999
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1000
|
-
});
|
|
1001
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1002
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/schemas/${id}?${stringifiedParams}` : `${getProxyPath()}/schemas/${id}`;
|
|
1003
|
-
};
|
|
1004
|
-
/**
|
|
1005
|
-
* Get a schema by its ID. This will return the default revision of the schema.
|
|
1006
|
-
* @summary Get schema by ID
|
|
1007
|
-
*/
|
|
1008
|
-
const getSchemaById = async (id, params, options) => {
|
|
1009
|
-
return customFetch(getGetSchemaByIdUrl(id, params), {
|
|
1010
|
-
...options,
|
|
1011
|
-
method: "GET"
|
|
1012
|
-
});
|
|
1013
|
-
};
|
|
1014
|
-
const getCreateFlowDefinitionUrl = () => {
|
|
1015
|
-
return `${getProxyPath()}/flow_definitions`;
|
|
1016
|
-
};
|
|
1017
|
-
/**
|
|
1018
|
-
* Creates a new flow definition.
|
|
1019
|
-
Flow definitions are templates that define the sequence of steps (capabilities)
|
|
1020
|
-
for a particular user journey (e.g., registration, login, password reset).
|
|
1021
|
-
|
|
1022
|
-
Flow definitions are created based on the flow meta schema, which includes the flow's purpose, audience, and the steps involved.
|
|
1023
|
-
|
|
1024
|
-
* @summary Create a new flow definition
|
|
1025
|
-
*/
|
|
1026
|
-
const createFlowDefinition = async (createFlowDefinitionBody, options) => {
|
|
1027
|
-
return customFetch(getCreateFlowDefinitionUrl(), {
|
|
1028
|
-
...options,
|
|
1029
|
-
method: "POST",
|
|
1030
|
-
headers: {
|
|
1031
|
-
"Content-Type": "application/json",
|
|
1032
|
-
...options?.headers
|
|
1033
|
-
},
|
|
1034
|
-
body: JSON.stringify(createFlowDefinitionBody)
|
|
1035
|
-
});
|
|
1036
|
-
};
|
|
1037
|
-
const getListFlowDefinitionsUrl = (params) => {
|
|
1038
|
-
const normalizedParams = new URLSearchParams();
|
|
1039
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1040
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1041
|
-
});
|
|
1042
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1043
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions?${stringifiedParams}` : `${getProxyPath()}/flow_definitions`;
|
|
1044
|
-
};
|
|
1045
|
-
/**
|
|
1046
|
-
* Retrieves a list of all flow definitions.
|
|
1047
|
-
This endpoint can be used to view existing flow definitions and their configurations.
|
|
1048
|
-
|
|
1049
|
-
* @summary List flow definitions
|
|
1050
|
-
*/
|
|
1051
|
-
const listFlowDefinitions = async (params, options) => {
|
|
1052
|
-
return customFetch(getListFlowDefinitionsUrl(params), {
|
|
1053
|
-
...options,
|
|
1054
|
-
method: "GET"
|
|
1055
|
-
});
|
|
1056
|
-
};
|
|
1057
|
-
const getGetFlowDefinitionUrl = (id, params) => {
|
|
1058
|
-
const normalizedParams = new URLSearchParams();
|
|
1059
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1060
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1061
|
-
});
|
|
1062
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1063
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions/${id}?${stringifiedParams}` : `${getProxyPath()}/flow_definitions/${id}`;
|
|
1064
|
-
};
|
|
1065
|
-
/**
|
|
1066
|
-
* Get a flow definition by id
|
|
1067
|
-
* @summary Get a flow definition by id
|
|
1068
|
-
*/
|
|
1069
|
-
const getFlowDefinition = async (id, params, options) => {
|
|
1070
|
-
return customFetch(getGetFlowDefinitionUrl(id, params), {
|
|
1071
|
-
...options,
|
|
1072
|
-
method: "GET"
|
|
1073
|
-
});
|
|
1074
|
-
};
|
|
1075
|
-
const getUpdateFlowDefinitionUrl = (id, params) => {
|
|
1076
|
-
const normalizedParams = new URLSearchParams();
|
|
1077
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1078
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1079
|
-
});
|
|
1080
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1081
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions/${id}?${stringifiedParams}` : `${getProxyPath()}/flow_definitions/${id}`;
|
|
1082
|
-
};
|
|
1083
|
-
/**
|
|
1084
|
-
* Update a flow definition by id
|
|
1085
|
-
* @summary Update a flow definition by id
|
|
1086
|
-
*/
|
|
1087
|
-
const updateFlowDefinition = async (id, updateFlowDefinitionBody, params, options) => {
|
|
1088
|
-
return customFetch(getUpdateFlowDefinitionUrl(id, params), {
|
|
1089
|
-
...options,
|
|
1090
|
-
method: "PATCH",
|
|
1091
|
-
headers: {
|
|
1092
|
-
"Content-Type": "application/json",
|
|
1093
|
-
...options?.headers
|
|
1094
|
-
},
|
|
1095
|
-
body: JSON.stringify(updateFlowDefinitionBody)
|
|
1096
|
-
});
|
|
1097
|
-
};
|
|
1098
|
-
const getDeleteFlowDefinitionUrl = (id, params) => {
|
|
1099
|
-
const normalizedParams = new URLSearchParams();
|
|
1100
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1101
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1102
|
-
});
|
|
1103
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1104
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions/${id}?${stringifiedParams}` : `${getProxyPath()}/flow_definitions/${id}`;
|
|
1105
|
-
};
|
|
1106
|
-
/**
|
|
1107
|
-
* Delete a flow definition by id
|
|
1108
|
-
* @summary Delete a flow definition by id
|
|
1109
|
-
*/
|
|
1110
|
-
const deleteFlowDefinition = async (id, params, options) => {
|
|
1111
|
-
return customFetch(getDeleteFlowDefinitionUrl(id, params), {
|
|
1112
|
-
...options,
|
|
1113
|
-
method: "DELETE"
|
|
1114
|
-
});
|
|
1115
|
-
};
|
|
1116
|
-
const getCreateTeamUrl = (params) => {
|
|
1117
|
-
const normalizedParams = new URLSearchParams();
|
|
1118
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1119
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1120
|
-
});
|
|
1121
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1122
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams?${stringifiedParams}` : `${getProxyPath()}/teams`;
|
|
1123
|
-
};
|
|
1124
|
-
/**
|
|
1125
|
-
* @summary Create team
|
|
1126
|
-
*/
|
|
1127
|
-
const createTeam = async (params, createTeamBody, options) => {
|
|
1128
|
-
return customFetch(getCreateTeamUrl(params), {
|
|
1129
|
-
...options,
|
|
1130
|
-
method: "POST",
|
|
1131
|
-
headers: {
|
|
1132
|
-
"Content-Type": "application/json",
|
|
1133
|
-
...options?.headers
|
|
1134
|
-
},
|
|
1135
|
-
body: JSON.stringify(createTeamBody)
|
|
1136
|
-
});
|
|
1137
|
-
};
|
|
1138
|
-
const getGetTeamUrl = (teamId, params) => {
|
|
1139
|
-
const normalizedParams = new URLSearchParams();
|
|
1140
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1141
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1142
|
-
});
|
|
1143
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1144
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/${teamId}?${stringifiedParams}` : `${getProxyPath()}/teams/${teamId}`;
|
|
1145
|
-
};
|
|
1146
|
-
/**
|
|
1147
|
-
* Returns the current state of a team.
|
|
1148
|
-
|
|
1149
|
-
* @summary Get team
|
|
1150
|
-
*/
|
|
1151
|
-
const getTeam = async (teamId, params, options) => {
|
|
1152
|
-
return customFetch(getGetTeamUrl(teamId, params), {
|
|
1153
|
-
...options,
|
|
1154
|
-
method: "GET"
|
|
1155
|
-
});
|
|
1156
|
-
};
|
|
1157
|
-
//#endregion
|
|
1158
|
-
//#region ../api/dist/runtime/api-factory.mjs
|
|
1159
|
-
/**
|
|
1160
|
-
* Factory for the typed Zitadel client every consumer reaches for.
|
|
1161
|
-
*
|
|
1162
|
-
* Wraps the orval-generated endpoints in a {@link Proxy} so each
|
|
1163
|
-
* generated function sees the right base URL and (optionally) the
|
|
1164
|
-
* right bearer token at call time — without the caller having to
|
|
1165
|
-
* thread either through every operation, and without exposing the
|
|
1166
|
-
* module-globals (`setProxyPath` / `setApiAuthToken`) as part of the
|
|
1167
|
-
* public API.
|
|
1168
|
-
*
|
|
1169
|
-
* Per-instance isolation: multiple clients can coexist in one process
|
|
1170
|
-
* (different servers, different tokens). Their Proxy `get` traps set
|
|
1171
|
-
* the globals synchronously before invoking the underlying generated
|
|
1172
|
-
* function, which reads them at the top of `customFetch` before any
|
|
1173
|
-
* `await`. Two clients running interleaved within a single JS turn
|
|
1174
|
-
* are safe; two clients running in parallel across awaits could
|
|
1175
|
-
* clobber each other — not a pattern any current consumer uses.
|
|
1176
|
-
*/
|
|
1177
|
-
/**
|
|
1178
|
-
* Build a typed Zitadel client pre-bound to a base URL and (optionally)
|
|
1179
|
-
* a bearer token. Every method on the returned object mirrors a
|
|
1180
|
-
* generated orval function:
|
|
1181
|
-
*
|
|
1182
|
-
* const client = createZitadelClient({ baseUrl, token });
|
|
1183
|
-
* await client.createProject({ previewOrigins: [] });
|
|
1184
|
-
* await client.createSchema(body, { project_id });
|
|
1185
|
-
*/
|
|
1186
|
-
function createZitadelClient(opts) {
|
|
1187
|
-
const baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
1188
|
-
return new Proxy(zitadelNextGen_exports, { get(target, prop, receiver) {
|
|
1189
|
-
const value = Reflect.get(target, prop, receiver);
|
|
1190
|
-
if (typeof value !== "function") return value;
|
|
1191
|
-
return (...args) => {
|
|
1192
|
-
setProxyPath(baseUrl);
|
|
1193
|
-
setApiAuthToken(opts.token);
|
|
1194
|
-
return value(...args);
|
|
1195
|
-
};
|
|
1196
|
-
} });
|
|
1197
|
-
}
|
|
1198
|
-
/**
|
|
1199
|
-
* Legacy entry point: builds a client with only the base URL set, no
|
|
1200
|
-
* token. Components and SDKs that don't carry an auth token (browser
|
|
1201
|
-
* flows authenticated by platform cookies) still consume this name.
|
|
1202
|
-
* Equivalent to `createZitadelClient({ baseUrl: apiBase })`.
|
|
1203
|
-
*/
|
|
1204
|
-
function createApi(apiBase) {
|
|
1205
|
-
return createZitadelClient({ baseUrl: apiBase });
|
|
1206
|
-
}
|
|
1207
|
-
//#endregion
|
|
1208
|
-
//#region ../api/dist/runtime/config.mjs
|
|
1209
|
-
/**
|
|
1210
|
-
* Slot for the configured project, stored on `globalThis` under a
|
|
1211
|
-
* {@link Symbol.for} key. Every copy of this module evaluated in the
|
|
1212
|
-
* same JS realm resolves the symbol through the global symbol registry
|
|
1213
|
-
* to the same identity, so they all read and write a single slot —
|
|
1214
|
-
* needed because the standalone components bundle inlines its own copy
|
|
1215
|
-
* of this file, and dual-package hazards / monorepo duplicates can load
|
|
1216
|
-
* a second copy alongside the app's. With a module-local `let` each
|
|
1217
|
-
* instance kept its own singleton and `configureZitadel()` calls in one
|
|
1218
|
-
* were invisible to `getZitadelConfig()` calls in another. Sharing is
|
|
1219
|
-
* realm-scoped — separate realms (iframes, Node `vm` contexts, worker
|
|
1220
|
-
* threads) have their own registries and are not unified by this.
|
|
1221
|
-
*/
|
|
1222
|
-
const PROJECT_SLOT = Symbol.for("@zitadel/api/config:currentProject");
|
|
1223
|
-
function readSlot() {
|
|
1224
|
-
return globalThis[PROJECT_SLOT] ?? null;
|
|
1225
|
-
}
|
|
1226
|
-
/**
|
|
1227
|
-
* Per-project API client cache. Ensures `getApi(project)` returns the
|
|
1228
|
-
* same instance for the same project handle — no re-wrapping on every call.
|
|
1229
|
-
* Module-local on purpose: keyed by the shared frozen `ZitadelProject`
|
|
1230
|
-
* object identity, so cross-instance reads still resolve through the
|
|
1231
|
-
* same key and at worst memoize once per module instance.
|
|
1232
|
-
*/
|
|
1233
|
-
const apiCache = /* @__PURE__ */ new WeakMap();
|
|
1234
|
-
/**
|
|
1235
|
-
* Returns a typed API client for the given project, with the base URL
|
|
1236
|
-
* pre-bound. Cached per project handle — safe to call multiple times.
|
|
1237
|
-
*
|
|
1238
|
-
* ```ts
|
|
1239
|
-
* const project = configureZitadel({ ... });
|
|
1240
|
-
* export const api = getApi(project);
|
|
1241
|
-
* ```
|
|
1242
|
-
*/
|
|
1243
|
-
function getApi(project) {
|
|
1244
|
-
let api = apiCache.get(project);
|
|
1245
|
-
if (!api) {
|
|
1246
|
-
api = createApi(project.proxyPath);
|
|
1247
|
-
apiCache.set(project, api);
|
|
1248
|
-
}
|
|
1249
|
-
return api;
|
|
1250
|
-
}
|
|
1251
|
-
/**
|
|
1252
|
-
* Returns the current project handle, or `null` if {@link configureZitadel}
|
|
1253
|
-
* has not been called yet.
|
|
1254
|
-
*/
|
|
1255
|
-
function getZitadelConfig() {
|
|
1256
|
-
return readSlot();
|
|
1257
|
-
}
|
|
1258
|
-
//#endregion
|
|
1259
|
-
//#region src/orchestrator/resolve-api.ts
|
|
1260
|
-
/** Matches the `configureZitadel()` default so attribute and JS config agree. */
|
|
1261
|
-
const DEFAULT_PROXY_PATH = "/__nextgen";
|
|
1262
|
-
/**
|
|
1263
|
-
* Cache of project handles synthesized from attributes, keyed by their value
|
|
1264
|
-
* tuple. `getApi()` caches its client in a `WeakMap` keyed by the project's
|
|
1265
|
-
* identity, so the same attribute values must yield the *same* frozen object
|
|
1266
|
-
* across the several `resolveApi()` calls a single flow makes — otherwise
|
|
1267
|
-
* every call misses the cache and re-wraps a fresh client. A real page uses a
|
|
1268
|
-
* handful of distinct configs at most; the FIFO bound below is just a guard so
|
|
1269
|
-
* a pathological page that churns through unique configs can't leak memory.
|
|
1270
|
-
*/
|
|
1271
|
-
const MAX_SYNTHETIC_PROJECTS = 32;
|
|
1272
|
-
const syntheticProjects = /* @__PURE__ */ new Map();
|
|
1273
|
-
/**
|
|
1274
|
-
* Builds a `ZitadelProject` from declarative attributes, or `undefined` when
|
|
1275
|
-
* no `project-id` was given (nothing to configure). Mirrors
|
|
1276
|
-
* `configureZitadel()`'s proxy-path defaulting so the two config paths are
|
|
1277
|
-
* interchangeable.
|
|
1278
|
-
*/
|
|
1279
|
-
function projectFromAttrs({ projectId, proxyPath, url }) {
|
|
1280
|
-
if (!projectId) return void 0;
|
|
1281
|
-
const resolvedProxyPath = proxyPath || DEFAULT_PROXY_PATH;
|
|
1282
|
-
const resolvedUrl = url || void 0;
|
|
1283
|
-
const key = JSON.stringify([
|
|
1284
|
-
projectId,
|
|
1285
|
-
resolvedProxyPath,
|
|
1286
|
-
resolvedUrl ?? ""
|
|
1287
|
-
]);
|
|
1288
|
-
let project = syntheticProjects.get(key);
|
|
1289
|
-
if (!project) {
|
|
1290
|
-
project = Object.freeze({
|
|
1291
|
-
projectId,
|
|
1292
|
-
proxyPath: resolvedProxyPath,
|
|
1293
|
-
url: resolvedUrl
|
|
1294
|
-
});
|
|
1295
|
-
if (syntheticProjects.size >= MAX_SYNTHETIC_PROJECTS) {
|
|
1296
|
-
const oldest = syntheticProjects.keys().next().value;
|
|
1297
|
-
if (oldest !== void 0) syntheticProjects.delete(oldest);
|
|
1298
|
-
}
|
|
1299
|
-
syntheticProjects.set(key, project);
|
|
1300
|
-
}
|
|
1301
|
-
return project;
|
|
1302
|
-
}
|
|
1303
|
-
/**
|
|
1304
|
-
* Resolves the effective SDK project handle and its typed API client for an
|
|
1305
|
-
* orchestrator element. Precedence, highest first:
|
|
1306
|
-
*
|
|
1307
|
-
* 1. the element's `project` property (an SDK handle set from JS / a framework),
|
|
1308
|
-
* 2. the global handle from `configureZitadel()`,
|
|
1309
|
-
* 3. the `project-id` / `proxy-path` / `url` attributes set declaratively in HTML.
|
|
1310
|
-
*
|
|
1311
|
-
* Both JS paths (property and global) win over the declarative attributes, so a
|
|
1312
|
-
* deliberate `configureZitadel()` is never silently overridden by fallback or
|
|
1313
|
-
* stale HTML attributes; the attributes are the no-JS fallback.
|
|
1314
|
-
*
|
|
1315
|
-
* Both `<zitadel-login>` and `<zitadel-logout>` need the same resolution and
|
|
1316
|
-
* the same "no config" failure, so it lives here rather than being copied
|
|
1317
|
-
* into each element. Callers run this inside their try/catch so a missing
|
|
1318
|
-
* configuration surfaces through the element's normal error path rather than
|
|
1319
|
-
* as an unhandled rejection.
|
|
1320
|
-
*
|
|
1321
|
-
* @param project Optional per-element override handle.
|
|
1322
|
-
* @param attrs Declarative config read from the element's attributes.
|
|
1323
|
-
* @param element Tag name used in the thrown error (e.g. `<zitadel-login>`).
|
|
1324
|
-
*/
|
|
1325
|
-
function resolveApi(project, attrs, element) {
|
|
1326
|
-
const cfg = project ?? getZitadelConfig() ?? projectFromAttrs(attrs);
|
|
1327
|
-
if (!cfg) throw new Error(`${element} requires a configured project: set the \`project-id\` attribute, set the \`project\` property, or call configureZitadel().`);
|
|
1328
|
-
return {
|
|
1329
|
-
project: cfg,
|
|
1330
|
-
api: getApi(cfg)
|
|
1331
|
-
};
|
|
1332
|
-
}
|
|
1333
|
-
//#endregion
|
|
1334
|
-
//#region src/orchestrator/branding-to-tokens.ts
|
|
1335
|
-
/**
|
|
1336
|
-
* Branding -> CSS token bridge.
|
|
1337
|
-
*
|
|
1338
|
-
* Per `docs/design/branding/README.md` the orchestrator owns theming. Templates
|
|
1339
|
-
* never emit `<style>` blocks; they only structure HTML. This module translates
|
|
1340
|
-
* the `Branding` JSON into a CSSStyleSheet of `:host { --zl-* }` declarations
|
|
1341
|
-
* and applies it via `shadowRoot.adoptedStyleSheets`.
|
|
1342
|
-
*
|
|
1343
|
-
* Dark-mode overrides are emitted as `:host([data-theme="dark"]) { ... }`
|
|
1344
|
-
* (matching `docs/design/branding/tokens.md`). Resolution between
|
|
1345
|
-
* `light | dark | auto` happens in `<zitadel-login>`.
|
|
1346
|
-
*
|
|
1347
|
-
* Base layer: the design-tokens package ships the full `--zl-*` set as both a
|
|
1348
|
-
* `.css` file (for host pages) and a `tokensCss` string (for shadow roots and
|
|
1349
|
-
* SSR). We adopt the string into every orchestrator shadow root so atoms
|
|
1350
|
-
* paint correctly even when the host page didn't `@import` tokens.css — the
|
|
1351
|
-
* orchestrator is meant to drop into any page.
|
|
1352
|
-
*/
|
|
1353
|
-
const RADIUS_MAP = {
|
|
1354
|
-
none: "0",
|
|
1355
|
-
sm: "0.25rem",
|
|
1356
|
-
md: "0.5rem",
|
|
1357
|
-
lg: "0.75rem",
|
|
1358
|
-
full: "9999px"
|
|
1359
|
-
};
|
|
1360
|
-
const DENSITY_MAP = {
|
|
1361
|
-
compact: {
|
|
1362
|
-
"--zl-spacing-03": "0.75rem",
|
|
1363
|
-
"--zl-spacing-05": "1.5rem"
|
|
1364
|
-
},
|
|
1365
|
-
regular: {},
|
|
1366
|
-
comfortable: {
|
|
1367
|
-
"--zl-spacing-03": "1.25rem",
|
|
1368
|
-
"--zl-spacing-05": "2.25rem"
|
|
1369
|
-
}
|
|
1370
|
-
};
|
|
1371
|
-
const PALETTE_MAP = {
|
|
1372
|
-
primary: ["--zl-color-surface-default-white"],
|
|
1373
|
-
on_primary: ["--zl-color-text-button-default"],
|
|
1374
|
-
background: ["--zl-color-surface-default-black"],
|
|
1375
|
-
surface: ["--zl-color-surface-default-primary-gray"],
|
|
1376
|
-
muted: ["--zl-color-surface-default-secondary-gray"],
|
|
1377
|
-
border: ["--zl-color-border-default-gray-200", "--zl-color-border-default-gray-100"],
|
|
1378
|
-
text: ["--zl-color-text-primary-white"],
|
|
1379
|
-
text_muted: ["--zl-color-text-secondary-gray"],
|
|
1380
|
-
link: ["--zl-color-text-subtitle-pink"],
|
|
1381
|
-
success: [
|
|
1382
|
-
"--zl-color-text-success",
|
|
1383
|
-
"--zl-color-border-success",
|
|
1384
|
-
"--zl-color-icon-success"
|
|
1385
|
-
],
|
|
1386
|
-
warning: ["--zl-color-text-subtitle-orange"],
|
|
1387
|
-
error: [
|
|
1388
|
-
"--zl-color-text-error",
|
|
1389
|
-
"--zl-color-border-error",
|
|
1390
|
-
"--zl-color-icon-error"
|
|
1391
|
-
]
|
|
1392
|
-
};
|
|
1393
|
-
/**
|
|
1394
|
-
* Build a CSS string of `:host { --zl-* }` declarations from a Branding
|
|
1395
|
-
* payload. Light values land on `:host`; dark overrides land on
|
|
1396
|
-
* `:host([data-theme="dark"])`.
|
|
1397
|
-
*/
|
|
1398
|
-
function buildBrandingStylesheet(branding, options = {}) {
|
|
1399
|
-
const lightDecls = collectDeclarations(branding);
|
|
1400
|
-
const darkPalette = branding?.theme?.dark?.palette;
|
|
1401
|
-
const darkDecls = darkPalette ? mapPalette(darkPalette) : {};
|
|
1402
|
-
const blocks = [];
|
|
1403
|
-
if (Object.keys(lightDecls).length > 0) blocks.push(formatBlock(":host", lightDecls));
|
|
1404
|
-
if (Object.keys(darkDecls).length > 0) blocks.push(formatBlock(":host([data-theme=\"dark\"])", darkDecls));
|
|
1405
|
-
if (options.resolvedTheme === "dark" && darkPalette) {
|
|
1406
|
-
const merged = {
|
|
1407
|
-
...lightDecls,
|
|
1408
|
-
...darkDecls
|
|
1409
|
-
};
|
|
1410
|
-
blocks.length = 0;
|
|
1411
|
-
blocks.push(formatBlock(":host", merged));
|
|
1412
|
-
}
|
|
1413
|
-
return blocks.join("\n");
|
|
1414
|
-
}
|
|
1415
|
-
function collectDeclarations(branding) {
|
|
1416
|
-
if (!branding) return {};
|
|
1417
|
-
const decls = {};
|
|
1418
|
-
Object.assign(decls, mapPalette(branding.palette));
|
|
1419
|
-
Object.assign(decls, mapTypography(branding.typography));
|
|
1420
|
-
Object.assign(decls, mapShape(branding.shape));
|
|
1421
|
-
return decls;
|
|
1422
|
-
}
|
|
1423
|
-
function mapPalette(palette) {
|
|
1424
|
-
if (!palette) return {};
|
|
1425
|
-
const out = {};
|
|
1426
|
-
for (const [key, varNames] of Object.entries(PALETTE_MAP)) {
|
|
1427
|
-
const value = palette[key];
|
|
1428
|
-
if (typeof value === "string" && value.length > 0) for (const varName of varNames) out[varName] = value;
|
|
1429
|
-
}
|
|
1430
|
-
return out;
|
|
1431
|
-
}
|
|
1432
|
-
function mapTypography(typography) {
|
|
1433
|
-
if (!typography) return {};
|
|
1434
|
-
const out = {};
|
|
1435
|
-
if (typography.font_family) {
|
|
1436
|
-
out["--zl-font-family-sans"] = typography.font_family;
|
|
1437
|
-
out["--zl-font-family-heading"] = typography.font_family;
|
|
1438
|
-
}
|
|
1439
|
-
if (typography.font_family_mono) out["--zl-font-family-mono"] = typography.font_family_mono;
|
|
1440
|
-
const scale = clamp(typography.scale ?? 1, .75, 1.25);
|
|
1441
|
-
if (scale !== 1) out["--zl-font-scale"] = `${scale}`;
|
|
1442
|
-
return out;
|
|
1443
|
-
}
|
|
1444
|
-
function mapShape(shape) {
|
|
1445
|
-
if (!shape) return {};
|
|
1446
|
-
const out = {};
|
|
1447
|
-
if (shape.radius && RADIUS_MAP[shape.radius]) {
|
|
1448
|
-
const radius = RADIUS_MAP[shape.radius];
|
|
1449
|
-
out["--zl-radius-s"] = radius;
|
|
1450
|
-
out["--zl-radius-m"] = radius === "0" ? "0" : `calc(${radius} * 1.5)`;
|
|
1451
|
-
out["--zl-radius-l"] = radius === "0" ? "0" : `calc(${radius} * 2)`;
|
|
1452
|
-
}
|
|
1453
|
-
if (shape.density) Object.assign(out, DENSITY_MAP[shape.density]);
|
|
1454
|
-
return out;
|
|
1455
|
-
}
|
|
1456
|
-
function formatBlock(selector, decls) {
|
|
1457
|
-
return `${selector} {\n${Object.entries(decls).map(([prop, value]) => ` ${prop}: ${value};`).join("\n")}\n}`;
|
|
1458
|
-
}
|
|
1459
|
-
function clamp(value, min, max) {
|
|
1460
|
-
if (Number.isNaN(value)) return min;
|
|
1461
|
-
return Math.min(max, Math.max(min, value));
|
|
1462
|
-
}
|
|
1463
|
-
let baseTokenSheet;
|
|
1464
|
-
function getBaseTokenSheet() {
|
|
1465
|
-
if (typeof CSSStyleSheet === "undefined") return void 0;
|
|
1466
|
-
if (!baseTokenSheet) {
|
|
1467
|
-
baseTokenSheet = new CSSStyleSheet();
|
|
1468
|
-
baseTokenSheet.replaceSync(tokensCss.replaceAll(":root,\n[data-theme=\"dark\"]", ":host,\n:host([data-theme=\"dark\"])").replaceAll("[data-theme=\"light\"]", ":host([data-theme=\"light\"])"));
|
|
1469
|
-
}
|
|
1470
|
-
return baseTokenSheet;
|
|
1471
|
-
}
|
|
1472
|
-
/**
|
|
1473
|
-
* Adopt the design-system base token layer onto a `ShadowRoot`. Safe to call
|
|
1474
|
-
* many times — the underlying constructable sheet is shared across every
|
|
1475
|
-
* orchestrator instance and de-duplicated in the adopted list.
|
|
1476
|
-
*/
|
|
1477
|
-
function applyBaseTokens(shadowRoot) {
|
|
1478
|
-
const sheet = getBaseTokenSheet();
|
|
1479
|
-
if (!sheet) return;
|
|
1480
|
-
const existing = Array.isArray(shadowRoot.adoptedStyleSheets) ? shadowRoot.adoptedStyleSheets : [];
|
|
1481
|
-
if (existing.includes(sheet)) return;
|
|
1482
|
-
try {
|
|
1483
|
-
shadowRoot.adoptedStyleSheets = [sheet, ...existing];
|
|
1484
|
-
} catch {}
|
|
1485
|
-
}
|
|
1486
|
-
/**
|
|
1487
|
-
* Apply a branding payload as a `--zl-*` overrides layer on top of the base
|
|
1488
|
-
* token sheet. Subsequent calls replace the previous override sheet so a new
|
|
1489
|
-
* branding payload paints cleanly without leaking older declarations.
|
|
1490
|
-
*
|
|
1491
|
-
* Callers should run `applyBaseTokens(shadowRoot)` first (once per shadow
|
|
1492
|
-
* root) so the base values exist before branding patches them.
|
|
1493
|
-
*/
|
|
1494
|
-
function applyBrandingTokens(shadowRoot, branding, resolvedTheme) {
|
|
1495
|
-
const css = buildBrandingStylesheet(branding, { resolvedTheme });
|
|
1496
|
-
if (typeof CSSStyleSheet === "undefined") return;
|
|
1497
|
-
const sheet = new CSSStyleSheet();
|
|
1498
|
-
sheet.replaceSync(css);
|
|
1499
|
-
const previous = shadowRoot.__zlTokenSheet;
|
|
1500
|
-
shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets.filter((s) => s !== previous), sheet];
|
|
1501
|
-
shadowRoot.__zlTokenSheet = sheet;
|
|
1502
|
-
}
|
|
1503
|
-
function resolveTheme(branding) {
|
|
1504
|
-
const mode = branding?.theme?.mode ?? "dark";
|
|
1505
|
-
if (mode === "light") return "light";
|
|
1506
|
-
if (mode === "dark") return "dark";
|
|
1507
|
-
if (typeof matchMedia === "function") return matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
|
|
1508
|
-
return "dark";
|
|
1509
|
-
}
|
|
1510
|
-
//#endregion
|
|
1511
|
-
//#region ../api/dist/generated/model/index.mjs
|
|
1512
|
-
const CreateFlow201BrandingLayout = {
|
|
1513
|
-
centered: "centered",
|
|
1514
|
-
split: "split"
|
|
1515
|
-
};
|
|
1516
|
-
//#endregion
|
|
1517
|
-
//#region src/orchestrator/branding-validator.ts
|
|
1518
|
-
/**
|
|
1519
|
-
* Lightweight paint-time validator for `Branding` payloads.
|
|
1520
|
-
*
|
|
1521
|
-
* Per `docs/design/branding/schema.md` §Shape invariants:
|
|
1522
|
-
*
|
|
1523
|
-
* 1. Every referenced URL is https.
|
|
1524
|
-
* 2. `layout` is in the documented enum.
|
|
1525
|
-
* 3. `liquid_template` shape checks happen elsewhere (security pipeline runs
|
|
1526
|
-
* server-side; structural validator is deferred until the full atom set
|
|
1527
|
-
* lands).
|
|
1528
|
-
*
|
|
1529
|
-
* Failures are non-fatal: we collect issues, log a dev-build warning, and
|
|
1530
|
-
* return a sanitised payload with the offending fields stripped (set to
|
|
1531
|
-
* `undefined`) so the orchestrator falls back to the bundled defaults.
|
|
1532
|
-
*/
|
|
1533
|
-
const VALID_LAYOUTS = new Set(Object.values(CreateFlow201BrandingLayout));
|
|
1534
|
-
function validateBranding(input) {
|
|
1535
|
-
if (!input) return {
|
|
1536
|
-
branding: void 0,
|
|
1537
|
-
issues: []
|
|
1538
|
-
};
|
|
1539
|
-
const issues = [];
|
|
1540
|
-
const out = { ...input };
|
|
1541
|
-
if (out.layout && !VALID_LAYOUTS.has(out.layout)) {
|
|
1542
|
-
issues.push(`Unknown layout "${out.layout}" — falling back to "centered".`);
|
|
1543
|
-
out.layout = CreateFlow201BrandingLayout.centered;
|
|
1544
|
-
}
|
|
1545
|
-
out.logo_url = sanitiseUrl(out.logo_url, "logo_url", issues);
|
|
1546
|
-
out.font_url = sanitiseUrl(out.font_url, "font_url", issues);
|
|
1547
|
-
out.hero_url = sanitiseUrl(out.hero_url, "hero_url", issues);
|
|
1548
|
-
if (out.assets) out.assets = {
|
|
1549
|
-
logo_dark: sanitiseUrl(out.assets.logo_dark, "assets.logo_dark", issues),
|
|
1550
|
-
favicon: sanitiseUrl(out.assets.favicon, "assets.favicon", issues),
|
|
1551
|
-
background_image: sanitiseUrl(out.assets.background_image, "assets.background_image", issues)
|
|
1552
|
-
};
|
|
1553
|
-
return {
|
|
1554
|
-
branding: out,
|
|
1555
|
-
issues
|
|
1556
|
-
};
|
|
1557
|
-
}
|
|
1558
|
-
function sanitiseUrl(value, field, issues) {
|
|
1559
|
-
if (value == null || value === "") return;
|
|
1560
|
-
try {
|
|
1561
|
-
const parsed = new URL(value);
|
|
1562
|
-
if (parsed.protocol !== "https:") {
|
|
1563
|
-
issues.push(`${field} must use https (got "${parsed.protocol}") — dropped.`);
|
|
1564
|
-
return;
|
|
1565
|
-
}
|
|
1566
|
-
return parsed.toString();
|
|
1567
|
-
} catch {
|
|
1568
|
-
issues.push(`${field} is not a valid URL — dropped.`);
|
|
1569
|
-
return;
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
//#endregion
|
|
1573
|
-
//#region src/orchestrator/font-loader.ts
|
|
1574
|
-
/**
|
|
1575
|
-
* Inject `branding.font_url` as `<link rel="stylesheet">` into the host
|
|
1576
|
-
* document's `<head>`. Per `docs/design/branding/templates.md`:
|
|
1577
|
-
*
|
|
1578
|
-
* "The `font_url` stylesheet is injected by the orchestrator; the template
|
|
1579
|
-
* does not emit the `<link>` tag itself."
|
|
1580
|
-
*
|
|
1581
|
-
* The link must live at document level, not inside the shadow root:
|
|
1582
|
-
* browsers ignore `@font-face` rules declared in shadow-tree stylesheets,
|
|
1583
|
-
* so a shadow-scoped link would never register the font faces and every
|
|
1584
|
-
* branded font silently falls back to the system stack. `font-family`
|
|
1585
|
-
* references inside the shadow tree resolve against document-level faces.
|
|
1586
|
-
*
|
|
1587
|
-
* Idempotent: calling with the same URL is a no-op; calling with a new URL
|
|
1588
|
-
* replaces the previous link. Calling with `null`/`undefined` removes any
|
|
1589
|
-
* previously injected link.
|
|
1590
|
-
*/
|
|
1591
|
-
const LINK_ID = "zl-font-link";
|
|
1592
|
-
function applyFontUrl(shadowRoot, fontUrl) {
|
|
1593
|
-
const ownerDocument = shadowRoot.ownerDocument ?? document;
|
|
1594
|
-
const existing = ownerDocument.head.querySelector(`link#${LINK_ID}`);
|
|
1595
|
-
if (!fontUrl) {
|
|
1596
|
-
existing?.remove();
|
|
1597
|
-
return;
|
|
1598
|
-
}
|
|
1599
|
-
const link = existing ?? ownerDocument.createElement("link");
|
|
1600
|
-
link.id = LINK_ID;
|
|
1601
|
-
link.rel = "stylesheet";
|
|
1602
|
-
if (link.href !== fontUrl) link.href = fontUrl;
|
|
1603
|
-
if (!existing) ownerDocument.head.appendChild(link);
|
|
1604
|
-
}
|
|
1605
|
-
//#endregion
|
|
1606
|
-
//#region src/internal/escape-html.ts
|
|
1607
|
-
/**
|
|
1608
|
-
* Escapes the five HTML-significant characters so a string is safe to
|
|
1609
|
-
* interpolate into either element text or a double-quoted attribute value.
|
|
1610
|
-
*
|
|
1611
|
-
* Used for the orchestrator-owned attribution markup, which is appended after
|
|
1612
|
-
* the Liquid output has already been sanitised — so it can't rely on the
|
|
1613
|
-
* DOMPurify pass and must escape its own (tenant-supplied) values.
|
|
1614
|
-
*/
|
|
1615
|
-
function escapeHtml(value) {
|
|
1616
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1617
|
-
}
|
|
1618
|
-
//#endregion
|
|
1619
|
-
//#region src/orchestrator/mandatory-gates.ts
|
|
1620
|
-
const MANDATORY_GATES_MARKER = "ZL_MANDATORY_GATES";
|
|
1621
|
-
const mandatoryGatesMarkerComment = `<!--${MANDATORY_GATES_MARKER}-->`;
|
|
1622
|
-
function patchMandatoryGates(html, step, locale) {
|
|
1623
|
-
const template = document.createElement("template");
|
|
1624
|
-
template.innerHTML = html;
|
|
1625
|
-
const fragment = template.content;
|
|
1626
|
-
const additions = collectMissingAtoms(fragment, step, locale);
|
|
1627
|
-
const marker = findMarkerComment(fragment);
|
|
1628
|
-
if (marker?.parentNode) {
|
|
1629
|
-
for (const node of additions) marker.parentNode.insertBefore(node, marker);
|
|
1630
|
-
marker.remove();
|
|
1631
|
-
} else for (const node of additions) fragment.appendChild(node);
|
|
1632
|
-
return template.innerHTML;
|
|
1633
|
-
}
|
|
1634
|
-
function collectMissingAtoms(fragment, step, locale) {
|
|
1635
|
-
const additions = [];
|
|
1636
|
-
if (step.fields) for (const field of step.fields) {
|
|
1637
|
-
if (!field.required) continue;
|
|
1638
|
-
if (hasFieldFor(fragment, field.name)) continue;
|
|
1639
|
-
additions.push(buildField(field.name, field.text_key, field.type, locale, field.required));
|
|
1640
|
-
}
|
|
1641
|
-
if (step.actions && !hasPrimaryButton(fragment)) {
|
|
1642
|
-
const primary = step.actions.find((action) => action.primary);
|
|
1643
|
-
if (primary) additions.push(buildSubmit(primary.name, primary.text_key, locale));
|
|
1644
|
-
}
|
|
1645
|
-
return additions;
|
|
1646
|
-
}
|
|
1647
|
-
function hasPrimaryButton(fragment) {
|
|
1648
|
-
return Boolean(fragment.querySelector("zl-button[hierarchy=\"primary\"]"));
|
|
1649
|
-
}
|
|
1650
|
-
function hasFieldFor(fragment, name) {
|
|
1651
|
-
for (const field of fragment.querySelectorAll("zl-field")) if (field.getAttribute("name") === name) return true;
|
|
1652
|
-
return false;
|
|
1653
|
-
}
|
|
1654
|
-
function findMarkerComment(fragment) {
|
|
1655
|
-
const walker = (fragment.ownerDocument ?? document).createTreeWalker(fragment, NodeFilter.SHOW_COMMENT);
|
|
1656
|
-
let node = walker.nextNode();
|
|
1657
|
-
while (node) {
|
|
1658
|
-
if (node.nodeValue?.trim() === "ZL_MANDATORY_GATES") return node;
|
|
1659
|
-
node = walker.nextNode();
|
|
1660
|
-
}
|
|
1661
|
-
return null;
|
|
1662
|
-
}
|
|
1663
|
-
function buildField(name, textKey, type, locale, required) {
|
|
1664
|
-
const el = document.createElement("zl-field");
|
|
1665
|
-
el.setAttribute("name", name);
|
|
1666
|
-
el.setAttribute("label", lookup(locale, textKey ?? name));
|
|
1667
|
-
el.setAttribute("type", type);
|
|
1668
|
-
if (required) el.setAttribute("required", "");
|
|
1669
|
-
return el;
|
|
1670
|
-
}
|
|
1671
|
-
function buildSubmit(name, textKey, locale) {
|
|
1672
|
-
const el = document.createElement("zl-button");
|
|
1673
|
-
el.setAttribute("hierarchy", "primary");
|
|
1674
|
-
el.setAttribute("size", "medium");
|
|
1675
|
-
el.setAttribute("type", "submit");
|
|
1676
|
-
el.setAttribute("block", "");
|
|
1677
|
-
el.setAttribute("action", name);
|
|
1678
|
-
el.setAttribute("label", lookup(locale, textKey ?? "submit.continue"));
|
|
1679
|
-
return el;
|
|
1680
|
-
}
|
|
1681
|
-
function lookup(locale, key) {
|
|
1682
|
-
return locale[key] ?? key;
|
|
1683
|
-
}
|
|
1684
|
-
//#endregion
|
|
1685
|
-
//#region src/orchestrator/liquid.ts
|
|
1686
|
-
/**
|
|
1687
|
-
* LiquidJS engine factory for the `<zitadel-login>` orchestrator.
|
|
1688
|
-
*
|
|
1689
|
-
* Configures the engine per the security pipeline in
|
|
1690
|
-
* `docs/design/flowengine/template-security.md` and registers the
|
|
1691
|
-
* `| t` filter and `{% mandatory_gates %}` tag from
|
|
1692
|
-
* `docs/design/branding/templates.md`.
|
|
1693
|
-
*
|
|
1694
|
-
* Hard rules:
|
|
1695
|
-
*
|
|
1696
|
-
* - `{{ }}` output is HTML-escaped (LiquidJS default since v10).
|
|
1697
|
-
* - The `| raw` filter is overridden to a no-op string passthrough that still
|
|
1698
|
-
* escapes (Layer 1 of the security pipeline).
|
|
1699
|
-
* - Partials are loaded from an in-memory map; no filesystem access.
|
|
1700
|
-
*/
|
|
1701
|
-
const TEMPLATE_NAMES$1 = { default: "default" };
|
|
1702
|
-
function createLiquidEngine(options) {
|
|
1703
|
-
const engine = new Liquid({
|
|
1704
|
-
templates: {
|
|
1705
|
-
[TEMPLATE_NAMES$1.default]: default_default,
|
|
1706
|
-
...options.templates ?? {}
|
|
1707
|
-
},
|
|
1708
|
-
cache: true,
|
|
1709
|
-
jsTruthy: true,
|
|
1710
|
-
relativeReference: false,
|
|
1711
|
-
strictFilters: false,
|
|
1712
|
-
strictVariables: false,
|
|
1713
|
-
outputEscape: "escape"
|
|
1714
|
-
});
|
|
1715
|
-
engine.registerFilter("raw", (value) => stringify(value));
|
|
1716
|
-
engine.registerFilter("t", function tFilter(key, ...args) {
|
|
1717
|
-
const lookupKey = stringify(key);
|
|
1718
|
-
return interpolate(options.locale[lookupKey] ?? lookupKey, args.map(stringify));
|
|
1719
|
-
});
|
|
1720
|
-
/** Resolves `{text_key}.placeholder` — empty when undefined (not the raw key). */
|
|
1721
|
-
engine.registerFilter("fieldPlaceholder", (textKey) => {
|
|
1722
|
-
const lookupKey = `${stringify(textKey)}.placeholder`;
|
|
1723
|
-
return options.locale[lookupKey] ?? "";
|
|
1724
|
-
});
|
|
1725
|
-
/** Resolves `{text_key}.help` — empty when undefined. */
|
|
1726
|
-
engine.registerFilter("fieldHelp", (textKey) => {
|
|
1727
|
-
const lookupKey = `${stringify(textKey)}.help`;
|
|
1728
|
-
return options.locale[lookupKey] ?? "";
|
|
1729
|
-
});
|
|
1730
|
-
/** Maps `error.*` text keys to a field name (Figma inline-error annotations). */
|
|
1731
|
-
const fieldErrorKeys = {
|
|
1732
|
-
"error.email_required": "email",
|
|
1733
|
-
"error.email_invalid": "email",
|
|
1734
|
-
"error.email_exists": "email",
|
|
1735
|
-
"error.password_required": "password",
|
|
1736
|
-
"error.password_incorrect": "password",
|
|
1737
|
-
"error.invalid_credentials": "password"
|
|
1738
|
-
};
|
|
1739
|
-
/** Resolves `{text_key}.title` for form-level `<zl-alert heading>`. */
|
|
1740
|
-
engine.registerFilter("alertHeading", (textKey) => {
|
|
1741
|
-
const lookupKey = `${stringify(textKey)}.title`;
|
|
1742
|
-
return options.locale[lookupKey] ?? "";
|
|
1743
|
-
});
|
|
1744
|
-
/** Resolves `{text_key}.body` for form-level `<zl-alert>` message slot. */
|
|
1745
|
-
engine.registerFilter("alertBody", (textKey) => {
|
|
1746
|
-
const lookupKey = `${stringify(textKey)}.body`;
|
|
1747
|
-
return options.locale[lookupKey] ?? "";
|
|
1748
|
-
});
|
|
1749
|
-
/** Localized inline error for `fieldName`, or empty when none applies. */
|
|
1750
|
-
engine.registerFilter("fieldError", (fieldName, errors) => {
|
|
1751
|
-
const name = stringify(fieldName);
|
|
1752
|
-
if (!Array.isArray(errors)) return "";
|
|
1753
|
-
for (const item of errors) {
|
|
1754
|
-
const key = item.text_key ?? "";
|
|
1755
|
-
if (fieldErrorKeys[key] === name) return options.locale[key] ?? key;
|
|
1756
|
-
}
|
|
1757
|
-
return "";
|
|
1758
|
-
});
|
|
1759
|
-
/** True when the error should render as `<zl-alert>`, not on a field. */
|
|
1760
|
-
engine.registerFilter("formLevelError", (err) => {
|
|
1761
|
-
const key = err?.text_key ?? "";
|
|
1762
|
-
return key === "" || !(key in fieldErrorKeys);
|
|
1763
|
-
});
|
|
1764
|
-
engine.registerTag("mandatory_gates", {
|
|
1765
|
-
parse() {},
|
|
1766
|
-
render() {
|
|
1767
|
-
return mandatoryGatesMarkerComment;
|
|
1768
|
-
}
|
|
1769
|
-
});
|
|
1770
|
-
return engine;
|
|
1771
|
-
}
|
|
1772
|
-
function stringify(value) {
|
|
1773
|
-
if (value == null) return "";
|
|
1774
|
-
if (typeof value === "string") return value;
|
|
1775
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1776
|
-
try {
|
|
1777
|
-
return JSON.stringify(value);
|
|
1778
|
-
} catch {
|
|
1779
|
-
return String(value);
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
function interpolate(template, args) {
|
|
1783
|
-
if (args.length === 0) return template;
|
|
1784
|
-
return template.replace(/\{(\d+)\}/g, (match, index) => {
|
|
1785
|
-
return args[Number(index)] ?? match;
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
//#endregion
|
|
1789
|
-
//#region src/orchestrator/template-names.ts
|
|
1790
|
-
/**
|
|
1791
|
-
* Names of the bundled Liquid templates the `<zitadel-login>` orchestrator
|
|
1792
|
-
* registers.
|
|
1793
|
-
*
|
|
1794
|
-
* This lives in its own module — separate from `liquid.ts` — so the public
|
|
1795
|
-
* barrel can re-export `TEMPLATE_NAMES` without dragging LiquidJS' `Liquid`
|
|
1796
|
-
* type (and its Node ambient-type requirements) into the published
|
|
1797
|
-
* declaration surface. See `liquid.ts` / the orchestrator barrel.
|
|
1798
|
-
*/
|
|
1799
|
-
const TEMPLATE_NAMES = {
|
|
1800
|
-
default: "default",
|
|
1801
|
-
authForm: "auth-form",
|
|
1802
|
-
passkeyUpsell: "passkey-upsell",
|
|
1803
|
-
signedIn: "signed-in"
|
|
1804
|
-
};
|
|
1805
|
-
//#endregion
|
|
1806
|
-
//#region src/orchestrator/locales/en.ts
|
|
1807
|
-
/**
|
|
1808
|
-
* Minimal English locale dictionary.
|
|
1809
|
-
*
|
|
1810
|
-
* The `| t` filter looks up `text_key` strings here. Missing keys fall through
|
|
1811
|
-
* to the raw key (matches the spec in
|
|
1812
|
-
* `docs/design/flowengine/flow-engine-guide.md`).
|
|
1813
|
-
*
|
|
1814
|
-
* Text keys are auto-generated by the server from the flow definition:
|
|
1815
|
-
* - Step titles: `<step>.title`
|
|
1816
|
-
* - Step descriptions: `<step>.description`
|
|
1817
|
-
* - Field labels: `<step>.field.<field>`
|
|
1818
|
-
* - Action labels: `<step>.action.<action>`
|
|
1819
|
-
*
|
|
1820
|
-
* Copy aligned to Figma screens file `xkvBjkOJ8ENuHdTGZHXezK` (May 2026).
|
|
1821
|
-
* MVP only — multi-locale support is deferred.
|
|
1822
|
-
*/
|
|
1823
|
-
const en = {
|
|
1824
|
-
"identifier.title": "Sign in",
|
|
1825
|
-
"identifier.description": "Enter your email to continue",
|
|
1826
|
-
"identifier.field.email": "Work email",
|
|
1827
|
-
"identifier.field.email.placeholder": "you@company.com",
|
|
1828
|
-
"identifier.field.password": "Password",
|
|
1829
|
-
"identifier.action.submit": "Sign in",
|
|
1830
|
-
"identifier.action.continue": "Sign in",
|
|
1831
|
-
"identifier.action.passkey": "Sign in with a passkey",
|
|
1832
|
-
"identifier.action.register.lead": "Don't have an account? ",
|
|
1833
|
-
"identifier.action.register.link": "Sign up",
|
|
1834
|
-
"authenticate.title": "Sign in",
|
|
1835
|
-
"authenticate.description": "Enter your password",
|
|
1836
|
-
"authenticate.field.password": "Password",
|
|
1837
|
-
"authenticate.action.submit": "Sign in",
|
|
1838
|
-
"choose-register.title": "Create your account",
|
|
1839
|
-
"choose-register.description": "Choose how you'd like to sign up",
|
|
1840
|
-
"choose-register.action.register-with-password": "Sign up with password",
|
|
1841
|
-
"choose-register.action.register-with-passkey": "Sign up with passkey",
|
|
1842
|
-
"collect-credentials.title": "Create your account",
|
|
1843
|
-
"collect-credentials.description": "Set up your email and password",
|
|
1844
|
-
"collect-credentials.field.email": "Work email",
|
|
1845
|
-
"collect-credentials.field.email.placeholder": "you@company.com",
|
|
1846
|
-
"collect-credentials.field.password": "Password",
|
|
1847
|
-
"collect-credentials.field.password.help": "At least 8 characters, including a symbol and number.",
|
|
1848
|
-
"collect-credentials.action.submit": "Sign up",
|
|
1849
|
-
"register-password.title": "Create your password",
|
|
1850
|
-
"register-password.description": "Choose a secure password for your account",
|
|
1851
|
-
"register-password.field.password": "Password",
|
|
1852
|
-
"register-password.field.password.help": "At least 8 characters, including a symbol and number.",
|
|
1853
|
-
"register-password.action.submit": "Sign up",
|
|
1854
|
-
"passkey-upsell.title": "Sign in faster next time",
|
|
1855
|
-
"passkey-upsell.description": "No password needed ever again.",
|
|
1856
|
-
"passkey-upsell.description.line1": "No password needed ever again.",
|
|
1857
|
-
"passkey-upsell.description.line2": "Sign in with Face ID, Touch ID, or PIN.",
|
|
1858
|
-
"passkey-upsell.action.passkey_register": "Set up passkey",
|
|
1859
|
-
"passkey-upsell.action.skip": "Skip for now",
|
|
1860
|
-
"passkey-upsell.action.setup": "Set up passkey",
|
|
1861
|
-
"collect-passkey-email.title": "Create your account",
|
|
1862
|
-
"collect-passkey-email.description": "Enter your email to set up a passkey",
|
|
1863
|
-
"collect-passkey-email.field.email": "Work email",
|
|
1864
|
-
"collect-passkey-email.field.email.placeholder": "you@company.com",
|
|
1865
|
-
"collect-passkey-email.action.submit": "Continue",
|
|
1866
|
-
"passkey-enroll.title": "Set up your passkey",
|
|
1867
|
-
"passkey-enroll.description": "Use Face ID, Touch ID, or your device PIN to create a passkey.",
|
|
1868
|
-
"passkey-enroll.action.passkey_register": "Set up passkey",
|
|
1869
|
-
"done.title": "You're signed in as",
|
|
1870
|
-
"done.description": "",
|
|
1871
|
-
"password.title": "Sign in",
|
|
1872
|
-
"password.description": "Enter your password",
|
|
1873
|
-
"password.field.password": "Password",
|
|
1874
|
-
"password.action.signin": "Sign in",
|
|
1875
|
-
"password.action.passkey": "Sign in with a passkey",
|
|
1876
|
-
"password.action.register.lead": "Don't have an account? ",
|
|
1877
|
-
"password.action.register.link": "Sign up",
|
|
1878
|
-
"register.title": "Create your account",
|
|
1879
|
-
"register.description": "",
|
|
1880
|
-
"register.field.email": "Work email",
|
|
1881
|
-
"register.field.email.placeholder": "you@company.com",
|
|
1882
|
-
"register.field.password": "Password",
|
|
1883
|
-
"register.field.password.help": "At least 8 characters, including a symbol and number.",
|
|
1884
|
-
"register.field.givenName": "Given name",
|
|
1885
|
-
"register.field.familyName": "Family name",
|
|
1886
|
-
"register.field.dateOfBirth": "Date of birth",
|
|
1887
|
-
"register.field.dateOfBirth.placeholder": "YYYY-MM-DD",
|
|
1888
|
-
"register.field.dateOfBirth.help": "Use YYYY-MM-DD.",
|
|
1889
|
-
"register.action.password": "Continue with password",
|
|
1890
|
-
"register.action.passkey": "Continue with a passkey",
|
|
1891
|
-
"register.action.submit": "Sign up",
|
|
1892
|
-
"register.action.sign_in.lead": "Already have an account? ",
|
|
1893
|
-
"register.action.sign_in.link": "Sign in",
|
|
1894
|
-
"complete.title": "You're signed in as",
|
|
1895
|
-
"signed-in.continue": "Continue",
|
|
1896
|
-
"signed-in.logout": "Logout",
|
|
1897
|
-
"passkey-login.title": "Sign in with your passkey",
|
|
1898
|
-
"recover.title": "Check your email",
|
|
1899
|
-
"recover.description": "We sent a password reset link to your email address.",
|
|
1900
|
-
"recover.action.back": "Back to sign in",
|
|
1901
|
-
"submit.continue": "Continue",
|
|
1902
|
-
"submit.signin": "Sign in",
|
|
1903
|
-
"action.forgot_password": "Forgot password?",
|
|
1904
|
-
"action.cancel": "Cancel",
|
|
1905
|
-
"sso.redirect.title": "Redirecting to your provider…",
|
|
1906
|
-
"error.passkey_cancelled": "Passkey setup was cancelled",
|
|
1907
|
-
"error.passkey_not_registered": "This passkey is not registered. Please sign in with your email and password.",
|
|
1908
|
-
"error.passkey_setup_failed": "Passkey registration did not complete. Please try again.",
|
|
1909
|
-
"error.passkey_unsupported": "This device does not support passkeys",
|
|
1910
|
-
"error.passkey_failed": "Something went wrong. Please try again.",
|
|
1911
|
-
"error.email_required": "Please enter an email address",
|
|
1912
|
-
"error.email_invalid": "Please enter a valid email",
|
|
1913
|
-
"error.password_required": "Please enter a password",
|
|
1914
|
-
"error.password_incorrect": "Wrong email or password.",
|
|
1915
|
-
"error.email_exists": "An account with this email already exists.",
|
|
1916
|
-
/** Figma sign-in error `6602:180268` — inline on password field. */
|
|
1917
|
-
"error.invalid_credentials": "Wrong email or password.",
|
|
1918
|
-
"error.required": "This field is required.",
|
|
1919
|
-
"error.sign_in_server.title": "We couldn't complete your sign in.",
|
|
1920
|
-
"error.sign_in_server.body": "Please try again in a few minutes"
|
|
1921
|
-
};
|
|
1922
|
-
//#endregion
|
|
1923
|
-
//#region src/orchestrator/locales/de.ts
|
|
1924
|
-
const de = {
|
|
1925
|
-
"identifier.title": "Anmelden",
|
|
1926
|
-
"identifier.description": "Gib deine E-Mail-Adresse ein, um fortzufahren",
|
|
1927
|
-
"identifier.field.email": "E-Mail",
|
|
1928
|
-
"identifier.field.email.placeholder": "du@unternehmen.com",
|
|
1929
|
-
"identifier.field.password": "Passwort",
|
|
1930
|
-
"identifier.action.submit": "Anmelden",
|
|
1931
|
-
"identifier.action.continue": "Anmelden",
|
|
1932
|
-
"identifier.action.passkey": "Mit Passkey anmelden",
|
|
1933
|
-
"identifier.action.register.lead": "Noch kein Konto? ",
|
|
1934
|
-
"identifier.action.register.link": "Registrieren",
|
|
1935
|
-
"authenticate.title": "Anmelden",
|
|
1936
|
-
"authenticate.description": "Gib dein Passwort ein",
|
|
1937
|
-
"authenticate.field.password": "Passwort",
|
|
1938
|
-
"authenticate.action.submit": "Anmelden",
|
|
1939
|
-
"choose-register.title": "Konto erstellen",
|
|
1940
|
-
"choose-register.description": "Wähle, wie du dich registrieren möchtest",
|
|
1941
|
-
"choose-register.action.register-with-password": "Mit Passwort registrieren",
|
|
1942
|
-
"choose-register.action.register-with-passkey": "Mit Passkey registrieren",
|
|
1943
|
-
"collect-credentials.title": "Konto erstellen",
|
|
1944
|
-
"collect-credentials.description": "Richte E-Mail und Passwort ein",
|
|
1945
|
-
"collect-credentials.field.email": "E-Mail",
|
|
1946
|
-
"collect-credentials.field.email.placeholder": "du@unternehmen.com",
|
|
1947
|
-
"collect-credentials.field.password": "Passwort",
|
|
1948
|
-
"collect-credentials.field.password.help": "Mindestens 8 Zeichen, einschließlich eines Sonderzeichens und einer Zahl.",
|
|
1949
|
-
"collect-credentials.action.submit": "Registrieren",
|
|
1950
|
-
"register-password.title": "Passwort erstellen",
|
|
1951
|
-
"register-password.description": "Wähle ein sicheres Passwort für dein Konto",
|
|
1952
|
-
"register-password.field.password": "Passwort",
|
|
1953
|
-
"register-password.field.password.help": "Mindestens 8 Zeichen, einschließlich eines Sonderzeichens und einer Zahl.",
|
|
1954
|
-
"register-password.action.submit": "Registrieren",
|
|
1955
|
-
"passkey-upsell.title": "Nächstes Mal schneller anmelden",
|
|
1956
|
-
"passkey-upsell.description": "Kein Passwort mehr nötig.",
|
|
1957
|
-
"passkey-upsell.description.line1": "Kein Passwort mehr nötig.",
|
|
1958
|
-
"passkey-upsell.description.line2": "Melde dich mit Face ID, Touch ID oder PIN an.",
|
|
1959
|
-
"passkey-upsell.action.passkey_register": "Passkey einrichten",
|
|
1960
|
-
"passkey-upsell.action.skip": "Vorerst überspringen",
|
|
1961
|
-
"passkey-upsell.action.setup": "Passkey einrichten",
|
|
1962
|
-
"collect-passkey-email.title": "Konto erstellen",
|
|
1963
|
-
"collect-passkey-email.description": "Gib deine E-Mail-Adresse ein, um einen Passkey einzurichten",
|
|
1964
|
-
"collect-passkey-email.field.email": "E-Mail",
|
|
1965
|
-
"collect-passkey-email.field.email.placeholder": "du@unternehmen.com",
|
|
1966
|
-
"collect-passkey-email.action.submit": "Weiter",
|
|
1967
|
-
"passkey-enroll.title": "Passkey einrichten",
|
|
1968
|
-
"passkey-enroll.description": "Verwende Face ID, Touch ID oder die Geräte-PIN, um einen Passkey zu erstellen.",
|
|
1969
|
-
"passkey-enroll.action.passkey_register": "Passkey einrichten",
|
|
1970
|
-
"done.title": "Du bist angemeldet als",
|
|
1971
|
-
"done.description": "",
|
|
1972
|
-
"password.title": "Anmelden",
|
|
1973
|
-
"password.description": "Gib dein Passwort ein",
|
|
1974
|
-
"password.field.password": "Passwort",
|
|
1975
|
-
"password.action.signin": "Anmelden",
|
|
1976
|
-
"password.action.passkey": "Mit Passkey anmelden",
|
|
1977
|
-
"password.action.register.lead": "Noch kein Konto? ",
|
|
1978
|
-
"password.action.register.link": "Registrieren",
|
|
1979
|
-
"register.title": "Konto erstellen",
|
|
1980
|
-
"register.description": "",
|
|
1981
|
-
"register.field.email": "E-Mail",
|
|
1982
|
-
"register.field.email.placeholder": "du@unternehmen.com",
|
|
1983
|
-
"register.field.password": "Passwort",
|
|
1984
|
-
"register.field.password.help": "Mindestens 8 Zeichen, einschließlich eines Sonderzeichens und einer Zahl.",
|
|
1985
|
-
"register.field.givenName": "Vorname",
|
|
1986
|
-
"register.field.familyName": "Nachname",
|
|
1987
|
-
"register.field.dateOfBirth": "Geburtsdatum",
|
|
1988
|
-
"register.field.dateOfBirth.placeholder": "JJJJ-MM-TT",
|
|
1989
|
-
"register.field.dateOfBirth.help": "Verwende JJJJ-MM-TT.",
|
|
1990
|
-
"register.action.password": "Mit Passwort fortfahren",
|
|
1991
|
-
"register.action.passkey": "Weiter mit Passkey",
|
|
1992
|
-
"register.action.submit": "Registrieren",
|
|
1993
|
-
"register.action.sign_in.lead": "Bereits ein Konto? ",
|
|
1994
|
-
"register.action.sign_in.link": "Anmelden",
|
|
1995
|
-
"complete.title": "Du bist angemeldet als",
|
|
1996
|
-
"signed-in.continue": "Weiter",
|
|
1997
|
-
"signed-in.logout": "Abmelden",
|
|
1998
|
-
"passkey-login.title": "Mit Passkey anmelden",
|
|
1999
|
-
"recover.title": "E-Mail prüfen",
|
|
2000
|
-
"recover.description": "Wir haben einen Link zum Zurücksetzen des Passworts an deine E-Mail-Adresse gesendet.",
|
|
2001
|
-
"recover.action.back": "Zurück zur Anmeldung",
|
|
2002
|
-
"submit.continue": "Weiter",
|
|
2003
|
-
"submit.signin": "Anmelden",
|
|
2004
|
-
"action.forgot_password": "Passwort vergessen?",
|
|
2005
|
-
"action.cancel": "Abbrechen",
|
|
2006
|
-
"sso.redirect.title": "Weiterleitung zum Anbieter…",
|
|
2007
|
-
"error.passkey_cancelled": "Passkey-Einrichtung wurde abgebrochen",
|
|
2008
|
-
"error.passkey_not_registered": "Dieser Passkey ist nicht registriert. Bitte melde dich mit E-Mail und Passwort an.",
|
|
2009
|
-
"error.passkey_setup_failed": "Passkey-Registrierung wurde nicht abgeschlossen. Bitte versuche es erneut.",
|
|
2010
|
-
"error.passkey_unsupported": "Dieses Gerät unterstützt keine Passkeys",
|
|
2011
|
-
"error.passkey_failed": "Etwas ist schiefgelaufen. Bitte versuche es erneut.",
|
|
2012
|
-
"error.email_required": "Bitte gib eine E-Mail-Adresse ein",
|
|
2013
|
-
"error.email_invalid": "Bitte gib eine gültige E-Mail-Adresse ein",
|
|
2014
|
-
"error.password_required": "Bitte gib ein Passwort ein",
|
|
2015
|
-
"error.password_incorrect": "Falsche E-Mail oder falsches Passwort.",
|
|
2016
|
-
"error.email_exists": "Ein Konto mit dieser E-Mail-Adresse existiert bereits.",
|
|
2017
|
-
"error.invalid_credentials": "Falsche E-Mail oder falsches Passwort.",
|
|
2018
|
-
"error.required": "Dieses Feld ist erforderlich.",
|
|
2019
|
-
"error.sign_in_server.title": "Anmeldung konnte nicht abgeschlossen werden.",
|
|
2020
|
-
"error.sign_in_server.body": "Bitte versuche es in einigen Minuten erneut"
|
|
2021
|
-
};
|
|
2022
|
-
//#endregion
|
|
2023
|
-
//#region src/orchestrator/locales/it.ts
|
|
2024
|
-
const it = {
|
|
2025
|
-
"identifier.title": "Accedi",
|
|
2026
|
-
"identifier.description": "Inserisci la tua e-mail per continuare",
|
|
2027
|
-
"identifier.field.email": "E-mail aziendale",
|
|
2028
|
-
"identifier.field.email.placeholder": "tu@azienda.com",
|
|
2029
|
-
"identifier.field.password": "Password",
|
|
2030
|
-
"identifier.action.submit": "Accedi",
|
|
2031
|
-
"identifier.action.continue": "Accedi",
|
|
2032
|
-
"identifier.action.passkey": "Accedi con passkey",
|
|
2033
|
-
"identifier.action.register.lead": "Non hai un account? ",
|
|
2034
|
-
"identifier.action.register.link": "Registrati",
|
|
2035
|
-
"authenticate.title": "Accedi",
|
|
2036
|
-
"authenticate.description": "Inserisci la tua password",
|
|
2037
|
-
"authenticate.field.password": "Password",
|
|
2038
|
-
"authenticate.action.submit": "Accedi",
|
|
2039
|
-
"choose-register.title": "Crea il tuo account",
|
|
2040
|
-
"choose-register.description": "Scegli come registrarti",
|
|
2041
|
-
"choose-register.action.register-with-password": "Registrati con password",
|
|
2042
|
-
"choose-register.action.register-with-passkey": "Registrati con passkey",
|
|
2043
|
-
"collect-credentials.title": "Crea il tuo account",
|
|
2044
|
-
"collect-credentials.description": "Configura e-mail e password",
|
|
2045
|
-
"collect-credentials.field.email": "E-mail aziendale",
|
|
2046
|
-
"collect-credentials.field.email.placeholder": "tu@azienda.com",
|
|
2047
|
-
"collect-credentials.field.password": "Password",
|
|
2048
|
-
"collect-credentials.field.password.help": "Almeno 8 caratteri, incluso un simbolo e un numero.",
|
|
2049
|
-
"collect-credentials.action.submit": "Registrati",
|
|
2050
|
-
"register-password.title": "Crea la tua password",
|
|
2051
|
-
"register-password.description": "Scegli una password sicura per il tuo account",
|
|
2052
|
-
"register-password.field.password": "Password",
|
|
2053
|
-
"register-password.field.password.help": "Almeno 8 caratteri, incluso un simbolo e un numero.",
|
|
2054
|
-
"register-password.action.submit": "Registrati",
|
|
2055
|
-
"passkey-upsell.title": "Accedi più velocemente la prossima volta",
|
|
2056
|
-
"passkey-upsell.description": "Nessuna password necessaria.",
|
|
2057
|
-
"passkey-upsell.description.line1": "Nessuna password necessaria.",
|
|
2058
|
-
"passkey-upsell.description.line2": "Accedi con Face ID, Touch ID o PIN.",
|
|
2059
|
-
"passkey-upsell.action.passkey_register": "Configura passkey",
|
|
2060
|
-
"passkey-upsell.action.skip": "Salta per ora",
|
|
2061
|
-
"passkey-upsell.action.setup": "Configura passkey",
|
|
2062
|
-
"collect-passkey-email.title": "Crea il tuo account",
|
|
2063
|
-
"collect-passkey-email.description": "Inserisci la tua e-mail per configurare una passkey",
|
|
2064
|
-
"collect-passkey-email.field.email": "E-mail aziendale",
|
|
2065
|
-
"collect-passkey-email.field.email.placeholder": "tu@azienda.com",
|
|
2066
|
-
"collect-passkey-email.action.submit": "Continua",
|
|
2067
|
-
"passkey-enroll.title": "Configura la tua passkey",
|
|
2068
|
-
"passkey-enroll.description": "Usa Face ID, Touch ID o il PIN del dispositivo per creare una passkey.",
|
|
2069
|
-
"passkey-enroll.action.passkey_register": "Configura passkey",
|
|
2070
|
-
"done.title": "Sei connesso come",
|
|
2071
|
-
"done.description": "",
|
|
2072
|
-
"password.title": "Accedi",
|
|
2073
|
-
"password.description": "Inserisci la tua password",
|
|
2074
|
-
"password.field.password": "Password",
|
|
2075
|
-
"password.action.signin": "Accedi",
|
|
2076
|
-
"password.action.passkey": "Accedi con passkey",
|
|
2077
|
-
"password.action.register.lead": "Non hai un account? ",
|
|
2078
|
-
"password.action.register.link": "Registrati",
|
|
2079
|
-
"register.title": "Crea il tuo account",
|
|
2080
|
-
"register.description": "",
|
|
2081
|
-
"register.field.email": "E-mail aziendale",
|
|
2082
|
-
"register.field.email.placeholder": "tu@azienda.com",
|
|
2083
|
-
"register.field.password": "Password",
|
|
2084
|
-
"register.field.password.help": "Almeno 8 caratteri, incluso un simbolo e un numero.",
|
|
2085
|
-
"register.field.givenName": "Nome",
|
|
2086
|
-
"register.field.familyName": "Cognome",
|
|
2087
|
-
"register.field.dateOfBirth": "Data di nascita",
|
|
2088
|
-
"register.field.dateOfBirth.placeholder": "AAAA-MM-GG",
|
|
2089
|
-
"register.field.dateOfBirth.help": "Usa AAAA-MM-GG.",
|
|
2090
|
-
"register.action.password": "Continua con password",
|
|
2091
|
-
"register.action.passkey": "Continua con passkey",
|
|
2092
|
-
"register.action.submit": "Registrati",
|
|
2093
|
-
"register.action.sign_in.lead": "Hai già un account? ",
|
|
2094
|
-
"register.action.sign_in.link": "Accedi",
|
|
2095
|
-
"complete.title": "Sei connesso come",
|
|
2096
|
-
"signed-in.continue": "Continua",
|
|
2097
|
-
"signed-in.logout": "Esci",
|
|
2098
|
-
"passkey-login.title": "Accedi con la tua passkey",
|
|
2099
|
-
"recover.title": "Controlla la tua e-mail",
|
|
2100
|
-
"recover.description": "Abbiamo inviato un link per reimpostare la password al tuo indirizzo e-mail.",
|
|
2101
|
-
"recover.action.back": "Torna all'accesso",
|
|
2102
|
-
"submit.continue": "Continua",
|
|
2103
|
-
"submit.signin": "Accedi",
|
|
2104
|
-
"action.forgot_password": "Password dimenticata?",
|
|
2105
|
-
"action.cancel": "Annulla",
|
|
2106
|
-
"sso.redirect.title": "Reindirizzamento al provider…",
|
|
2107
|
-
"error.passkey_cancelled": "La configurazione della passkey è stata annullata",
|
|
2108
|
-
"error.passkey_not_registered": "Questa passkey non è registrata. Accedi con e-mail e password.",
|
|
2109
|
-
"error.passkey_setup_failed": "La registrazione della passkey non è stata completata. Riprova.",
|
|
2110
|
-
"error.passkey_unsupported": "Questo dispositivo non supporta le passkey",
|
|
2111
|
-
"error.passkey_failed": "Qualcosa è andato storto. Riprova.",
|
|
2112
|
-
"error.email_required": "Inserisci un indirizzo e-mail",
|
|
2113
|
-
"error.email_invalid": "Inserisci un indirizzo e-mail valido",
|
|
2114
|
-
"error.password_required": "Inserisci una password",
|
|
2115
|
-
"error.password_incorrect": "E-mail o password errata.",
|
|
2116
|
-
"error.email_exists": "Esiste già un account con questo indirizzo e-mail.",
|
|
2117
|
-
"error.invalid_credentials": "E-mail o password errata.",
|
|
2118
|
-
"error.required": "Questo campo è obbligatorio.",
|
|
2119
|
-
"error.sign_in_server.title": "Non è stato possibile completare l'accesso.",
|
|
2120
|
-
"error.sign_in_server.body": "Riprova tra qualche minuto"
|
|
2121
|
-
};
|
|
2122
|
-
//#endregion
|
|
2123
|
-
//#region src/orchestrator/locales/index.ts
|
|
2124
|
-
const builtinLocales = {
|
|
2125
|
-
en,
|
|
2126
|
-
de,
|
|
2127
|
-
it
|
|
2128
|
-
};
|
|
2129
|
-
//#endregion
|
|
2130
|
-
//#region ../shared-component-styles/dist/attribution-markup.mjs
|
|
2131
|
-
/**
|
|
2132
|
-
* Figma sign-in attribution chip (`6593:141767`): "Secured with" + 65×16 logotype.
|
|
2133
|
-
* Shared by Lit orchestrator HTML, dev playground, and paired React playgrounds.
|
|
2134
|
-
*/
|
|
2135
|
-
const ZITADEL_ATTRIBUTION_LOGOTYPE_SVG = "<svg class=\"zr-pill__logotype-svg\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 67.3501 15.1111\" width=\"65\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M2.04839 4.265C2.06224 4.1284 2.1748 3.98591 2.32756 3.91161L8.98593 0.0494262C9.227 -0.0678242 9.45274 0.0334116 9.43089 0.248972V4.52681C9.41705 4.66341 9.30448 4.80589 9.15172 4.88019L2.49335 8.66426C2.25228 8.78151 2.02654 8.68027 2.04839 8.46471V4.265Z\"/><path d=\"M14.0086 5.25359C14.12 5.33388 14.1871 5.50261 14.1751 5.67205V13.2643C14.1561 13.5317 13.9555 13.6766 13.7798 13.5499L10.1005 11.4468C9.98908 11.3666 9.92196 11.1978 9.934 11.0284V3.32695C9.95299 3.05956 10.1535 2.91467 10.3293 3.04138L14.0086 5.25359Z\"/><path d=\"M7.37428 15.0817C7.24906 15.138 7.06938 15.1118 6.92865 15.0167L0.183465 11.1405C-0.038611 10.9903 -0.0638098 10.7442 0.133796 10.6554L3.76278 8.53833C3.888 8.48203 4.06767 8.50827 4.2084 8.60341L10.9046 12.5341C11.1267 12.6843 11.1557 12.9125 10.9543 13.0193L7.37428 15.0817Z\"/><path d=\"M65.6687 13.1043V2.00077H67.3501V13.1043H65.6687Z\"/><path d=\"M60.4255 13.2947C59.6535 13.2947 58.9715 13.1202 58.3793 12.7712C57.7871 12.4222 57.3218 11.9358 56.9834 11.3119C56.6556 10.688 56.4917 9.97418 56.4917 9.1705C56.4917 8.33509 56.6556 7.61073 56.9834 6.99739C57.3112 6.38405 57.7712 5.90819 58.3634 5.56979C58.9662 5.22083 59.6588 5.04634 60.4414 5.04634C61.2027 5.04634 61.869 5.21554 62.44 5.55393C63.011 5.88175 63.4552 6.33118 63.7724 6.90222C64.1002 7.47326 64.2642 8.12359 64.2642 8.85325C64.2642 9.0013 64.2589 9.1282 64.2483 9.23395C64.2483 9.33969 64.2377 9.46659 64.2166 9.61464H58.1731C58.2048 10.0588 58.3158 10.4553 58.5062 10.8043C58.6965 11.1427 58.9556 11.4124 59.2834 11.6133C59.6112 11.8036 59.9866 11.8988 60.4096 11.8988C60.8644 11.8988 61.2503 11.7983 61.5676 11.5974C61.8954 11.3859 62.1386 11.0898 62.2972 10.7091H64.0421C63.9046 11.185 63.672 11.6186 63.3441 12.0098C63.0269 12.4011 62.6251 12.7131 62.1386 12.9457C61.6522 13.1784 61.0811 13.2947 60.4255 13.2947ZM58.1889 8.42497H62.551C62.5299 7.81163 62.3131 7.3305 61.9007 6.98153C61.4883 6.62199 60.986 6.44222 60.3938 6.44222C59.865 6.44222 59.3839 6.60613 58.9503 6.93394C58.5168 7.26176 58.263 7.75876 58.1889 8.42497Z\"/><path d=\"M50.5389 13.2947C49.7563 13.2947 49.0796 13.1096 48.5085 12.7395C47.9481 12.3588 47.5145 11.8565 47.2078 11.2326C46.9117 10.6087 46.7637 9.91073 46.7637 9.13877C46.7637 8.37739 46.9117 7.69004 47.2078 7.0767C47.5145 6.45279 47.9481 5.96106 48.5085 5.60152C49.0796 5.2314 49.7563 5.04634 50.5389 5.04634C51.1205 5.04634 51.6492 5.16795 52.1251 5.41117C52.601 5.64382 52.9869 5.97692 53.283 6.41049V2.00077H54.9803V13.1043H53.3465L53.283 11.8829C53.0292 12.2954 52.6644 12.6337 52.1886 12.8981C51.7127 13.1625 51.1628 13.2947 50.5389 13.2947ZM50.872 11.8195C51.369 11.8195 51.7973 11.7032 52.1568 11.4705C52.5164 11.2379 52.7913 10.9206 52.9817 10.5188C53.172 10.1169 53.2672 9.66751 53.2672 9.1705C53.2672 8.59946 53.1561 8.1183 52.9341 7.72704C52.7226 7.33577 52.4318 7.0344 52.0617 6.82291C51.7021 6.61141 51.3003 6.50567 50.8561 6.50567C50.4014 6.50567 49.9943 6.6167 49.6347 6.83877C49.2752 7.06084 48.9897 7.36749 48.7782 7.75876C48.5773 8.15003 48.4768 8.61532 48.4768 9.15463C48.4768 9.68337 48.5825 10.1487 48.794 10.5505C49.0055 10.9524 49.2911 11.2643 49.6506 11.4864C50.0101 11.7085 50.4173 11.8195 50.872 11.8195Z\"/><path d=\"M40.7882 13.2947C40.2806 13.2947 39.8153 13.2048 39.3923 13.025C38.9693 12.8347 38.6309 12.5597 38.3771 12.2002C38.1233 11.8406 37.9965 11.4071 37.9965 10.8995C37.9965 10.2967 38.1445 9.81556 38.4406 9.45602C38.7473 9.0859 39.1544 8.81624 39.662 8.64704C40.1801 8.46727 40.7459 8.37739 41.3592 8.37739H43.3579C43.3579 7.92267 43.2839 7.55786 43.1358 7.28291C42.9878 6.99739 42.7868 6.79118 42.533 6.66429C42.2792 6.52681 41.9832 6.45808 41.6448 6.45808C41.2006 6.45808 40.8094 6.56383 40.471 6.77532C40.1431 6.97624 39.9369 7.29876 39.8523 7.7429H38.1551C38.2079 7.17186 38.3983 6.68544 38.7261 6.28359C39.0645 5.88175 39.4928 5.57508 40.011 5.36358C40.5291 5.15209 41.079 5.04634 41.6606 5.04634C42.422 5.04634 43.0512 5.1891 43.5482 5.47462C44.0558 5.74957 44.4312 6.13555 44.6744 6.63256C44.9282 7.12958 45.0551 7.69531 45.0551 8.3298V10.7409C45.0657 11.0475 45.1292 11.2749 45.2455 11.4229C45.3724 11.5604 45.5945 11.6397 45.9117 11.6609V13.1043C45.4887 13.1043 45.1186 13.0567 44.8013 12.9616C44.4947 12.8664 44.2409 12.7236 44.04 12.5333C43.839 12.3324 43.6804 12.0786 43.5641 11.7719C43.3103 12.1949 42.9402 12.5544 42.4537 12.8505C41.9673 13.1466 41.4121 13.2947 40.7882 13.2947ZM41.1848 11.9147C41.6183 11.9147 41.999 11.8089 42.3268 11.5974C42.6547 11.3753 42.9084 11.0898 43.0882 10.7409C43.268 10.3813 43.3579 9.99533 43.3579 9.58291V9.55119H41.4861C41.1795 9.55119 40.8887 9.5882 40.6137 9.66223C40.3493 9.73625 40.1326 9.86315 39.9634 10.0429C39.8047 10.2121 39.7254 10.45 39.7254 10.7567C39.7254 11.1586 39.8682 11.4547 40.1537 11.645C40.4392 11.8248 40.7829 11.9147 41.1848 11.9147Z\"/><path d=\"M35.5197 13.1043C34.7372 13.1043 34.1344 12.914 33.7114 12.5333C33.2884 12.1526 33.0769 11.5287 33.0769 10.6615V6.64842H31.8873V5.23669H33.0769V3.3332H34.7583V5.23669H36.757V6.64842H34.7583V10.6615C34.7583 11.0634 34.8482 11.333 35.028 11.4705C35.2078 11.608 35.488 11.6767 35.8687 11.6767H36.757V13.1043H35.5197Z\"/><path d=\"M29.6602 3.85668C29.3536 3.85668 29.0892 3.7509 28.8671 3.53941C28.6556 3.31734 28.5499 3.04768 28.5499 2.73043C28.5499 2.42376 28.6556 2.16468 28.8671 1.95319C29.0892 1.74169 29.3536 1.63594 29.6602 1.63594C29.9775 1.63594 30.2419 1.74169 30.4533 1.95319C30.6754 2.16468 30.7865 2.42376 30.7865 2.73043C30.7865 3.04768 30.6754 3.31734 30.4533 3.53941C30.2419 3.7509 29.9775 3.85668 29.6602 3.85668ZM28.8195 13.1043V5.23669H30.5009V13.1043H28.8195Z\"/><path d=\"M19.8276 13.1043V11.8036L25.1098 3.42837H19.8911V2.00077H27.0608V3.30147L21.7628 11.6609H27.1084V13.1043H19.8276Z\"/></svg>";
|
|
2136
|
-
/** Inner markup for the default Zitadel attribution pill (label + logotype). */
|
|
2137
|
-
function zitadelAttributionPillInnerHtml() {
|
|
2138
|
-
return `Secured with <span class="zr-pill__logotype">${ZITADEL_ATTRIBUTION_LOGOTYPE_SVG}</span>`;
|
|
2139
|
-
}
|
|
2140
|
-
//#endregion
|
|
2141
|
-
//#region src/orchestrator/sanitiser.ts
|
|
2142
|
-
/**
|
|
2143
|
-
* Layer 2 of the template security pipeline (`docs/design/flowengine/template-security.md`).
|
|
2144
|
-
*
|
|
2145
|
-
* Wraps DOMPurify with the configuration the auth-UI templates need:
|
|
2146
|
-
*
|
|
2147
|
-
* - Allow every `<zl-*>` custom element registered in the manifest registry,
|
|
2148
|
-
* plus the union of attributes declared on each manifest.
|
|
2149
|
-
* - Allow common HTML structural / typographic tags used by `default.liquid`.
|
|
2150
|
-
* - Strip `on*` event handlers and any `<script>` / `<style>` tags (theming is
|
|
2151
|
-
* orchestrator-owned via `adoptedStyleSheets`).
|
|
2152
|
-
*
|
|
2153
|
-
* Runs after Liquid finishes rendering and before the HTML is handed to Lit's
|
|
2154
|
-
* `unsafeHTML` directive.
|
|
2155
|
-
*/
|
|
2156
|
-
/**
|
|
2157
|
-
* Standard HTML tags that templates may use as structural chrome around
|
|
2158
|
-
* `<zl-*>` atoms. Anything outside this list and the manifest registry is
|
|
2159
|
-
* stripped at sanitise-time.
|
|
2160
|
-
*/
|
|
2161
|
-
const STRUCTURAL_TAGS = [
|
|
2162
|
-
"div",
|
|
2163
|
-
"span",
|
|
2164
|
-
"p",
|
|
2165
|
-
"small",
|
|
2166
|
-
"strong",
|
|
2167
|
-
"em",
|
|
2168
|
-
"br",
|
|
2169
|
-
"hr",
|
|
2170
|
-
"h1",
|
|
2171
|
-
"h2",
|
|
2172
|
-
"h3",
|
|
2173
|
-
"h4",
|
|
2174
|
-
"h5",
|
|
2175
|
-
"h6",
|
|
2176
|
-
"ul",
|
|
2177
|
-
"ol",
|
|
2178
|
-
"li",
|
|
2179
|
-
"a",
|
|
2180
|
-
"img",
|
|
2181
|
-
"section",
|
|
2182
|
-
"header",
|
|
2183
|
-
"footer",
|
|
2184
|
-
"main",
|
|
2185
|
-
"nav",
|
|
2186
|
-
"aside",
|
|
2187
|
-
"article",
|
|
2188
|
-
"figure",
|
|
2189
|
-
"figcaption",
|
|
2190
|
-
"label",
|
|
2191
|
-
"fieldset",
|
|
2192
|
-
"legend"
|
|
2193
|
-
];
|
|
2194
|
-
/**
|
|
2195
|
-
* Attributes templates may carry on structural tags. Per-atom attributes are
|
|
2196
|
-
* derived from the manifest registry below — manifests are the single source
|
|
2197
|
-
* of truth for "what attributes does `<zl-field>` accept?".
|
|
2198
|
-
*/
|
|
2199
|
-
const COMMON_ATTRS = [
|
|
2200
|
-
"id",
|
|
2201
|
-
"class",
|
|
2202
|
-
"role",
|
|
2203
|
-
"slot",
|
|
2204
|
-
"tabindex",
|
|
2205
|
-
"lang",
|
|
2206
|
-
"dir",
|
|
2207
|
-
"alt",
|
|
2208
|
-
"title",
|
|
2209
|
-
"src",
|
|
2210
|
-
"srcset",
|
|
2211
|
-
"sizes",
|
|
2212
|
-
"href",
|
|
2213
|
-
"target",
|
|
2214
|
-
"rel",
|
|
2215
|
-
"style",
|
|
2216
|
-
"name",
|
|
2217
|
-
"for",
|
|
2218
|
-
"data-testid",
|
|
2219
|
-
"data-theme",
|
|
2220
|
-
"hidden"
|
|
2221
|
-
];
|
|
2222
|
-
const CUSTOM_TAG_PATTERN = /^zl-[a-z0-9-]+$/;
|
|
2223
|
-
function buildAllowedTagList() {
|
|
2224
|
-
const set = new Set(STRUCTURAL_TAGS);
|
|
2225
|
-
for (const manifest of manifestRegistry) set.add(manifest.tag);
|
|
2226
|
-
return [...set];
|
|
2227
|
-
}
|
|
2228
|
-
function buildAllowedAttrList() {
|
|
2229
|
-
const set = new Set(COMMON_ATTRS);
|
|
2230
|
-
for (const manifest of manifestRegistry) for (const attr of manifest.attrs) set.add(attr);
|
|
2231
|
-
return [...set];
|
|
2232
|
-
}
|
|
2233
|
-
/**
|
|
2234
|
-
* Returns a sanitiser bound to the current manifest registry. Callers should
|
|
2235
|
-
* cache the result; rebuilding is cheap but allocates a fresh DOMPurify config.
|
|
2236
|
-
*/
|
|
2237
|
-
function createSanitiser() {
|
|
2238
|
-
const config = {
|
|
2239
|
-
USE_PROFILES: { html: true },
|
|
2240
|
-
ADD_TAGS: buildAllowedTagList(),
|
|
2241
|
-
ADD_ATTR: buildAllowedAttrList(),
|
|
2242
|
-
FORBID_TAGS: [
|
|
2243
|
-
"script",
|
|
2244
|
-
"style",
|
|
2245
|
-
"iframe",
|
|
2246
|
-
"object",
|
|
2247
|
-
"embed",
|
|
2248
|
-
"form",
|
|
2249
|
-
"input",
|
|
2250
|
-
"button"
|
|
2251
|
-
],
|
|
2252
|
-
FORBID_ATTR: [
|
|
2253
|
-
"onerror",
|
|
2254
|
-
"onclick",
|
|
2255
|
-
"onload",
|
|
2256
|
-
"onfocus",
|
|
2257
|
-
"onblur"
|
|
2258
|
-
],
|
|
2259
|
-
CUSTOM_ELEMENT_HANDLING: {
|
|
2260
|
-
tagNameCheck: CUSTOM_TAG_PATTERN,
|
|
2261
|
-
attributeNameCheck: /^(?:[a-z][a-z0-9-]*)$/,
|
|
2262
|
-
allowCustomizedBuiltInElements: false
|
|
2263
|
-
},
|
|
2264
|
-
KEEP_CONTENT: true,
|
|
2265
|
-
ALLOW_DATA_ATTR: true,
|
|
2266
|
-
ALLOW_ARIA_ATTR: true,
|
|
2267
|
-
RETURN_TRUSTED_TYPE: false
|
|
2268
|
-
};
|
|
2269
|
-
return function sanitise(html) {
|
|
2270
|
-
const result = DOMPurify.sanitize(html, config);
|
|
2271
|
-
return typeof result === "string" ? result : String(result);
|
|
2272
|
-
};
|
|
2273
|
-
}
|
|
2274
|
-
//#endregion
|
|
2275
|
-
//#region src/orchestrator/templates/layout-chrome.css?inline
|
|
2276
|
-
var layout_chrome_default = ":host {\n background: var(--zl-color-surface-default-black, #0f0f11);\n min-height: 100%;\n color: var(--zl-color-text-primary-white, #f4f4f6);\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n display: block;\n}\n\n.zl-mount {\n background: inherit;\n min-height: 100vh;\n color: inherit;\n flex-direction: column;\n display: flex;\n}\n\n.zl-mount > zl-page-shell, .zl-mount > [data-zl-template-root] {\n flex: auto;\n}\n\n.zl-attribution {\n background: none;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.zl-attribution[hidden] {\n display: none;\n}\n\n.zl-card-title {\n font-family: var(--zl-font-family-heading, var(--zl-font-family-sans, system-ui, sans-serif));\n letter-spacing: -.02em;\n color: var(--zl-color-text-primary-white, #f4f4f6);\n text-align: left;\n margin: 0;\n font-size: 2rem;\n font-weight: 400;\n line-height: 2.5rem;\n}\n\n.zl-card-subtitle {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-text-primary-white, #f4f4f6);\n margin: 0;\n font-size: .875rem;\n font-weight: 400;\n line-height: 1.25rem;\n}\n\n.zl-card-nav {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-text-primary-white, #f4f4f6);\n text-align: left;\n margin: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5rem;\n}\n\n.zl-card-nav__link {\n font: inherit;\n line-height: inherit;\n color: var(--zl-color-icon-default-purple, #bba5e4);\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-nav__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-nav__link:focus-visible {\n outline: var(--zl-focus-width, 2px) solid var(--zl-focus-color, #f4f4f6);\n outline-offset: var(--zl-focus-offset, 2px);\n}\n\n.zl-card-forgot {\n text-align: left;\n margin: 0;\n}\n\n.zl-card-forgot__link {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-icon-default-purple, #bba5e4);\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5rem;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-forgot__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-forgot__link:focus-visible {\n outline: var(--zl-focus-width, 2px) solid var(--zl-focus-color, #f4f4f6);\n outline-offset: var(--zl-focus-offset, 2px);\n}\n";
|
|
2277
|
-
//#endregion
|
|
2278
|
-
//#region src/orchestrator/theme-controller.ts
|
|
2279
|
-
const DARK_QUERY = "(prefers-color-scheme: dark)";
|
|
2280
|
-
var ThemeController = class {
|
|
2281
|
-
host;
|
|
2282
|
-
branding;
|
|
2283
|
-
mediaQuery = null;
|
|
2284
|
-
_theme = "dark";
|
|
2285
|
-
constructor(host) {
|
|
2286
|
-
this.host = host;
|
|
2287
|
-
host.addController(this);
|
|
2288
|
-
}
|
|
2289
|
-
get theme() {
|
|
2290
|
-
return this._theme;
|
|
2291
|
-
}
|
|
2292
|
-
setBranding(branding) {
|
|
2293
|
-
this.branding = branding;
|
|
2294
|
-
this.refresh();
|
|
2295
|
-
}
|
|
2296
|
-
hostConnected() {
|
|
2297
|
-
this.refresh();
|
|
2298
|
-
}
|
|
2299
|
-
hostDisconnected() {
|
|
2300
|
-
this.detach();
|
|
2301
|
-
}
|
|
2302
|
-
refresh() {
|
|
2303
|
-
const mode = this.branding?.theme?.mode ?? "dark";
|
|
2304
|
-
if (mode === "auto") {
|
|
2305
|
-
this.attach();
|
|
2306
|
-
this.update(this.mediaQuery?.matches === false ? "light" : "dark");
|
|
2307
|
-
return;
|
|
2308
|
-
}
|
|
2309
|
-
this.detach();
|
|
2310
|
-
this.update(mode === "light" ? "light" : "dark");
|
|
2311
|
-
}
|
|
2312
|
-
attach() {
|
|
2313
|
-
if (this.mediaQuery || typeof matchMedia !== "function") return;
|
|
2314
|
-
this.mediaQuery = matchMedia(DARK_QUERY);
|
|
2315
|
-
this.mediaQuery.addEventListener("change", this.onMediaChange);
|
|
2316
|
-
}
|
|
2317
|
-
detach() {
|
|
2318
|
-
if (!this.mediaQuery) return;
|
|
2319
|
-
this.mediaQuery.removeEventListener("change", this.onMediaChange);
|
|
2320
|
-
this.mediaQuery = null;
|
|
2321
|
-
}
|
|
2322
|
-
onMediaChange = (event) => {
|
|
2323
|
-
this.update(event.matches ? "dark" : "light");
|
|
2324
|
-
};
|
|
2325
|
-
update(next) {
|
|
2326
|
-
if (this._theme === next) return;
|
|
2327
|
-
this._theme = next;
|
|
2328
|
-
this.host.requestUpdate();
|
|
2329
|
-
}
|
|
2330
|
-
};
|
|
2331
|
-
//#endregion
|
|
2332
|
-
//#region src/orchestrator/zitadel-login.ts
|
|
2333
|
-
let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
2334
|
-
static shadowRootOptions = {
|
|
2335
|
-
...LitElement.shadowRootOptions,
|
|
2336
|
-
delegatesFocus: true
|
|
2337
|
-
};
|
|
2338
|
-
static styles = css`
|
|
2339
|
-
:host {
|
|
2340
|
-
display: block;
|
|
2341
|
-
width: 100%;
|
|
2342
|
-
min-height: 100vh;
|
|
2343
|
-
}
|
|
2344
|
-
`;
|
|
2345
|
-
#_purpose_accessor_storage = "login";
|
|
2346
|
-
get purpose() {
|
|
2347
|
-
return this.#_purpose_accessor_storage;
|
|
2348
|
-
}
|
|
2349
|
-
set purpose(value) {
|
|
2350
|
-
this.#_purpose_accessor_storage = value;
|
|
2351
|
-
}
|
|
2352
|
-
#_project_accessor_storage;
|
|
2353
|
-
get project() {
|
|
2354
|
-
return this.#_project_accessor_storage;
|
|
2355
|
-
}
|
|
2356
|
-
set project(value) {
|
|
2357
|
-
this.#_project_accessor_storage = value;
|
|
2358
|
-
}
|
|
2359
|
-
#_projectId_accessor_storage = "";
|
|
2360
|
-
get projectId() {
|
|
2361
|
-
return this.#_projectId_accessor_storage;
|
|
2362
|
-
}
|
|
2363
|
-
set projectId(value) {
|
|
2364
|
-
this.#_projectId_accessor_storage = value;
|
|
2365
|
-
}
|
|
2366
|
-
#_proxyPath_accessor_storage = "";
|
|
2367
|
-
get proxyPath() {
|
|
2368
|
-
return this.#_proxyPath_accessor_storage;
|
|
2369
|
-
}
|
|
2370
|
-
set proxyPath(value) {
|
|
2371
|
-
this.#_proxyPath_accessor_storage = value;
|
|
2372
|
-
}
|
|
2373
|
-
#_url_accessor_storage = "";
|
|
2374
|
-
get url() {
|
|
2375
|
-
return this.#_url_accessor_storage;
|
|
2376
|
-
}
|
|
2377
|
-
set url(value) {
|
|
2378
|
-
this.#_url_accessor_storage = value;
|
|
2379
|
-
}
|
|
2380
|
-
#_postSignInUrl_accessor_storage = "";
|
|
2381
|
-
get postSignInUrl() {
|
|
2382
|
-
return this.#_postSignInUrl_accessor_storage;
|
|
2383
|
-
}
|
|
2384
|
-
set postSignInUrl(value) {
|
|
2385
|
-
this.#_postSignInUrl_accessor_storage = value;
|
|
2386
|
-
}
|
|
2387
|
-
#_resumeFlowId_accessor_storage = "";
|
|
2388
|
-
get resumeFlowId() {
|
|
2389
|
-
return this.#_resumeFlowId_accessor_storage;
|
|
2390
|
-
}
|
|
2391
|
-
set resumeFlowId(value) {
|
|
2392
|
-
this.#_resumeFlowId_accessor_storage = value;
|
|
2393
|
-
}
|
|
2394
|
-
#_lang_accessor_storage = "";
|
|
2395
|
-
get lang() {
|
|
2396
|
-
return this.#_lang_accessor_storage;
|
|
2397
|
-
}
|
|
2398
|
-
set lang(value) {
|
|
2399
|
-
this.#_lang_accessor_storage = value;
|
|
2400
|
-
}
|
|
2401
|
-
#_locales_accessor_storage;
|
|
2402
|
-
get locales() {
|
|
2403
|
-
return this.#_locales_accessor_storage;
|
|
2404
|
-
}
|
|
2405
|
-
set locales(value) {
|
|
2406
|
-
this.#_locales_accessor_storage = value;
|
|
2407
|
-
}
|
|
2408
|
-
#_response_accessor_storage = null;
|
|
2409
|
-
get response() {
|
|
2410
|
-
return this.#_response_accessor_storage;
|
|
2411
|
-
}
|
|
2412
|
-
set response(value) {
|
|
2413
|
-
this.#_response_accessor_storage = value;
|
|
2414
|
-
}
|
|
2415
|
-
#_branding_accessor_storage = void 0;
|
|
2416
|
-
get branding() {
|
|
2417
|
-
return this.#_branding_accessor_storage;
|
|
2418
|
-
}
|
|
2419
|
-
set branding(value) {
|
|
2420
|
-
this.#_branding_accessor_storage = value;
|
|
2421
|
-
}
|
|
2422
|
-
#_loading_accessor_storage = false;
|
|
2423
|
-
get loading() {
|
|
2424
|
-
return this.#_loading_accessor_storage;
|
|
2425
|
-
}
|
|
2426
|
-
set loading(value) {
|
|
2427
|
-
this.#_loading_accessor_storage = value;
|
|
2428
|
-
}
|
|
2429
|
-
#_startupError_accessor_storage = null;
|
|
2430
|
-
get startupError() {
|
|
2431
|
-
return this.#_startupError_accessor_storage;
|
|
2432
|
-
}
|
|
2433
|
-
set startupError(value) {
|
|
2434
|
-
this.#_startupError_accessor_storage = value;
|
|
2435
|
-
}
|
|
2436
|
-
#_formValues_accessor_storage = {};
|
|
2437
|
-
get formValues() {
|
|
2438
|
-
return this.#_formValues_accessor_storage;
|
|
2439
|
-
}
|
|
2440
|
-
set formValues(value) {
|
|
2441
|
-
this.#_formValues_accessor_storage = value;
|
|
2442
|
-
}
|
|
2443
|
-
themeController = new ThemeController(this);
|
|
2444
|
-
engine = null;
|
|
2445
|
-
sanitise = createSanitiser();
|
|
2446
|
-
/**
|
|
2447
|
-
* Cached compiled tenant template, keyed by source string. Re-rendering on
|
|
2448
|
-
* every `formValues` change otherwise re-parses the same template.
|
|
2449
|
-
*/
|
|
2450
|
-
tenantTemplateCache = null;
|
|
2451
|
-
createRenderRoot() {
|
|
2452
|
-
const root = super.createRenderRoot();
|
|
2453
|
-
if (root instanceof ShadowRoot) {
|
|
2454
|
-
const existing = Array.isArray(root.adoptedStyleSheets) ? root.adoptedStyleSheets : [];
|
|
2455
|
-
const sheet = new CSSStyleSheet();
|
|
2456
|
-
sheet.replaceSync(layout_chrome_default);
|
|
2457
|
-
root.adoptedStyleSheets = [...existing, sheet];
|
|
2458
|
-
root.addEventListener("zl-input", this.handleAtomInput);
|
|
2459
|
-
root.addEventListener("zl-submit", this.handleAtomSubmit);
|
|
2460
|
-
root.addEventListener("click", this.handleDelegatedAction);
|
|
2461
|
-
root.addEventListener("submit", this.handleFormSubmit);
|
|
2462
|
-
root.addEventListener("zl-passkey-result", this.handlePasskeyResult);
|
|
2463
|
-
root.addEventListener("zl-passkey-error", this.handlePasskeyError);
|
|
2464
|
-
}
|
|
2465
|
-
return root;
|
|
2466
|
-
}
|
|
2467
|
-
/**
|
|
2468
|
-
* Start the flow after the first render rather than in `connectedCallback`.
|
|
2469
|
-
* Frameworks that wrap web components (e.g. `@lit/react` in the console)
|
|
2470
|
-
* attach the element first and then assign object properties (`branding`,
|
|
2471
|
-
* `locale`) via setters. `connectedCallback` runs synchronously on attach
|
|
2472
|
-
* — before any setters from a wrapper's effects/refs — so reading those
|
|
2473
|
-
* properties there sees stale defaults. `firstUpdated` runs after Lit's
|
|
2474
|
-
* first render, by which time setters from the wrapping framework have
|
|
2475
|
-
* fired.
|
|
2476
|
-
*/
|
|
2477
|
-
firstUpdated() {
|
|
2478
|
-
queueMicrotask(() => void this.startFlow());
|
|
2479
|
-
}
|
|
2480
|
-
/**
|
|
2481
|
-
* Resolves the effective locale dictionary. The built-in dictionary for the
|
|
2482
|
-
* resolved language is used as the base; entries from the `locales` map (if
|
|
2483
|
-
* set) are spread on top so partial overrides work without importing and
|
|
2484
|
-
* spreading the full base dictionary.
|
|
2485
|
-
*
|
|
2486
|
-
* Priority: explicit `lang` attr → `navigator.language` (user preference)
|
|
2487
|
-
* → `document.documentElement.lang` (page default) → English fallback.
|
|
2488
|
-
*/
|
|
2489
|
-
resolveLocale() {
|
|
2490
|
-
const primary = ((this.lang || (typeof navigator !== "undefined" ? navigator.language : "") || (typeof document !== "undefined" ? document.documentElement.lang : "")).split("-")[0] ?? "").toLowerCase();
|
|
2491
|
-
const builtin = builtinLocales[primary] ?? en;
|
|
2492
|
-
const custom = this.locales?.[primary];
|
|
2493
|
-
return custom ? {
|
|
2494
|
-
...builtin,
|
|
2495
|
-
...custom
|
|
2496
|
-
} : builtin;
|
|
2497
|
-
}
|
|
2498
|
-
willUpdate(changed) {
|
|
2499
|
-
if (!this.engine || changed.has("locales") || changed.has("lang")) this.engine = createLiquidEngine({ locale: this.resolveLocale() });
|
|
2500
|
-
const root = this.shadowRoot;
|
|
2501
|
-
if (root) {
|
|
2502
|
-
applyBaseTokens(root);
|
|
2503
|
-
applyBrandingTokens(root, this.branding, this.themeController.theme);
|
|
2504
|
-
applyFontUrl(root, this.branding?.font_url ?? null);
|
|
2505
|
-
}
|
|
2506
|
-
this.dataset.theme = this.themeController.theme;
|
|
2507
|
-
this.toggleAttribute("data-theme-dark", this.themeController.theme === "dark");
|
|
2508
|
-
this.setAttribute("aria-busy", this.loading ? "true" : "false");
|
|
2509
|
-
}
|
|
2510
|
-
updated(changed) {
|
|
2511
|
-
if (!changed.has("response")) return;
|
|
2512
|
-
this.hydrateStepAfterRender();
|
|
2513
|
-
}
|
|
2514
|
-
/**
|
|
2515
|
-
* Apply captured values and move focus once the new step has fully
|
|
2516
|
-
* rendered. This commit produces the step's `zl-field`/`zl-button` atoms,
|
|
2517
|
-
* but those render their own shadow DOM on a later microtask — so await
|
|
2518
|
-
* this element's update *and* the child atoms' first render before touching
|
|
2519
|
-
* them, rather than guessing a frame with `requestAnimationFrame`.
|
|
2520
|
-
*/
|
|
2521
|
-
async hydrateStepAfterRender() {
|
|
2522
|
-
await this.updateComplete;
|
|
2523
|
-
const atoms = this.shadowRoot?.querySelectorAll("zl-field, zl-button");
|
|
2524
|
-
if (atoms) await Promise.all(Array.from(atoms).map((atom) => atom.updateComplete));
|
|
2525
|
-
this.applyValuesToFields();
|
|
2526
|
-
this.moveFocusToFirstField();
|
|
2527
|
-
}
|
|
2528
|
-
render() {
|
|
2529
|
-
if (this.startupError) return html`<form class="zl-mount" novalidate>
|
|
2530
|
-
<zl-alert severity="error">${this.startupError}</zl-alert>
|
|
2531
|
-
</form>`;
|
|
2532
|
-
if (!this.response || !this.engine) return html`<slot name="loader"></slot>`;
|
|
2533
|
-
const rendered = this.injectAttribution(this.renderStep(this.response.step, this.engine));
|
|
2534
|
-
return html`<form
|
|
2535
|
-
class="zl-mount"
|
|
2536
|
-
part="form"
|
|
2537
|
-
novalidate
|
|
2538
|
-
aria-busy=${this.loading ? "true" : "false"}
|
|
2539
|
-
>
|
|
2540
|
-
${unsafeHTML(rendered)}
|
|
2541
|
-
</form>`;
|
|
2542
|
-
}
|
|
2543
|
-
/**
|
|
2544
|
-
* Inject the attribution badge into the rendered template's
|
|
2545
|
-
* `<zl-page-shell>` footer slot. The attribution must live INSIDE the
|
|
2546
|
-
* page-shell so it sits within the 100vh viewport rhythm (matching the
|
|
2547
|
-
* Figma sign-in frame where the pill sits 24px below the card, both
|
|
2548
|
-
* centred on the page). It can't be a sibling of the page-shell because
|
|
2549
|
-
* the page-shell already occupies the full viewport height.
|
|
2550
|
-
*
|
|
2551
|
-
* This markup is appended AFTER `renderStep` has sanitised the Liquid
|
|
2552
|
-
* output, so it is not run through DOMPurify. It is orchestrator-owned and
|
|
2553
|
-
* any tenant-supplied values (`custom_link`) are escaped via `escapeHtml`.
|
|
2554
|
-
*/
|
|
2555
|
-
injectAttribution(rendered) {
|
|
2556
|
-
const html = this.renderAttributionHtml();
|
|
2557
|
-
if (!html) return rendered;
|
|
2558
|
-
if (rendered.includes("</zl-page-shell>")) return rendered.replace("</zl-page-shell>", `${html}</zl-page-shell>`);
|
|
2559
|
-
return rendered + html;
|
|
2560
|
-
}
|
|
2561
|
-
/**
|
|
2562
|
-
* "Secured with Zitadel" attribution chrome injected into every
|
|
2563
|
-
* template's page-shell footer slot. Controlled by
|
|
2564
|
-
* `branding.attribution.show_zitadel` — defaults to `true` for
|
|
2565
|
-
* community / OSS deployments. Licensed tenants can suppress the badge
|
|
2566
|
-
* entirely or swap it for a `custom_link` value.
|
|
2567
|
-
*/
|
|
2568
|
-
renderAttributionHtml() {
|
|
2569
|
-
const attribution = this.branding?.attribution;
|
|
2570
|
-
const show = attribution?.show_zitadel !== false;
|
|
2571
|
-
const custom = attribution?.custom_link;
|
|
2572
|
-
if (!show && !custom) return "";
|
|
2573
|
-
if (custom) return `<div slot="footer" part="attribution" class="zl-attribution"><zl-pill tone="neutral" href="${escapeHtml(String(custom.href))}">${escapeHtml(String(custom.label))}</zl-pill></div>`;
|
|
2574
|
-
return `<div slot="footer" part="attribution" class="zl-attribution"><zl-pill tone="neutral" href="https://zitadel.com" part="attribution-pill" aria-label="Secured with Zitadel">${zitadelAttributionPillInnerHtml()}</zl-pill></div>`;
|
|
2575
|
-
}
|
|
2576
|
-
/** Declarative config read from this element's attributes. */
|
|
2577
|
-
get projectAttrs() {
|
|
2578
|
-
return {
|
|
2579
|
-
projectId: this.projectId,
|
|
2580
|
-
proxyPath: this.proxyPath,
|
|
2581
|
-
url: this.url
|
|
2582
|
-
};
|
|
2583
|
-
}
|
|
2584
|
-
async startFlow() {
|
|
2585
|
-
this.loading = true;
|
|
2586
|
-
this.startupError = null;
|
|
2587
|
-
try {
|
|
2588
|
-
const { project: cfg, api } = resolveApi(this.project, this.projectAttrs, "<zitadel-login>");
|
|
2589
|
-
let wire;
|
|
2590
|
-
if (this.resumeFlowId) wire = await getCurrentStep(api, this.resumeFlowId);
|
|
2591
|
-
else {
|
|
2592
|
-
if (!cfg.projectId) throw new Error("<zitadel-login> requires a project id (the `project-id` attribute, `configureZitadel({ projectId })`, or a `project` handle) to start a flow.");
|
|
2593
|
-
wire = await startFlow(api, {
|
|
2594
|
-
project_id: cfg.projectId,
|
|
2595
|
-
purpose: this.purpose
|
|
2596
|
-
});
|
|
2597
|
-
}
|
|
2598
|
-
this.applyResponse(wire);
|
|
2599
|
-
} catch (error) {
|
|
2600
|
-
this.handleTransportError(error);
|
|
2601
|
-
} finally {
|
|
2602
|
-
this.loading = false;
|
|
2603
|
-
}
|
|
2604
|
-
}
|
|
2605
|
-
applyResponse(wire) {
|
|
2606
|
-
this.response = wire;
|
|
2607
|
-
const { branding, issues } = validateBranding(wire.branding);
|
|
2608
|
-
this.branding = branding;
|
|
2609
|
-
this.themeController.setBranding(branding);
|
|
2610
|
-
if (issues.length > 0) console.warn("[zitadel-login] branding payload has issues:", issues);
|
|
2611
|
-
this.formValues = {
|
|
2612
|
-
...collectInitialValues(wire.step),
|
|
2613
|
-
...this.formValues
|
|
2614
|
-
};
|
|
2615
|
-
this.maybeCompleteFlow(wire);
|
|
2616
|
-
}
|
|
2617
|
-
/**
|
|
2618
|
-
* Acts on terminal flow steps. The wire surfaces two kinds of completion:
|
|
2619
|
-
*
|
|
2620
|
-
* - `step.complete === "redirect"` — navigate the browser to
|
|
2621
|
-
* `response.redirect_uri` (OIDC/SAML `auth_request_id` resolved). This
|
|
2622
|
-
* takes precedence over `post-sign-in-url`.
|
|
2623
|
-
* - `step.complete === "show"` — when `post-sign-in-url` is set, exchange
|
|
2624
|
-
* the `handoff_token` for a session cookie and navigate there.
|
|
2625
|
-
*
|
|
2626
|
-
* `zitadel-flow-complete` is always emitted so hosts with custom post-sign-in
|
|
2627
|
-
* flows can handle the handoff themselves when `post-sign-in-url` is omitted.
|
|
2628
|
-
*/
|
|
2629
|
-
async maybeCompleteFlow(response) {
|
|
2630
|
-
const behavior = response.step.complete;
|
|
2631
|
-
if (!behavior) return;
|
|
2632
|
-
emit(this, "zitadel-flow-complete", {
|
|
2633
|
-
behavior,
|
|
2634
|
-
redirect_uri: response.redirect_uri,
|
|
2635
|
-
handoff_token: response.handoff_token,
|
|
2636
|
-
handoff_token_expires_at: response.handoff_token_expires_at
|
|
2637
|
-
});
|
|
2638
|
-
if (typeof window === "undefined") return;
|
|
2639
|
-
if (behavior === "redirect" && response.redirect_uri) {
|
|
2640
|
-
window.location.assign(response.redirect_uri);
|
|
2641
|
-
return;
|
|
2642
|
-
}
|
|
2643
|
-
const handoffToken = response.handoff_token;
|
|
2644
|
-
if (behavior === "show" && handoffToken && this.postSignInUrl) {
|
|
2645
|
-
this.loading = true;
|
|
2646
|
-
try {
|
|
2647
|
-
const { project: cfg, api } = resolveApi(this.project, this.projectAttrs, "<zitadel-login>");
|
|
2648
|
-
await exchangeSession(api, { handoff_token: handoffToken }, { project_id: cfg.projectId });
|
|
2649
|
-
window.location.assign(this.postSignInUrl);
|
|
2650
|
-
} catch (error) {
|
|
2651
|
-
this.handleTransportError(error);
|
|
2652
|
-
} finally {
|
|
2653
|
-
this.loading = false;
|
|
2654
|
-
}
|
|
2655
|
-
}
|
|
2656
|
-
}
|
|
2657
|
-
renderStep(step, engine) {
|
|
2658
|
-
const tenantSource = typeof this.branding?.liquid_template === "string" && this.branding.liquid_template.length > 0 ? this.branding.liquid_template : null;
|
|
2659
|
-
const errors = step.error ? step.error.startsWith("error.") ? [{ text_key: step.error }] : [{ message: step.error }] : [];
|
|
2660
|
-
const fields = step.fields ?? [];
|
|
2661
|
-
const actions = step.actions ?? [];
|
|
2662
|
-
const context = {
|
|
2663
|
-
step: {
|
|
2664
|
-
name: step.name,
|
|
2665
|
-
complete: step.complete,
|
|
2666
|
-
texts: step.texts ?? {}
|
|
2667
|
-
},
|
|
2668
|
-
fields,
|
|
2669
|
-
actions,
|
|
2670
|
-
gates: step.gates ?? {},
|
|
2671
|
-
sso_providers: step.sso_providers ?? [],
|
|
2672
|
-
challenge: step.challenge ?? null,
|
|
2673
|
-
messages: [],
|
|
2674
|
-
identity: this.deriveIdentity(),
|
|
2675
|
-
errors,
|
|
2676
|
-
branding: this.branding ?? {},
|
|
2677
|
-
loading: this.loading
|
|
2678
|
-
};
|
|
2679
|
-
let raw;
|
|
2680
|
-
try {
|
|
2681
|
-
if (tenantSource) {
|
|
2682
|
-
const compiled = this.tenantTemplateCache?.source === tenantSource ? this.tenantTemplateCache.template : engine.parse(tenantSource);
|
|
2683
|
-
this.tenantTemplateCache = {
|
|
2684
|
-
source: tenantSource,
|
|
2685
|
-
template: compiled
|
|
2686
|
-
};
|
|
2687
|
-
raw = engine.renderSync(compiled, context);
|
|
2688
|
-
} else raw = engine.renderFileSync(TEMPLATE_NAMES.default, context);
|
|
2689
|
-
} catch (error) {
|
|
2690
|
-
console.error("[zitadel-login] Liquid render failed:", error);
|
|
2691
|
-
try {
|
|
2692
|
-
raw = engine.renderFileSync(TEMPLATE_NAMES.default, context);
|
|
2693
|
-
} catch {
|
|
2694
|
-
return `<zl-alert severity="error">We couldn't render this step.</zl-alert>`;
|
|
2695
|
-
}
|
|
2696
|
-
}
|
|
2697
|
-
const patched = patchMandatoryGates(raw, step, this.resolveLocale());
|
|
2698
|
-
return this.sanitise(patched);
|
|
2699
|
-
}
|
|
2700
|
-
/**
|
|
2701
|
-
* Build a `FlowIdentity` from the orchestrator's captured form values so
|
|
2702
|
-
* the signed-in template can greet the user by email without the API
|
|
2703
|
-
* having to round-trip identity claims. Email comes from the
|
|
2704
|
-
* identifier step; display name composes from `given_name` /
|
|
2705
|
-
* `family_name` when the register step ran.
|
|
2706
|
-
*/
|
|
2707
|
-
deriveIdentity() {
|
|
2708
|
-
const email = this.formValues.email?.trim();
|
|
2709
|
-
const display = [this.formValues.given_name?.trim(), this.formValues.family_name?.trim()].filter(Boolean).join(" ").trim();
|
|
2710
|
-
if (!email && !display) return null;
|
|
2711
|
-
return {
|
|
2712
|
-
...email ? { email_address: email } : {},
|
|
2713
|
-
...display ? { display_name: display } : email ? { display_name: email } : {}
|
|
2714
|
-
};
|
|
2715
|
-
}
|
|
2716
|
-
applyValuesToFields() {
|
|
2717
|
-
const root = this.shadowRoot;
|
|
2718
|
-
if (!root) return;
|
|
2719
|
-
const fields = root.querySelectorAll("zl-field");
|
|
2720
|
-
if (fields.length === 0) return;
|
|
2721
|
-
for (const field of fields) {
|
|
2722
|
-
const name = field.getAttribute("name");
|
|
2723
|
-
if (!name || !(name in this.formValues)) continue;
|
|
2724
|
-
const next = this.formValues[name];
|
|
2725
|
-
if (field.value !== next) field.value = next;
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
|
-
captureValuesFromFields() {
|
|
2729
|
-
const root = this.shadowRoot;
|
|
2730
|
-
if (!root) return this.formValues;
|
|
2731
|
-
const fields = root.querySelectorAll("zl-field");
|
|
2732
|
-
if (fields.length === 0) return this.formValues;
|
|
2733
|
-
const next = { ...this.formValues };
|
|
2734
|
-
let changed = false;
|
|
2735
|
-
for (const field of fields) {
|
|
2736
|
-
const name = field.getAttribute("name");
|
|
2737
|
-
const value = this.currentFieldValue(field);
|
|
2738
|
-
if (!name || value === void 0) continue;
|
|
2739
|
-
if (field.value !== value) field.value = value;
|
|
2740
|
-
if (next[name] !== value) {
|
|
2741
|
-
next[name] = value;
|
|
2742
|
-
changed = true;
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
if (changed) this.formValues = next;
|
|
2746
|
-
return next;
|
|
2747
|
-
}
|
|
2748
|
-
currentFieldValue(field) {
|
|
2749
|
-
const native = field.shadowRoot?.querySelector("input");
|
|
2750
|
-
if (native && native.value !== field.value) return native.value;
|
|
2751
|
-
return typeof field.value === "string" ? field.value : void 0;
|
|
2752
|
-
}
|
|
2753
|
-
handleAtomInput = (event) => {
|
|
2754
|
-
if (!event.detail) return;
|
|
2755
|
-
const { name, value } = event.detail;
|
|
2756
|
-
if (!name) return;
|
|
2757
|
-
this.formValues = {
|
|
2758
|
-
...this.formValues,
|
|
2759
|
-
[name]: value
|
|
2760
|
-
};
|
|
2761
|
-
this.syncFieldElementValue(name, value);
|
|
2762
|
-
emit(this, "zitadel-flow-input", {
|
|
2763
|
-
name,
|
|
2764
|
-
value
|
|
2765
|
-
});
|
|
2766
|
-
};
|
|
2767
|
-
syncFieldElementValue(name, value) {
|
|
2768
|
-
const root = this.shadowRoot;
|
|
2769
|
-
if (!root) return;
|
|
2770
|
-
const fields = root.querySelectorAll("zl-field");
|
|
2771
|
-
for (const field of fields) {
|
|
2772
|
-
if (field.getAttribute("name") !== name) continue;
|
|
2773
|
-
if (field.value !== value) field.value = value;
|
|
2774
|
-
const native = field.shadowRoot?.querySelector("input");
|
|
2775
|
-
if (native && native.value !== value) native.value = value;
|
|
2776
|
-
}
|
|
2777
|
-
}
|
|
2778
|
-
handleAtomSubmit = (event) => {
|
|
2779
|
-
if (this.loading) return;
|
|
2780
|
-
this.submit(event.detail?.action ?? null);
|
|
2781
|
-
};
|
|
2782
|
-
/** Secondary navigation rows (`data-action` on `.zl-card-nav__link`). */
|
|
2783
|
-
handleDelegatedAction = (event) => {
|
|
2784
|
-
const target = event.target?.closest("[data-action]");
|
|
2785
|
-
if (!target || target.closest("zl-button") || this.loading) return;
|
|
2786
|
-
const action = target.getAttribute("data-action");
|
|
2787
|
-
if (!action) return;
|
|
2788
|
-
event.preventDefault();
|
|
2789
|
-
this.submit(action);
|
|
2790
|
-
};
|
|
2791
|
-
handleFormSubmit = (event) => {
|
|
2792
|
-
event.preventDefault();
|
|
2793
|
-
if (this.loading) return;
|
|
2794
|
-
const action = event.submitter?.getAttribute?.("action") ?? null ?? this.findPrimaryAction();
|
|
2795
|
-
this.submit(action);
|
|
2796
|
-
};
|
|
2797
|
-
/**
|
|
2798
|
-
* Handle a successful WebAuthn ceremony. Auto-submit with the proof
|
|
2799
|
-
* as `challenge_response` so the flow advances without extra user
|
|
2800
|
-
* interaction — the ceremony IS the factor verification (ADR 013).
|
|
2801
|
-
*/
|
|
2802
|
-
handlePasskeyResult = (event) => {
|
|
2803
|
-
if (this.loading) return;
|
|
2804
|
-
const { challenge_id, method, proof } = event.detail;
|
|
2805
|
-
this.submit(method, {
|
|
2806
|
-
challenge_id,
|
|
2807
|
-
method,
|
|
2808
|
-
proof
|
|
2809
|
-
});
|
|
2810
|
-
};
|
|
2811
|
-
/**
|
|
2812
|
-
* Handle a WebAuthn ceremony error. Re-render the current step with
|
|
2813
|
-
* an error message so the user sees feedback and can retry or skip.
|
|
2814
|
-
*
|
|
2815
|
-
* Guard: if the step already carries the same error key, skip the update.
|
|
2816
|
-
* Mutating `this.response` triggers `unsafeHTML` to replace the DOM tree,
|
|
2817
|
-
* which reconnects a fresh `<zl-passkey>` that immediately re-starts the
|
|
2818
|
-
* ceremony — creating an infinite loop. The guard breaks the cycle.
|
|
2819
|
-
*
|
|
2820
|
-
* We also strip the `challenge` from the step so the template does not
|
|
2821
|
-
* render a new `<zl-passkey>` on re-render. Without this, the first
|
|
2822
|
-
* cancel would trigger a second ceremony (the guard prevents a third).
|
|
2823
|
-
*/
|
|
2824
|
-
handlePasskeyError = (event) => {
|
|
2825
|
-
if (!this.response) return;
|
|
2826
|
-
const { error: message, aborted } = event.detail;
|
|
2827
|
-
const errorKey = aborted ? "error.passkey_cancelled" : "error.passkey_failed";
|
|
2828
|
-
if (this.response.step.error === errorKey) return;
|
|
2829
|
-
const { challenge: _dropped, ...stepWithoutChallenge } = this.response.step;
|
|
2830
|
-
this.response = {
|
|
2831
|
-
...this.response,
|
|
2832
|
-
step: {
|
|
2833
|
-
...stepWithoutChallenge,
|
|
2834
|
-
error: errorKey
|
|
2835
|
-
}
|
|
2836
|
-
};
|
|
2837
|
-
console.warn(`[zitadel-login] passkey ceremony ${aborted ? "cancelled" : "failed"}: ${message}`);
|
|
2838
|
-
};
|
|
2839
|
-
findPrimaryAction() {
|
|
2840
|
-
const root = this.shadowRoot;
|
|
2841
|
-
if (!root) return null;
|
|
2842
|
-
return (root.querySelector("zl-button[hierarchy=\"primary\"][type=\"submit\"]") ?? root.querySelector("zl-button[hierarchy=\"primary\"]"))?.getAttribute("action") || null;
|
|
2843
|
-
}
|
|
2844
|
-
moveFocusToFirstField() {
|
|
2845
|
-
const root = this.shadowRoot;
|
|
2846
|
-
if (!root) return;
|
|
2847
|
-
const focusables = root.querySelectorAll("zl-field, zl-button");
|
|
2848
|
-
Array.from(focusables).find((el) => !el.hasAttribute("disabled"))?.focus();
|
|
2849
|
-
}
|
|
2850
|
-
async submit(action, challengeResponse) {
|
|
2851
|
-
if (!this.response || this.loading) return;
|
|
2852
|
-
const { id, session_token } = this.response;
|
|
2853
|
-
this.loading = true;
|
|
2854
|
-
try {
|
|
2855
|
-
const formValues = this.captureValuesFromFields();
|
|
2856
|
-
const stepFields = this.response.step.fields ?? [];
|
|
2857
|
-
const fields = {};
|
|
2858
|
-
for (const f of stepFields) {
|
|
2859
|
-
const value = formValues[f.name];
|
|
2860
|
-
if (value !== void 0) fields[f.name] = value;
|
|
2861
|
-
}
|
|
2862
|
-
const body = {
|
|
2863
|
-
session_token,
|
|
2864
|
-
action: action ?? "submit",
|
|
2865
|
-
fields,
|
|
2866
|
-
...challengeResponse ? { challenge_response: challengeResponse } : {}
|
|
2867
|
-
};
|
|
2868
|
-
const { api } = resolveApi(this.project, this.projectAttrs, "<zitadel-login>");
|
|
2869
|
-
const wire = await submitStep(api, id, body);
|
|
2870
|
-
this.applyResponse(wire);
|
|
2871
|
-
emit(this, "zitadel-flow-step", { step: wire.step });
|
|
2872
|
-
} catch (error) {
|
|
2873
|
-
this.handleTransportError(error);
|
|
2874
|
-
} finally {
|
|
2875
|
-
this.loading = false;
|
|
2876
|
-
}
|
|
2877
|
-
}
|
|
2878
|
-
handleTransportError(error) {
|
|
2879
|
-
const message = error instanceof Error ? error.message : "Unexpected error contacting the Flow API.";
|
|
2880
|
-
this.startupError = message;
|
|
2881
|
-
console.error("[zitadel-login]", error);
|
|
2882
|
-
emit(this, "zitadel-flow-error", { message });
|
|
2883
|
-
}
|
|
2884
|
-
};
|
|
2885
|
-
__decorate([property({ type: String })], ZitadelLogin.prototype, "purpose", null);
|
|
2886
|
-
__decorate([property({ attribute: false })], ZitadelLogin.prototype, "project", null);
|
|
2887
|
-
__decorate([property({
|
|
2888
|
-
type: String,
|
|
2889
|
-
attribute: "project-id"
|
|
2890
|
-
})], ZitadelLogin.prototype, "projectId", null);
|
|
2891
|
-
__decorate([property({
|
|
2892
|
-
type: String,
|
|
2893
|
-
attribute: "proxy-path"
|
|
2894
|
-
})], ZitadelLogin.prototype, "proxyPath", null);
|
|
2895
|
-
__decorate([property({ type: String })], ZitadelLogin.prototype, "url", null);
|
|
2896
|
-
__decorate([property({
|
|
2897
|
-
type: String,
|
|
2898
|
-
attribute: "post-sign-in-url"
|
|
2899
|
-
})], ZitadelLogin.prototype, "postSignInUrl", null);
|
|
2900
|
-
__decorate([property({
|
|
2901
|
-
type: String,
|
|
2902
|
-
attribute: "resume-flow-id"
|
|
2903
|
-
})], ZitadelLogin.prototype, "resumeFlowId", null);
|
|
2904
|
-
__decorate([property({ type: String })], ZitadelLogin.prototype, "lang", null);
|
|
2905
|
-
__decorate([property({ attribute: false })], ZitadelLogin.prototype, "locales", null);
|
|
2906
|
-
__decorate([state()], ZitadelLogin.prototype, "response", null);
|
|
2907
|
-
__decorate([state()], ZitadelLogin.prototype, "branding", null);
|
|
2908
|
-
__decorate([state()], ZitadelLogin.prototype, "loading", null);
|
|
2909
|
-
__decorate([state()], ZitadelLogin.prototype, "startupError", null);
|
|
2910
|
-
__decorate([state()], ZitadelLogin.prototype, "formValues", null);
|
|
2911
|
-
ZitadelLogin = __decorate([customElement("zitadel-login")], ZitadelLogin);
|
|
2912
|
-
function collectInitialValues(step) {
|
|
2913
|
-
const values = {};
|
|
2914
|
-
if (!step.fields) return values;
|
|
2915
|
-
for (const field of step.fields) values[field.name] = typeof field.value === "string" ? field.value : "";
|
|
2916
|
-
return values;
|
|
2917
|
-
}
|
|
2918
|
-
//#endregion
|
|
2919
|
-
//#region src/orchestrator/zitadel-logout.ts
|
|
2920
|
-
const DISPLAY_COOKIE_NAME = "__nextgen_display";
|
|
2921
|
-
let ZitadelLogout = class ZitadelLogout extends LitElement {
|
|
2922
|
-
static styles = [baseHostStyles, css`
|
|
2923
|
-
:host {
|
|
2924
|
-
display: inline-block;
|
|
2925
|
-
position: relative;
|
|
2926
|
-
}
|
|
2927
|
-
|
|
2928
|
-
.trigger {
|
|
2929
|
-
all: unset;
|
|
2930
|
-
cursor: pointer;
|
|
2931
|
-
width: 2.5rem;
|
|
2932
|
-
height: 2.5rem;
|
|
2933
|
-
border-radius: 9999px;
|
|
2934
|
-
background: ${t.color.surface.defaultWhite};
|
|
2935
|
-
color: ${t.color.text.buttonDefault};
|
|
2936
|
-
font-size: 0.875rem;
|
|
2937
|
-
font-weight: 600;
|
|
2938
|
-
display: inline-flex;
|
|
2939
|
-
align-items: center;
|
|
2940
|
-
justify-content: center;
|
|
2941
|
-
letter-spacing: 0.02em;
|
|
2942
|
-
user-select: none;
|
|
2943
|
-
transition: box-shadow ${t.motion.duration.fast} ${t.motion.easing.standard};
|
|
2944
|
-
}
|
|
2945
|
-
.trigger:focus-visible {
|
|
2946
|
-
${focusVisibleStyles};
|
|
2947
|
-
}
|
|
2948
|
-
.trigger[aria-expanded="true"] {
|
|
2949
|
-
box-shadow: 0 0 0 2px ${t.focus.color};
|
|
2950
|
-
}
|
|
2951
|
-
|
|
2952
|
-
.dropdown {
|
|
2953
|
-
position: absolute;
|
|
2954
|
-
top: calc(100% + ${t.spacing["02"]});
|
|
2955
|
-
right: 0;
|
|
2956
|
-
width: 14rem;
|
|
2957
|
-
background: ${t.color.surface.defaultPrimaryGray};
|
|
2958
|
-
border: 1px solid ${t.color.border.defaultGray100};
|
|
2959
|
-
border-radius: ${t.radius.m};
|
|
2960
|
-
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.32);
|
|
2961
|
-
z-index: 9999;
|
|
2962
|
-
overflow: hidden;
|
|
2963
|
-
}
|
|
2964
|
-
|
|
2965
|
-
.preview {
|
|
2966
|
-
display: flex;
|
|
2967
|
-
align-items: center;
|
|
2968
|
-
gap: ${t.spacing["03"]};
|
|
2969
|
-
padding: ${t.spacing["03"]};
|
|
2970
|
-
border-bottom: 1px solid ${t.color.border.defaultGray100};
|
|
2971
|
-
}
|
|
2972
|
-
.preview-avatar {
|
|
2973
|
-
flex-shrink: 0;
|
|
2974
|
-
width: 2.5rem;
|
|
2975
|
-
height: 2.5rem;
|
|
2976
|
-
border-radius: 9999px;
|
|
2977
|
-
background: ${t.color.surface.defaultWhite};
|
|
2978
|
-
color: ${t.color.text.buttonDefault};
|
|
2979
|
-
font-size: 0.875rem;
|
|
2980
|
-
font-weight: 600;
|
|
2981
|
-
display: inline-flex;
|
|
2982
|
-
align-items: center;
|
|
2983
|
-
justify-content: center;
|
|
2984
|
-
}
|
|
2985
|
-
.preview-info {
|
|
2986
|
-
min-width: 0;
|
|
2987
|
-
flex: 1;
|
|
2988
|
-
}
|
|
2989
|
-
.preview-name {
|
|
2990
|
-
font-size: 0.875rem;
|
|
2991
|
-
font-weight: 600;
|
|
2992
|
-
color: ${t.color.text.primaryWhite};
|
|
2993
|
-
white-space: nowrap;
|
|
2994
|
-
overflow: hidden;
|
|
2995
|
-
text-overflow: ellipsis;
|
|
2996
|
-
}
|
|
2997
|
-
.preview-email {
|
|
2998
|
-
font-size: 0.75rem;
|
|
2999
|
-
color: ${t.color.text.secondaryGray};
|
|
3000
|
-
white-space: nowrap;
|
|
3001
|
-
overflow: hidden;
|
|
3002
|
-
text-overflow: ellipsis;
|
|
3003
|
-
margin-top: 2px;
|
|
3004
|
-
}
|
|
3005
|
-
|
|
3006
|
-
.actions {
|
|
3007
|
-
padding: ${t.spacing["02"]};
|
|
3008
|
-
}
|
|
3009
|
-
.signout-btn {
|
|
3010
|
-
all: unset;
|
|
3011
|
-
cursor: pointer;
|
|
3012
|
-
display: flex;
|
|
3013
|
-
align-items: center;
|
|
3014
|
-
gap: ${t.spacing["02"]};
|
|
3015
|
-
width: 100%;
|
|
3016
|
-
padding: ${t.spacing["02"]} ${t.spacing["03"]};
|
|
3017
|
-
border-radius: ${t.radius.s};
|
|
3018
|
-
color: ${t.color.text.error};
|
|
3019
|
-
font-size: 0.875rem;
|
|
3020
|
-
font-weight: 500;
|
|
3021
|
-
box-sizing: border-box;
|
|
3022
|
-
}
|
|
3023
|
-
.signout-btn:hover:not([disabled]) {
|
|
3024
|
-
background: color-mix(in srgb, ${t.color.text.error} 12%, transparent);
|
|
3025
|
-
}
|
|
3026
|
-
.signout-btn:focus-visible {
|
|
3027
|
-
${focusVisibleStyles};
|
|
3028
|
-
}
|
|
3029
|
-
.signout-btn[disabled] {
|
|
3030
|
-
cursor: not-allowed;
|
|
3031
|
-
opacity: 0.6;
|
|
3032
|
-
}
|
|
3033
|
-
.signout-btn svg {
|
|
3034
|
-
flex-shrink: 0;
|
|
3035
|
-
}
|
|
3036
|
-
|
|
3037
|
-
.spinner {
|
|
3038
|
-
width: 1em;
|
|
3039
|
-
height: 1em;
|
|
3040
|
-
border-radius: 9999px;
|
|
3041
|
-
border: 2px solid currentColor;
|
|
3042
|
-
border-top-color: transparent;
|
|
3043
|
-
animation: zl-logout-spin 600ms linear infinite;
|
|
3044
|
-
}
|
|
3045
|
-
@keyframes zl-logout-spin {
|
|
3046
|
-
to {
|
|
3047
|
-
transform: rotate(360deg);
|
|
3048
|
-
}
|
|
3049
|
-
}
|
|
3050
|
-
|
|
3051
|
-
.error-bar {
|
|
3052
|
-
padding: ${t.spacing["02"]} ${t.spacing["03"]};
|
|
3053
|
-
font-size: 0.75rem;
|
|
3054
|
-
color: ${t.color.text.error};
|
|
3055
|
-
background: color-mix(in srgb, ${t.color.text.error} 12%, transparent);
|
|
3056
|
-
border-top: 1px solid ${t.color.border.defaultGray100};
|
|
3057
|
-
}
|
|
3058
|
-
`];
|
|
3059
|
-
#_project_accessor_storage;
|
|
3060
|
-
get project() {
|
|
3061
|
-
return this.#_project_accessor_storage;
|
|
3062
|
-
}
|
|
3063
|
-
set project(value) {
|
|
3064
|
-
this.#_project_accessor_storage = value;
|
|
3065
|
-
}
|
|
3066
|
-
#_projectId_accessor_storage = "";
|
|
3067
|
-
get projectId() {
|
|
3068
|
-
return this.#_projectId_accessor_storage;
|
|
3069
|
-
}
|
|
3070
|
-
set projectId(value) {
|
|
3071
|
-
this.#_projectId_accessor_storage = value;
|
|
3072
|
-
}
|
|
3073
|
-
#_proxyPath_accessor_storage = "";
|
|
3074
|
-
get proxyPath() {
|
|
3075
|
-
return this.#_proxyPath_accessor_storage;
|
|
3076
|
-
}
|
|
3077
|
-
set proxyPath(value) {
|
|
3078
|
-
this.#_proxyPath_accessor_storage = value;
|
|
3079
|
-
}
|
|
3080
|
-
#_url_accessor_storage = "";
|
|
3081
|
-
get url() {
|
|
3082
|
-
return this.#_url_accessor_storage;
|
|
3083
|
-
}
|
|
3084
|
-
set url(value) {
|
|
3085
|
-
this.#_url_accessor_storage = value;
|
|
3086
|
-
}
|
|
3087
|
-
#_postSignOutUrl_accessor_storage = "";
|
|
3088
|
-
get postSignOutUrl() {
|
|
3089
|
-
return this.#_postSignOutUrl_accessor_storage;
|
|
3090
|
-
}
|
|
3091
|
-
set postSignOutUrl(value) {
|
|
3092
|
-
this.#_postSignOutUrl_accessor_storage = value;
|
|
3093
|
-
}
|
|
3094
|
-
#_clientId_accessor_storage = "";
|
|
3095
|
-
get clientId() {
|
|
3096
|
-
return this.#_clientId_accessor_storage;
|
|
3097
|
-
}
|
|
3098
|
-
set clientId(value) {
|
|
3099
|
-
this.#_clientId_accessor_storage = value;
|
|
3100
|
-
}
|
|
3101
|
-
#_displayName_accessor_storage = "";
|
|
3102
|
-
get displayName() {
|
|
3103
|
-
return this.#_displayName_accessor_storage;
|
|
3104
|
-
}
|
|
3105
|
-
set displayName(value) {
|
|
3106
|
-
this.#_displayName_accessor_storage = value;
|
|
3107
|
-
}
|
|
3108
|
-
#_displayEmail_accessor_storage = "";
|
|
3109
|
-
get displayEmail() {
|
|
3110
|
-
return this.#_displayEmail_accessor_storage;
|
|
3111
|
-
}
|
|
3112
|
-
set displayEmail(value) {
|
|
3113
|
-
this.#_displayEmail_accessor_storage = value;
|
|
3114
|
-
}
|
|
3115
|
-
#_open_accessor_storage = false;
|
|
3116
|
-
get open() {
|
|
3117
|
-
return this.#_open_accessor_storage;
|
|
3118
|
-
}
|
|
3119
|
-
set open(value) {
|
|
3120
|
-
this.#_open_accessor_storage = value;
|
|
3121
|
-
}
|
|
3122
|
-
#_loading_accessor_storage = false;
|
|
3123
|
-
get loading() {
|
|
3124
|
-
return this.#_loading_accessor_storage;
|
|
3125
|
-
}
|
|
3126
|
-
set loading(value) {
|
|
3127
|
-
this.#_loading_accessor_storage = value;
|
|
3128
|
-
}
|
|
3129
|
-
#_errorMessage_accessor_storage = "";
|
|
3130
|
-
get errorMessage() {
|
|
3131
|
-
return this.#_errorMessage_accessor_storage;
|
|
3132
|
-
}
|
|
3133
|
-
set errorMessage(value) {
|
|
3134
|
-
this.#_errorMessage_accessor_storage = value;
|
|
3135
|
-
}
|
|
3136
|
-
templateMode = false;
|
|
3137
|
-
connectedCallback() {
|
|
3138
|
-
super.connectedCallback();
|
|
3139
|
-
this.dataset.theme = "dark";
|
|
3140
|
-
this.readDisplayCookie();
|
|
3141
|
-
const tmpl = this.querySelector("template");
|
|
3142
|
-
if (tmpl instanceof HTMLTemplateElement) {
|
|
3143
|
-
this.templateMode = true;
|
|
3144
|
-
this.renderTemplate(tmpl);
|
|
3145
|
-
}
|
|
3146
|
-
document.addEventListener("click", this.handleDocumentClick);
|
|
3147
|
-
document.addEventListener("keydown", this.handleDocumentKeydown);
|
|
3148
|
-
}
|
|
3149
|
-
disconnectedCallback() {
|
|
3150
|
-
super.disconnectedCallback();
|
|
3151
|
-
document.removeEventListener("click", this.handleDocumentClick);
|
|
3152
|
-
document.removeEventListener("keydown", this.handleDocumentKeydown);
|
|
3153
|
-
}
|
|
3154
|
-
updated() {
|
|
3155
|
-
const root = this.shadowRoot;
|
|
3156
|
-
if (root && !this.templateMode) applyBaseTokens(root);
|
|
3157
|
-
}
|
|
3158
|
-
/**
|
|
3159
|
-
* Decodes the `__nextgen_display` cookie (base64-encoded JSON, set by the
|
|
3160
|
-
* auth backend on sign-in) and populates `displayName` / `displayEmail`.
|
|
3161
|
-
* A missing or malformed cookie is intentionally non-fatal — the dropdown
|
|
3162
|
-
* still renders, just with empty values.
|
|
3163
|
-
*/
|
|
3164
|
-
readDisplayCookie() {
|
|
3165
|
-
if (typeof document === "undefined") return;
|
|
3166
|
-
const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${DISPLAY_COOKIE_NAME}=([^;]+)`));
|
|
3167
|
-
if (!match || !match[1]) return;
|
|
3168
|
-
try {
|
|
3169
|
-
const data = JSON.parse(atob(match[1]));
|
|
3170
|
-
this.displayName = typeof data.name === "string" ? data.name : "";
|
|
3171
|
-
this.displayEmail = typeof data.email === "string" ? data.email : "";
|
|
3172
|
-
} catch {}
|
|
3173
|
-
}
|
|
3174
|
-
get initial() {
|
|
3175
|
-
const source = this.displayName || this.displayEmail;
|
|
3176
|
-
return source ? source.charAt(0).toUpperCase() : "?";
|
|
3177
|
-
}
|
|
3178
|
-
/**
|
|
3179
|
-
* Clones a consumer-supplied `<template>` into the light DOM, fills the
|
|
3180
|
-
* `{{name}}`, `{{email}}`, and `{{initial}}` tokens via a TreeWalker, and
|
|
3181
|
-
* wires every element with `data-action="logout"` to trigger sign-out.
|
|
3182
|
-
* Light-DOM mounting is deliberate so the consumer's existing CSS applies.
|
|
3183
|
-
*/
|
|
3184
|
-
renderTemplate(tmpl) {
|
|
3185
|
-
const clone = tmpl.content.cloneNode(true);
|
|
3186
|
-
fillTemplateTokens(clone, this.displayName, this.displayEmail, this.initial);
|
|
3187
|
-
const container = document.createElement("span");
|
|
3188
|
-
container.appendChild(clone);
|
|
3189
|
-
this.appendChild(container);
|
|
3190
|
-
container.querySelectorAll("[data-action=\"logout\"]").forEach((el) => {
|
|
3191
|
-
el.addEventListener("click", (event) => {
|
|
3192
|
-
event.preventDefault();
|
|
3193
|
-
this.doLogout();
|
|
3194
|
-
});
|
|
3195
|
-
});
|
|
3196
|
-
}
|
|
3197
|
-
handleDocumentClick = (event) => {
|
|
3198
|
-
if (!this.open) return;
|
|
3199
|
-
if (event.composedPath().includes(this)) return;
|
|
3200
|
-
this.open = false;
|
|
3201
|
-
};
|
|
3202
|
-
handleDocumentKeydown = (event) => {
|
|
3203
|
-
if (!this.open) return;
|
|
3204
|
-
if (event.key !== "Escape") return;
|
|
3205
|
-
this.open = false;
|
|
3206
|
-
this.shadowRoot?.querySelector(".trigger")?.focus();
|
|
3207
|
-
};
|
|
3208
|
-
toggleOpen() {
|
|
3209
|
-
this.open = !this.open;
|
|
3210
|
-
this.errorMessage = "";
|
|
3211
|
-
}
|
|
3212
|
-
/**
|
|
3213
|
-
* Calls `DELETE /sessions/me` (`revokeMySession`) with `credentials: "include"`.
|
|
3214
|
-
* The server validates the `__nextgen_session` cookie, deletes the session, and
|
|
3215
|
-
* clears the cookie via `Set-Cookie: Max-Age=0`. On success this element fires
|
|
3216
|
-
* `zitadel-signout` and optionally navigates to `postSignOutUrl`.
|
|
3217
|
-
*/
|
|
3218
|
-
/** Declarative config read from this element's attributes. */
|
|
3219
|
-
get projectAttrs() {
|
|
3220
|
-
return {
|
|
3221
|
-
projectId: this.projectId,
|
|
3222
|
-
proxyPath: this.proxyPath,
|
|
3223
|
-
url: this.url
|
|
3224
|
-
};
|
|
3225
|
-
}
|
|
3226
|
-
async doLogout() {
|
|
3227
|
-
this.loading = true;
|
|
3228
|
-
this.errorMessage = "";
|
|
3229
|
-
try {
|
|
3230
|
-
const { api } = resolveApi(this.project, this.projectAttrs, "<zitadel-logout>");
|
|
3231
|
-
await api.revokeMySession({ credentials: "include" });
|
|
3232
|
-
} catch (error) {
|
|
3233
|
-
const message = error instanceof Error ? error.message : "";
|
|
3234
|
-
this.errorMessage = message || "Sign-out failed. Please try again.";
|
|
3235
|
-
this.loading = false;
|
|
3236
|
-
return;
|
|
3237
|
-
}
|
|
3238
|
-
this.open = false;
|
|
3239
|
-
this.loading = false;
|
|
3240
|
-
emit(this, "zitadel-signout", {
|
|
3241
|
-
name: this.displayName,
|
|
3242
|
-
email: this.displayEmail
|
|
3243
|
-
});
|
|
3244
|
-
if (this.postSignOutUrl && typeof window !== "undefined") window.location.href = this.postSignOutUrl;
|
|
3245
|
-
}
|
|
3246
|
-
/**
|
|
3247
|
-
* Returns the absolute URL the end-session request will hit. Useful for
|
|
3248
|
-
* test assertions and for consumers that prefer to navigate the browser
|
|
3249
|
-
* directly (instead of fetching) so the OIDC session-end redirect is
|
|
3250
|
-
* driven by the user agent.
|
|
3251
|
-
*/
|
|
3252
|
-
getEndSessionUrl() {
|
|
3253
|
-
const params = {
|
|
3254
|
-
...this.clientId ? { client_id: this.clientId } : {},
|
|
3255
|
-
...this.postSignOutUrl ? { post_logout_redirect_uri: this.postSignOutUrl } : {}
|
|
3256
|
-
};
|
|
3257
|
-
const { api } = resolveApi(this.project, this.projectAttrs, "<zitadel-logout>");
|
|
3258
|
-
return api.getEndSessionUrl(params);
|
|
3259
|
-
}
|
|
3260
|
-
handleSignOutClick(event) {
|
|
3261
|
-
event.preventDefault();
|
|
3262
|
-
this.doLogout();
|
|
3263
|
-
}
|
|
3264
|
-
render() {
|
|
3265
|
-
if (this.templateMode) return nothing;
|
|
3266
|
-
return html`
|
|
3267
|
-
<button
|
|
3268
|
-
class="trigger"
|
|
3269
|
-
type="button"
|
|
3270
|
-
aria-label=${this.open ? "Close user menu" : "Open user menu"}
|
|
3271
|
-
aria-expanded=${this.open ? "true" : "false"}
|
|
3272
|
-
aria-haspopup="dialog"
|
|
3273
|
-
@click=${this.toggleOpen}
|
|
3274
|
-
>
|
|
3275
|
-
${this.initial}
|
|
3276
|
-
</button>
|
|
3277
|
-
|
|
3278
|
-
${this.open ? html`
|
|
3279
|
-
<div class="dropdown" role="dialog" aria-label="User menu">
|
|
3280
|
-
<div class="preview">
|
|
3281
|
-
<div class="preview-avatar" aria-hidden="true">${this.initial}</div>
|
|
3282
|
-
<div class="preview-info">
|
|
3283
|
-
<div class="preview-name">${this.displayName || this.displayEmail}</div>
|
|
3284
|
-
${this.displayName ? html`<div class="preview-email">${this.displayEmail}</div>` : nothing}
|
|
3285
|
-
</div>
|
|
3286
|
-
</div>
|
|
3287
|
-
|
|
3288
|
-
<div class="actions">
|
|
3289
|
-
<button
|
|
3290
|
-
class="signout-btn"
|
|
3291
|
-
type="button"
|
|
3292
|
-
?disabled=${this.loading}
|
|
3293
|
-
@click=${this.handleSignOutClick}
|
|
3294
|
-
>
|
|
3295
|
-
${this.loading ? html`<span class="spinner" aria-hidden="true"></span>` : html`
|
|
3296
|
-
<svg
|
|
3297
|
-
width="14"
|
|
3298
|
-
height="14"
|
|
3299
|
-
viewBox="0 0 24 24"
|
|
3300
|
-
fill="none"
|
|
3301
|
-
stroke="currentColor"
|
|
3302
|
-
stroke-width="2"
|
|
3303
|
-
stroke-linecap="round"
|
|
3304
|
-
stroke-linejoin="round"
|
|
3305
|
-
aria-hidden="true"
|
|
3306
|
-
>
|
|
3307
|
-
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
|
3308
|
-
<polyline points="16 17 21 12 16 7" />
|
|
3309
|
-
<line x1="21" y1="12" x2="9" y2="12" />
|
|
3310
|
-
</svg>
|
|
3311
|
-
`}
|
|
3312
|
-
<span>${this.loading ? "Signing out…" : "Sign out"}</span>
|
|
3313
|
-
</button>
|
|
3314
|
-
</div>
|
|
3315
|
-
|
|
3316
|
-
${this.errorMessage ? html`<div class="error-bar" role="alert">${this.errorMessage}</div>` : nothing}
|
|
3317
|
-
</div>
|
|
3318
|
-
` : nothing}
|
|
3319
|
-
`;
|
|
3320
|
-
}
|
|
3321
|
-
};
|
|
3322
|
-
__decorate([property({ attribute: false })], ZitadelLogout.prototype, "project", null);
|
|
3323
|
-
__decorate([property({
|
|
3324
|
-
type: String,
|
|
3325
|
-
attribute: "project-id"
|
|
3326
|
-
})], ZitadelLogout.prototype, "projectId", null);
|
|
3327
|
-
__decorate([property({
|
|
3328
|
-
type: String,
|
|
3329
|
-
attribute: "proxy-path"
|
|
3330
|
-
})], ZitadelLogout.prototype, "proxyPath", null);
|
|
3331
|
-
__decorate([property({ type: String })], ZitadelLogout.prototype, "url", null);
|
|
3332
|
-
__decorate([property({
|
|
3333
|
-
type: String,
|
|
3334
|
-
attribute: "post-sign-out-url"
|
|
3335
|
-
})], ZitadelLogout.prototype, "postSignOutUrl", null);
|
|
3336
|
-
__decorate([property({
|
|
3337
|
-
type: String,
|
|
3338
|
-
attribute: "client-id"
|
|
3339
|
-
})], ZitadelLogout.prototype, "clientId", null);
|
|
3340
|
-
__decorate([state()], ZitadelLogout.prototype, "displayName", null);
|
|
3341
|
-
__decorate([state()], ZitadelLogout.prototype, "displayEmail", null);
|
|
3342
|
-
__decorate([state()], ZitadelLogout.prototype, "open", null);
|
|
3343
|
-
__decorate([state()], ZitadelLogout.prototype, "loading", null);
|
|
3344
|
-
__decorate([state()], ZitadelLogout.prototype, "errorMessage", null);
|
|
3345
|
-
ZitadelLogout = __decorate([customElement("zitadel-logout")], ZitadelLogout);
|
|
3346
|
-
/**
|
|
3347
|
-
* Substitutes `{{name}}`, `{{email}}`, and `{{initial}}` placeholders inside
|
|
3348
|
-
* a fragment's text nodes. Walking text nodes (rather than running a regex
|
|
3349
|
-
* over `outerHTML`) keeps attributes and structural markup untouched.
|
|
3350
|
-
*/
|
|
3351
|
-
function fillTemplateTokens(fragment, name, email, initial) {
|
|
3352
|
-
const walker = document.createTreeWalker(fragment, NodeFilter.SHOW_TEXT);
|
|
3353
|
-
let node = walker.nextNode();
|
|
3354
|
-
while (node) {
|
|
3355
|
-
if (node.textContent) node.textContent = node.textContent.replace(/\{\{name\}\}/g, name).replace(/\{\{email\}\}/g, email).replace(/\{\{initial\}\}/g, initial);
|
|
3356
|
-
node = walker.nextNode();
|
|
3357
|
-
}
|
|
3358
|
-
}
|
|
3359
|
-
//#endregion
|
|
3360
|
-
export { buildBrandingStylesheet as _, createSanitiser as a, startFlow as b, de as c, MANDATORY_GATES_MARKER as d, mandatoryGatesMarkerComment as f, applyBrandingTokens as g, validateBranding as h, layout_chrome_default as i, en as l, applyFontUrl as m, ZitadelLogin as n, builtinLocales as o, patchMandatoryGates as p, ThemeController as r, it as s, ZitadelLogout as t, TEMPLATE_NAMES as u, resolveTheme as v, submitStep as x, getCurrentStep as y };
|
|
3361
|
-
|
|
3362
|
-
//# sourceMappingURL=orchestrator-CW9zixuw.mjs.map
|