@zitadel/components 0.0.0

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