@zitadel/components 0.1.0-alpha.17 → 0.1.0-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -19
- package/dist/atoms/index.d.mts +1 -1
- package/dist/atoms/index.mjs +1 -1
- package/dist/{atoms-KC7OJWXZ.mjs → atoms-5-wYIl88.mjs} +263 -188
- package/dist/atoms-5-wYIl88.mjs.map +1 -0
- package/dist/default-Bnjy2dJB.mjs +6 -0
- package/dist/default-Bnjy2dJB.mjs.map +1 -0
- package/dist/{index-BniwXaIC.d.mts → index-BRGZPZ2y.d.mts} +111 -1
- package/dist/index-BRGZPZ2y.d.mts.map +1 -0
- package/dist/{index-DjYJwejg.d.mts → index-CsJiPZtJ.d.mts} +437 -209
- package/dist/index-CsJiPZtJ.d.mts.map +1 -0
- package/dist/{index-DSZnfOJH.d.mts → index-DIGjtG4z.d.mts} +1626 -1017
- package/dist/index-DIGjtG4z.d.mts.map +1 -0
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +5 -5
- package/dist/jsx.d.ts +63 -0
- package/dist/manifests.mjs +1 -1
- package/dist/orchestrator/index.d.mts +3 -3
- package/dist/orchestrator/index.mjs +3 -3
- package/dist/{orchestrator-BWqJw4Yl.mjs → orchestrator-CctLkpmB.mjs} +1195 -764
- package/dist/orchestrator-CctLkpmB.mjs.map +1 -0
- package/dist/standalone.mjs +2484 -1284
- package/dist/tokens/index.d.mts +1 -1
- package/dist/tokens/index.mjs +1 -1
- package/dist/tokens-BV-PPbQd.mjs +356 -0
- package/dist/tokens-BV-PPbQd.mjs.map +1 -0
- package/package.json +14 -10
- package/dist/atoms-KC7OJWXZ.mjs.map +0 -1
- package/dist/default-CfF7GoLv.mjs +0 -6
- package/dist/default-CfF7GoLv.mjs.map +0 -1
- package/dist/index-BniwXaIC.d.mts.map +0 -1
- package/dist/index-DSZnfOJH.d.mts.map +0 -1
- package/dist/index-DjYJwejg.d.mts.map +0 -1
- package/dist/orchestrator-BWqJw4Yl.mjs.map +0 -1
- package/dist/tokens-CbH2s1jV.mjs +0 -246
- package/dist/tokens-CbH2s1jV.mjs.map +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { C as focusVisibleStyles, E as emit, S as __decorate, T as t, d as hookName, w as baseHostStyles } from "./atoms-
|
|
2
|
-
import { r as tokensCss } from "./tokens-
|
|
3
|
-
import { t as default_default } from "./default-CfF7GoLv.mjs";
|
|
1
|
+
import { C as focusVisibleStyles, E as emit, S as __decorate, T as t, d as hookName, w as baseHostStyles } from "./atoms-5-wYIl88.mjs";
|
|
2
|
+
import { r as tokensCss } from "./tokens-BV-PPbQd.mjs";
|
|
4
3
|
import { manifestRegistry } from "./manifests.mjs";
|
|
4
|
+
import { t as default_default } from "./default-Bnjy2dJB.mjs";
|
|
5
5
|
import { LitElement, css, html, nothing } from "lit";
|
|
6
6
|
import { customElement, property, state } from "lit/decorators.js";
|
|
7
7
|
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
8
|
+
import { MANDATORY_GATES_TAG } from "@zitadel/config/template";
|
|
8
9
|
import { Liquid } from "liquidjs";
|
|
9
10
|
import DOMPurify from "dompurify";
|
|
10
11
|
//#region ../api/dist/runtime/auth.mjs
|
|
@@ -204,10 +205,9 @@ var __exportAll = (all, no_symbols) => {
|
|
|
204
205
|
* OpenAPI spec version: 0.0.1
|
|
205
206
|
*/
|
|
206
207
|
var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
|
|
207
|
-
|
|
208
|
-
authorizeDevice: () => authorizeDevice,
|
|
209
|
-
authorizeGet: () => authorizeGet,
|
|
208
|
+
completeClaim: () => completeClaim,
|
|
210
209
|
createAuthAttempt: () => createAuthAttempt,
|
|
210
|
+
createBranding: () => createBranding,
|
|
211
211
|
createFlow: () => createFlow,
|
|
212
212
|
createFlowDefinition: () => createFlowDefinition,
|
|
213
213
|
createHandoff: () => createHandoff,
|
|
@@ -216,15 +216,16 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
|
|
|
216
216
|
createSession: () => createSession,
|
|
217
217
|
createTeam: () => createTeam,
|
|
218
218
|
createUser: () => createUser,
|
|
219
|
-
deactivateFlowDefinition: () => deactivateFlowDefinition,
|
|
220
219
|
deleteFlowDefinition: () => deleteFlowDefinition,
|
|
221
|
-
|
|
220
|
+
deleteTeam: () => deleteTeam,
|
|
221
|
+
deleteUserByID: () => deleteUserByID,
|
|
222
222
|
exchangeHandoff: () => exchangeHandoff,
|
|
223
|
-
getActivateFlowDefinitionUrl: () => getActivateFlowDefinitionUrl,
|
|
224
223
|
getAuthAttempt: () => getAuthAttempt,
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
getBrandingById: () => getBrandingById,
|
|
225
|
+
getClaimStatus: () => getClaimStatus,
|
|
226
|
+
getCompleteClaimUrl: () => getCompleteClaimUrl,
|
|
227
227
|
getCreateAuthAttemptUrl: () => getCreateAuthAttemptUrl,
|
|
228
|
+
getCreateBrandingUrl: () => getCreateBrandingUrl,
|
|
228
229
|
getCreateFlowDefinitionUrl: () => getCreateFlowDefinitionUrl,
|
|
229
230
|
getCreateFlowUrl: () => getCreateFlowUrl,
|
|
230
231
|
getCreateHandoffUrl: () => getCreateHandoffUrl,
|
|
@@ -233,230 +234,76 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
|
|
|
233
234
|
getCreateSessionUrl: () => getCreateSessionUrl,
|
|
234
235
|
getCreateTeamUrl: () => getCreateTeamUrl,
|
|
235
236
|
getCreateUserUrl: () => getCreateUserUrl,
|
|
236
|
-
getDeactivateFlowDefinitionUrl: () => getDeactivateFlowDefinitionUrl,
|
|
237
237
|
getDeleteFlowDefinitionUrl: () => getDeleteFlowDefinitionUrl,
|
|
238
|
-
|
|
238
|
+
getDeleteTeamUrl: () => getDeleteTeamUrl,
|
|
239
|
+
getDeleteUserByIDUrl: () => getDeleteUserByIDUrl,
|
|
239
240
|
getExchangeHandoffUrl: () => getExchangeHandoffUrl,
|
|
240
241
|
getFlowDefinition: () => getFlowDefinition,
|
|
241
242
|
getFlowStep: () => getFlowStep,
|
|
242
243
|
getGetAuthAttemptUrl: () => getGetAuthAttemptUrl,
|
|
244
|
+
getGetBrandingByIdUrl: () => getGetBrandingByIdUrl,
|
|
245
|
+
getGetClaimStatusUrl: () => getGetClaimStatusUrl,
|
|
243
246
|
getGetFlowDefinitionUrl: () => getGetFlowDefinitionUrl,
|
|
244
247
|
getGetFlowStepUrl: () => getGetFlowStepUrl,
|
|
245
248
|
getGetHealthUrl: () => getGetHealthUrl,
|
|
246
|
-
getGetKeysUrl: () => getGetKeysUrl,
|
|
247
249
|
getGetLiveUrl: () => getGetLiveUrl,
|
|
248
250
|
getGetMySessionUrl: () => getGetMySessionUrl,
|
|
249
251
|
getGetMyUserUrl: () => getGetMyUserUrl,
|
|
250
|
-
getGetOpenIDConfigurationUrl: () => getGetOpenIDConfigurationUrl,
|
|
251
252
|
getGetProjectUrl: () => getGetProjectUrl,
|
|
252
253
|
getGetReadyUrl: () => getGetReadyUrl,
|
|
253
254
|
getGetSchemaByIdUrl: () => getGetSchemaByIdUrl,
|
|
254
255
|
getGetSessionUrl: () => getGetSessionUrl,
|
|
255
256
|
getGetTeamUrl: () => getGetTeamUrl,
|
|
256
|
-
getGetTokenUrl: () => getGetTokenUrl,
|
|
257
257
|
getGetUserByIDUrl: () => getGetUserByIDUrl,
|
|
258
|
-
getGetUserInfoUrl: () => getGetUserInfoUrl,
|
|
259
258
|
getHealth: () => getHealth,
|
|
260
|
-
|
|
259
|
+
getInitClaimUrl: () => getInitClaimUrl,
|
|
261
260
|
getIssueChallengeUrl: () => getIssueChallengeUrl,
|
|
262
|
-
|
|
261
|
+
getListBrandingUrl: () => getListBrandingUrl,
|
|
263
262
|
getListFlowDefinitionsUrl: () => getListFlowDefinitionsUrl,
|
|
264
263
|
getListSchemasUrl: () => getListSchemasUrl,
|
|
265
|
-
|
|
264
|
+
getListUserPasskeysUrl: () => getListUserPasskeysUrl,
|
|
265
|
+
getListUserTeamsUrl: () => getListUserTeamsUrl,
|
|
266
266
|
getListUsersUrl: () => getListUsersUrl,
|
|
267
267
|
getLive: () => getLive,
|
|
268
268
|
getMySession: () => getMySession,
|
|
269
269
|
getMyUser: () => getMyUser,
|
|
270
|
-
getOpenIDConfiguration: () => getOpenIDConfiguration,
|
|
271
270
|
getPatchProjectUrl: () => getPatchProjectUrl,
|
|
272
271
|
getProject: () => getProject,
|
|
273
272
|
getQueryProjectsUrl: () => getQueryProjectsUrl,
|
|
273
|
+
getQuerySessionsUrl: () => getQuerySessionsUrl,
|
|
274
|
+
getQueryTeamsUrl: () => getQueryTeamsUrl,
|
|
274
275
|
getReady: () => getReady,
|
|
275
276
|
getRevokeMySessionUrl: () => getRevokeMySessionUrl,
|
|
276
277
|
getRevokeSessionUrl: () => getRevokeSessionUrl,
|
|
277
|
-
getRevokeTokenUrl: () => getRevokeTokenUrl,
|
|
278
278
|
getSchemaById: () => getSchemaById,
|
|
279
279
|
getSession: () => getSession,
|
|
280
280
|
getSetUserPasswordUrl: () => getSetUserPasswordUrl,
|
|
281
|
-
getSubmitFlowEventUrl: () => getSubmitFlowEventUrl,
|
|
282
281
|
getSubmitFlowStepUrl: () => getSubmitFlowStepUrl,
|
|
283
282
|
getTeam: () => getTeam,
|
|
284
|
-
getToken: () => getToken,
|
|
285
283
|
getUpdateFlowDefinitionUrl: () => getUpdateFlowDefinitionUrl,
|
|
284
|
+
getUpdateTeamUrl: () => getUpdateTeamUrl,
|
|
286
285
|
getUserByID: () => getUserByID,
|
|
287
|
-
getUserInfo: () => getUserInfo,
|
|
288
286
|
getVerifyChallengeProofUrl: () => getVerifyChallengeProofUrl,
|
|
289
|
-
|
|
287
|
+
initClaim: () => initClaim,
|
|
290
288
|
issueChallenge: () => issueChallenge,
|
|
289
|
+
listBranding: () => listBranding,
|
|
291
290
|
listFlowDefinitions: () => listFlowDefinitions,
|
|
292
291
|
listSchemas: () => listSchemas,
|
|
293
|
-
|
|
292
|
+
listUserPasskeys: () => listUserPasskeys,
|
|
293
|
+
listUserTeams: () => listUserTeams,
|
|
294
294
|
listUsers: () => listUsers,
|
|
295
295
|
patchProject: () => patchProject,
|
|
296
296
|
queryProjects: () => queryProjects,
|
|
297
|
+
querySessions: () => querySessions,
|
|
298
|
+
queryTeams: () => queryTeams,
|
|
297
299
|
revokeMySession: () => revokeMySession,
|
|
298
300
|
revokeSession: () => revokeSession,
|
|
299
|
-
revokeToken: () => revokeToken,
|
|
300
301
|
setUserPassword: () => setUserPassword,
|
|
301
|
-
submitFlowEvent: () => submitFlowEvent,
|
|
302
302
|
submitFlowStep: () => submitFlowStep,
|
|
303
303
|
updateFlowDefinition: () => updateFlowDefinition,
|
|
304
|
+
updateTeam: () => updateTeam,
|
|
304
305
|
verifyChallengeProof: () => verifyChallengeProof
|
|
305
306
|
});
|
|
306
|
-
const getGetOpenIDConfigurationUrl = () => {
|
|
307
|
-
return `${getProxyPath()}/.well-known/openid-configuration`;
|
|
308
|
-
};
|
|
309
|
-
/**
|
|
310
|
-
* Retrieve the OpenID Connect configuration
|
|
311
|
-
* @summary Get OpenID Connect configuration
|
|
312
|
-
*/
|
|
313
|
-
const getOpenIDConfiguration = async (options) => {
|
|
314
|
-
return customFetch(getGetOpenIDConfigurationUrl(), {
|
|
315
|
-
...options,
|
|
316
|
-
method: "GET"
|
|
317
|
-
});
|
|
318
|
-
};
|
|
319
|
-
const getAuthorizeGetUrl = (params) => {
|
|
320
|
-
const normalizedParams = new URLSearchParams();
|
|
321
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
322
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
323
|
-
});
|
|
324
|
-
const stringifiedParams = normalizedParams.toString();
|
|
325
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/auth/authorize?${stringifiedParams}` : `${getProxyPath()}/auth/authorize`;
|
|
326
|
-
};
|
|
327
|
-
/**
|
|
328
|
-
* @summary Authorize a user
|
|
329
|
-
*/
|
|
330
|
-
const authorizeGet = async (params, options) => {
|
|
331
|
-
return customFetch(getAuthorizeGetUrl(params), {
|
|
332
|
-
...options,
|
|
333
|
-
method: "GET"
|
|
334
|
-
});
|
|
335
|
-
};
|
|
336
|
-
const getAuthorizeDeviceUrl = (params) => {
|
|
337
|
-
const normalizedParams = new URLSearchParams();
|
|
338
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
339
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
340
|
-
});
|
|
341
|
-
const stringifiedParams = normalizedParams.toString();
|
|
342
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/auth/device-authorization?${stringifiedParams}` : `${getProxyPath()}/auth/device-authorization`;
|
|
343
|
-
};
|
|
344
|
-
/**
|
|
345
|
-
* @summary Authorize a device
|
|
346
|
-
*/
|
|
347
|
-
const authorizeDevice = async (params, options) => {
|
|
348
|
-
return customFetch(getAuthorizeDeviceUrl(params), {
|
|
349
|
-
...options,
|
|
350
|
-
method: "GET"
|
|
351
|
-
});
|
|
352
|
-
};
|
|
353
|
-
const getEndSessionUrl = (params) => {
|
|
354
|
-
const normalizedParams = new URLSearchParams();
|
|
355
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
356
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
357
|
-
});
|
|
358
|
-
const stringifiedParams = normalizedParams.toString();
|
|
359
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/auth/end-session?${stringifiedParams}` : `${getProxyPath()}/auth/end-session`;
|
|
360
|
-
};
|
|
361
|
-
/**
|
|
362
|
-
* @summary End a session
|
|
363
|
-
*/
|
|
364
|
-
const endSession = async (params, options) => {
|
|
365
|
-
return customFetch(getEndSessionUrl(params), {
|
|
366
|
-
...options,
|
|
367
|
-
method: "GET"
|
|
368
|
-
});
|
|
369
|
-
};
|
|
370
|
-
const getIntrospectUrl = () => {
|
|
371
|
-
return `${getProxyPath()}/auth/introspect`;
|
|
372
|
-
};
|
|
373
|
-
/**
|
|
374
|
-
* @summary Introspect a token
|
|
375
|
-
*/
|
|
376
|
-
const introspect = async (introspectBody, options) => {
|
|
377
|
-
const formUrlEncoded = new URLSearchParams();
|
|
378
|
-
formUrlEncoded.append(`token`, introspectBody.token);
|
|
379
|
-
if (introspectBody.token_type_hint !== void 0) formUrlEncoded.append(`token_type_hint`, introspectBody.token_type_hint);
|
|
380
|
-
return customFetch(getIntrospectUrl(), {
|
|
381
|
-
...options,
|
|
382
|
-
method: "POST",
|
|
383
|
-
headers: {
|
|
384
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
385
|
-
...options?.headers
|
|
386
|
-
},
|
|
387
|
-
body: formUrlEncoded
|
|
388
|
-
});
|
|
389
|
-
};
|
|
390
|
-
const getGetKeysUrl = () => {
|
|
391
|
-
return `${getProxyPath()}/auth/keys`;
|
|
392
|
-
};
|
|
393
|
-
/**
|
|
394
|
-
* @summary Get public keys
|
|
395
|
-
*/
|
|
396
|
-
const getKeys = async (options) => {
|
|
397
|
-
return customFetch(getGetKeysUrl(), {
|
|
398
|
-
...options,
|
|
399
|
-
method: "GET"
|
|
400
|
-
});
|
|
401
|
-
};
|
|
402
|
-
const getRevokeTokenUrl = () => {
|
|
403
|
-
return `${getProxyPath()}/auth/revoke`;
|
|
404
|
-
};
|
|
405
|
-
/**
|
|
406
|
-
* @summary Revoke an access token or refresh token
|
|
407
|
-
*/
|
|
408
|
-
const revokeToken = async (revokeTokenBody, options) => {
|
|
409
|
-
const formUrlEncoded = new URLSearchParams();
|
|
410
|
-
if (revokeTokenBody.token !== void 0) formUrlEncoded.append(`token`, revokeTokenBody.token);
|
|
411
|
-
if (revokeTokenBody.token_type_hint !== void 0) formUrlEncoded.append(`token_type_hint`, revokeTokenBody.token_type_hint);
|
|
412
|
-
return customFetch(getRevokeTokenUrl(), {
|
|
413
|
-
...options,
|
|
414
|
-
method: "POST",
|
|
415
|
-
headers: {
|
|
416
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
417
|
-
...options?.headers
|
|
418
|
-
},
|
|
419
|
-
body: formUrlEncoded
|
|
420
|
-
});
|
|
421
|
-
};
|
|
422
|
-
const getGetTokenUrl = () => {
|
|
423
|
-
return `${getProxyPath()}/auth/token`;
|
|
424
|
-
};
|
|
425
|
-
/**
|
|
426
|
-
* @summary Get access token
|
|
427
|
-
*/
|
|
428
|
-
const getToken = async (getTokenBody, options) => {
|
|
429
|
-
const formUrlEncoded = new URLSearchParams();
|
|
430
|
-
if (getTokenBody.code !== void 0) formUrlEncoded.append(`code`, getTokenBody.code);
|
|
431
|
-
if (getTokenBody.client_assertion !== void 0) formUrlEncoded.append(`client_assertion`, getTokenBody.client_assertion);
|
|
432
|
-
if (getTokenBody.client_assertion_type !== void 0) formUrlEncoded.append(`client_assertion_type`, getTokenBody.client_assertion_type);
|
|
433
|
-
if (getTokenBody.client_id !== void 0) formUrlEncoded.append(`client_id`, getTokenBody.client_id);
|
|
434
|
-
if (getTokenBody.client_secret !== void 0) formUrlEncoded.append(`client_secret`, getTokenBody.client_secret);
|
|
435
|
-
if (getTokenBody.code_verifier !== void 0) formUrlEncoded.append(`code_verifier`, getTokenBody.code_verifier);
|
|
436
|
-
if (getTokenBody.grant_type !== void 0) formUrlEncoded.append(`grant_type`, getTokenBody.grant_type);
|
|
437
|
-
if (getTokenBody.redirect_uri !== void 0) formUrlEncoded.append(`redirect_uri`, getTokenBody.redirect_uri);
|
|
438
|
-
return customFetch(getGetTokenUrl(), {
|
|
439
|
-
...options,
|
|
440
|
-
method: "POST",
|
|
441
|
-
headers: {
|
|
442
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
443
|
-
...options?.headers
|
|
444
|
-
},
|
|
445
|
-
body: formUrlEncoded
|
|
446
|
-
});
|
|
447
|
-
};
|
|
448
|
-
const getGetUserInfoUrl = () => {
|
|
449
|
-
return `${getProxyPath()}/auth/userinfo`;
|
|
450
|
-
};
|
|
451
|
-
/**
|
|
452
|
-
* @summary Get user info
|
|
453
|
-
*/
|
|
454
|
-
const getUserInfo = async (options) => {
|
|
455
|
-
return customFetch(getGetUserInfoUrl(), {
|
|
456
|
-
...options,
|
|
457
|
-
method: "GET"
|
|
458
|
-
});
|
|
459
|
-
};
|
|
460
307
|
const getGetHealthUrl = () => {
|
|
461
308
|
return `${getProxyPath()}/healthz`;
|
|
462
309
|
};
|
|
@@ -552,6 +399,40 @@ const getUserByID = async (userId, params, options) => {
|
|
|
552
399
|
method: "GET"
|
|
553
400
|
});
|
|
554
401
|
};
|
|
402
|
+
const getDeleteUserByIDUrl = (userId, params) => {
|
|
403
|
+
const normalizedParams = new URLSearchParams();
|
|
404
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
405
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
406
|
+
});
|
|
407
|
+
const stringifiedParams = normalizedParams.toString();
|
|
408
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}?${stringifiedParams}` : `${getProxyPath()}/users/${userId}`;
|
|
409
|
+
};
|
|
410
|
+
/**
|
|
411
|
+
* @summary Delete user by ID
|
|
412
|
+
*/
|
|
413
|
+
const deleteUserByID = async (userId, params, options) => {
|
|
414
|
+
return customFetch(getDeleteUserByIDUrl(userId, params), {
|
|
415
|
+
...options,
|
|
416
|
+
method: "DELETE"
|
|
417
|
+
});
|
|
418
|
+
};
|
|
419
|
+
const getListUserPasskeysUrl = (userId, params) => {
|
|
420
|
+
const normalizedParams = new URLSearchParams();
|
|
421
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
422
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
423
|
+
});
|
|
424
|
+
const stringifiedParams = normalizedParams.toString();
|
|
425
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}/passkeys?${stringifiedParams}` : `${getProxyPath()}/users/${userId}/passkeys`;
|
|
426
|
+
};
|
|
427
|
+
/**
|
|
428
|
+
* @summary List user passkeys
|
|
429
|
+
*/
|
|
430
|
+
const listUserPasskeys = async (userId, params, options) => {
|
|
431
|
+
return customFetch(getListUserPasskeysUrl(userId, params), {
|
|
432
|
+
...options,
|
|
433
|
+
method: "GET"
|
|
434
|
+
});
|
|
435
|
+
};
|
|
555
436
|
const getSetUserPasswordUrl = (userId, params) => {
|
|
556
437
|
const normalizedParams = new URLSearchParams();
|
|
557
438
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
@@ -574,6 +455,31 @@ const setUserPassword = async (userId, setUserPasswordBody, params, options) =>
|
|
|
574
455
|
body: JSON.stringify(setUserPasswordBody)
|
|
575
456
|
});
|
|
576
457
|
};
|
|
458
|
+
const getListUserTeamsUrl = (userId, params) => {
|
|
459
|
+
const normalizedParams = new URLSearchParams();
|
|
460
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
461
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
462
|
+
});
|
|
463
|
+
const stringifiedParams = normalizedParams.toString();
|
|
464
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}/teams?${stringifiedParams}` : `${getProxyPath()}/users/${userId}/teams`;
|
|
465
|
+
};
|
|
466
|
+
/**
|
|
467
|
+
* The user's team roster, ordered by team name. Each entry carries the
|
|
468
|
+
team's name, so a client renders a page without resolving ids one by one.
|
|
469
|
+
|
|
470
|
+
This is the N:N roster and it is not lifecycle ownership: the single team
|
|
471
|
+
that owns the user's lifecycle is reported as `metadata.lifecycle_owner_team_id`
|
|
472
|
+
on the user read endpoints. Memberships the user was removed from are not
|
|
473
|
+
returned.
|
|
474
|
+
|
|
475
|
+
* @summary List the teams a user belongs to
|
|
476
|
+
*/
|
|
477
|
+
const listUserTeams = async (userId, params, options) => {
|
|
478
|
+
return customFetch(getListUserTeamsUrl(userId, params), {
|
|
479
|
+
...options,
|
|
480
|
+
method: "GET"
|
|
481
|
+
});
|
|
482
|
+
};
|
|
577
483
|
const getGetMyUserUrl = () => {
|
|
578
484
|
return `${getProxyPath()}/users/me`;
|
|
579
485
|
};
|
|
@@ -595,7 +501,7 @@ the first capability step. Creates a new session implicitly unless
|
|
|
595
501
|
`session_id` is provided (for step-up / reauth on an existing session).
|
|
596
502
|
|
|
597
503
|
The response contains an `id` field — the flow handle. Use it as the path
|
|
598
|
-
parameter for all subsequent `/flow/{id}/submit`
|
|
504
|
+
parameter for all subsequent `/flow/{id}/submit` calls.
|
|
599
505
|
|
|
600
506
|
The response also sets an encrypted `HttpOnly` cookie (`_zflow`) containing
|
|
601
507
|
the flow's orchestration state (current step, collected data, history).
|
|
@@ -675,26 +581,6 @@ const submitFlowStep = async (id, submitFlowStepBody, options) => {
|
|
|
675
581
|
body: JSON.stringify(submitFlowStepBody)
|
|
676
582
|
});
|
|
677
583
|
};
|
|
678
|
-
const getSubmitFlowEventUrl = (id) => {
|
|
679
|
-
return `${getProxyPath()}/flow/${id}/event`;
|
|
680
|
-
};
|
|
681
|
-
/**
|
|
682
|
-
* Submits telemetry or fingerprint data from the frontend.
|
|
683
|
-
Does not advance the state machine. Used for risk evaluation.
|
|
684
|
-
|
|
685
|
-
* @summary Submit client-side event
|
|
686
|
-
*/
|
|
687
|
-
const submitFlowEvent = async (id, submitFlowEventBody, options) => {
|
|
688
|
-
return customFetch(getSubmitFlowEventUrl(id), {
|
|
689
|
-
...options,
|
|
690
|
-
method: "POST",
|
|
691
|
-
headers: {
|
|
692
|
-
"Content-Type": "application/json",
|
|
693
|
-
...options?.headers
|
|
694
|
-
},
|
|
695
|
-
body: JSON.stringify(submitFlowEventBody)
|
|
696
|
-
});
|
|
697
|
-
};
|
|
698
584
|
const getCreateAuthAttemptUrl = () => {
|
|
699
585
|
return `${getProxyPath()}/auth_attempts`;
|
|
700
586
|
};
|
|
@@ -882,6 +768,68 @@ const patchProject = async (projectId, patchProjectBody, options) => {
|
|
|
882
768
|
body: JSON.stringify(patchProjectBody)
|
|
883
769
|
});
|
|
884
770
|
};
|
|
771
|
+
const getInitClaimUrl = (projectId) => {
|
|
772
|
+
return `${getProxyPath()}/projects/${projectId}/claim/init`;
|
|
773
|
+
};
|
|
774
|
+
/**
|
|
775
|
+
* Starts a claim challenge for an unclaimed project. Authenticated by the
|
|
776
|
+
project secret, this mints a single-use, short-lived challenge and returns
|
|
777
|
+
the `claim_url` the developer opens in a browser to complete the claim,
|
|
778
|
+
together with the `challenge_id` the CLI polls with. The exact expiry is
|
|
779
|
+
carried by `expires_at` on the response.
|
|
780
|
+
|
|
781
|
+
* @summary Initialize a project claim
|
|
782
|
+
*/
|
|
783
|
+
const initClaim = async (projectId, options) => {
|
|
784
|
+
return customFetch(getInitClaimUrl(projectId), {
|
|
785
|
+
...options,
|
|
786
|
+
method: "POST"
|
|
787
|
+
});
|
|
788
|
+
};
|
|
789
|
+
const getGetClaimStatusUrl = (projectId, params) => {
|
|
790
|
+
const normalizedParams = new URLSearchParams();
|
|
791
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
792
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
793
|
+
});
|
|
794
|
+
const stringifiedParams = normalizedParams.toString();
|
|
795
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/projects/${projectId}/claim/status?${stringifiedParams}` : `${getProxyPath()}/projects/${projectId}/claim/status`;
|
|
796
|
+
};
|
|
797
|
+
/**
|
|
798
|
+
* Polled by the CLI while a browser completes the claim. Authorized by the
|
|
799
|
+
project secret that initiated the challenge. Returns `pending`, or
|
|
800
|
+
`completed` with the owning team, the claim timestamp, and the dashboard
|
|
801
|
+
URL once the browser leg has finished.
|
|
802
|
+
|
|
803
|
+
* @summary Get claim status
|
|
804
|
+
*/
|
|
805
|
+
const getClaimStatus = async (projectId, params, options) => {
|
|
806
|
+
return customFetch(getGetClaimStatusUrl(projectId, params), {
|
|
807
|
+
...options,
|
|
808
|
+
method: "GET"
|
|
809
|
+
});
|
|
810
|
+
};
|
|
811
|
+
const getCompleteClaimUrl = (projectId) => {
|
|
812
|
+
return `${getProxyPath()}/projects/${projectId}/claim/complete`;
|
|
813
|
+
};
|
|
814
|
+
/**
|
|
815
|
+
* Called by the browser after the developer authenticates on the claim page.
|
|
816
|
+
Authenticated by the `__nextgen_session` cookie, it attaches the project to
|
|
817
|
+
the developer's personal team using the `challenge_id` from the claim URL as
|
|
818
|
+
its single-use, browser-safe authorization.
|
|
819
|
+
|
|
820
|
+
* @summary Complete a project claim
|
|
821
|
+
*/
|
|
822
|
+
const completeClaim = async (projectId, completeClaimBody, options) => {
|
|
823
|
+
return customFetch(getCompleteClaimUrl(projectId), {
|
|
824
|
+
...options,
|
|
825
|
+
method: "POST",
|
|
826
|
+
headers: {
|
|
827
|
+
"Content-Type": "application/json",
|
|
828
|
+
...options?.headers
|
|
829
|
+
},
|
|
830
|
+
body: JSON.stringify(completeClaimBody)
|
|
831
|
+
});
|
|
832
|
+
};
|
|
885
833
|
const getCreateSessionUrl = () => {
|
|
886
834
|
return `${getProxyPath()}/sessions`;
|
|
887
835
|
};
|
|
@@ -894,8 +842,16 @@ Use this explicitly when you want to:
|
|
|
894
842
|
can be correlated with the eventual authenticated session from the start.
|
|
895
843
|
- Track anonymous state (bot detection, device fingerprint) that survives until authentication.
|
|
896
844
|
|
|
897
|
-
|
|
898
|
-
|
|
845
|
+
Creating a session is an app-plane operation on the project credential
|
|
846
|
+
(`session.write`). The returned `session_token` is a session credential for the
|
|
847
|
+
end-user client, not a management scope: it is delivered as the
|
|
848
|
+
`__nextgen_session` cookie and authorises the self-service operations
|
|
849
|
+
`GET /sessions/me` and `DELETE /sessions/me` (`nextgenSession` scheme). The
|
|
850
|
+
by-id operations `GET /sessions/{session_id}` and `DELETE /sessions/{session_id}`
|
|
851
|
+
are operator endpoints and require `session.read` / `session.delete` instead.
|
|
852
|
+
|
|
853
|
+
The `session_token` is superseded when a handoff exchange completes — clients must
|
|
854
|
+
replace it at that point.
|
|
899
855
|
|
|
900
856
|
Anonymous sessions expire aggressively (10-minute TTL). The TTL resets to the configured
|
|
901
857
|
full session TTL when the first authentication factor is written via a completing `auth_attempt`.
|
|
@@ -913,24 +869,30 @@ const createSession = async (createSessionBody, options) => {
|
|
|
913
869
|
body: JSON.stringify(createSessionBody)
|
|
914
870
|
});
|
|
915
871
|
};
|
|
916
|
-
const
|
|
872
|
+
const getQuerySessionsUrl = (params) => {
|
|
917
873
|
const normalizedParams = new URLSearchParams();
|
|
918
874
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
919
875
|
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
920
876
|
});
|
|
921
877
|
const stringifiedParams = normalizedParams.toString();
|
|
922
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions?${stringifiedParams}` : `${getProxyPath()}/sessions`;
|
|
878
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions/query?${stringifiedParams}` : `${getProxyPath()}/sessions/query`;
|
|
923
879
|
};
|
|
924
880
|
/**
|
|
925
|
-
* Returns
|
|
926
|
-
|
|
881
|
+
* Returns the sessions of a project, paginated with a cursor.
|
|
882
|
+
Sessions of every lifecycle state are returned; each carries its `state`.
|
|
883
|
+
Requires `session.read` permission.
|
|
927
884
|
|
|
928
|
-
* @summary
|
|
885
|
+
* @summary Query sessions
|
|
929
886
|
*/
|
|
930
|
-
const
|
|
931
|
-
return customFetch(
|
|
887
|
+
const querySessions = async (querySessionsBody, params, options) => {
|
|
888
|
+
return customFetch(getQuerySessionsUrl(params), {
|
|
932
889
|
...options,
|
|
933
|
-
method: "
|
|
890
|
+
method: "POST",
|
|
891
|
+
headers: {
|
|
892
|
+
"Content-Type": "application/json",
|
|
893
|
+
...options?.headers
|
|
894
|
+
},
|
|
895
|
+
body: JSON.stringify(querySessionsBody)
|
|
934
896
|
});
|
|
935
897
|
};
|
|
936
898
|
const getExchangeHandoffUrl = (params) => {
|
|
@@ -1006,10 +968,15 @@ const getRevokeSessionUrl = (sessionId, params) => {
|
|
|
1006
968
|
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions/${sessionId}?${stringifiedParams}` : `${getProxyPath()}/sessions/${sessionId}`;
|
|
1007
969
|
};
|
|
1008
970
|
/**
|
|
1009
|
-
*
|
|
971
|
+
* Permanently deletes the session, terminating it immediately.
|
|
972
|
+
|
|
973
|
+
This is the operator revoke path and requires the `session.delete` scope on a
|
|
974
|
+
project-bound credential. End-user logout with the `__nextgen_session` cookie is
|
|
975
|
+
`DELETE /sessions/me` (`nextgenSession` scheme).
|
|
1010
976
|
|
|
1011
|
-
|
|
1012
|
-
After
|
|
977
|
+
Idempotent: deleting a session that does not exist (or was already deleted)
|
|
978
|
+
still returns 204. After deletion, any tokens derived from the session are
|
|
979
|
+
invalidated.
|
|
1013
980
|
|
|
1014
981
|
* @summary Revoke session
|
|
1015
982
|
*/
|
|
@@ -1042,10 +1009,12 @@ const getRevokeMySessionUrl = () => {
|
|
|
1042
1009
|
return `${getProxyPath()}/sessions/me`;
|
|
1043
1010
|
};
|
|
1044
1011
|
/**
|
|
1045
|
-
*
|
|
1012
|
+
* Logs out by permanently deleting the session.
|
|
1046
1013
|
|
|
1047
|
-
The __nextgen_session cookie issued at creation (or superseded by a handoff
|
|
1048
|
-
|
|
1014
|
+
The `__nextgen_session` cookie issued at creation (or superseded by a handoff
|
|
1015
|
+
exchange) is required. Idempotent: if the session is already gone this still
|
|
1016
|
+
returns 204. Any tokens derived from the session are invalidated, and the
|
|
1017
|
+
cookie itself is cleared in the response.
|
|
1049
1018
|
|
|
1050
1019
|
* @summary Revoke my session
|
|
1051
1020
|
*/
|
|
@@ -1064,10 +1033,12 @@ const getCreateSchemaUrl = (params) => {
|
|
|
1064
1033
|
return stringifiedParams.length > 0 ? `${getProxyPath()}/schemas?${stringifiedParams}` : `${getProxyPath()}/schemas`;
|
|
1065
1034
|
};
|
|
1066
1035
|
/**
|
|
1067
|
-
* Create a new schema. The
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1036
|
+
* Create a new schema. The optional `$id` field is the JSON Schema document
|
|
1037
|
+
URI used to identify the schema in future requests (GitOps-stable identity,
|
|
1038
|
+
not a free-form resource primary key). When `$id` is omitted, the server
|
|
1039
|
+
generates a `sch_*` URL. When provided, `$id` must be unique within the
|
|
1040
|
+
project and should ideally be a valid URI pointing at where the schema can
|
|
1041
|
+
be accessed.
|
|
1071
1042
|
|
|
1072
1043
|
The schema can either be a concrete schema, e.g. a user schema, or a
|
|
1073
1044
|
schema-url which will be resolved by the server.
|
|
@@ -1230,93 +1201,200 @@ const deleteFlowDefinition = async (id, params, options) => {
|
|
|
1230
1201
|
method: "DELETE"
|
|
1231
1202
|
});
|
|
1232
1203
|
};
|
|
1233
|
-
const
|
|
1204
|
+
const getCreateTeamUrl = (params) => {
|
|
1205
|
+
const normalizedParams = new URLSearchParams();
|
|
1206
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1207
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1208
|
+
});
|
|
1209
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1210
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams?${stringifiedParams}` : `${getProxyPath()}/teams`;
|
|
1211
|
+
};
|
|
1212
|
+
/**
|
|
1213
|
+
* @summary Create team
|
|
1214
|
+
*/
|
|
1215
|
+
const createTeam = async (createTeamBody, params, options) => {
|
|
1216
|
+
return customFetch(getCreateTeamUrl(params), {
|
|
1217
|
+
...options,
|
|
1218
|
+
method: "POST",
|
|
1219
|
+
headers: {
|
|
1220
|
+
"Content-Type": "application/json",
|
|
1221
|
+
...options?.headers
|
|
1222
|
+
},
|
|
1223
|
+
body: JSON.stringify(createTeamBody)
|
|
1224
|
+
});
|
|
1225
|
+
};
|
|
1226
|
+
const getQueryTeamsUrl = (params) => {
|
|
1234
1227
|
const normalizedParams = new URLSearchParams();
|
|
1235
1228
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1236
1229
|
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1237
1230
|
});
|
|
1238
1231
|
const stringifiedParams = normalizedParams.toString();
|
|
1239
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/
|
|
1232
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/query?${stringifiedParams}` : `${getProxyPath()}/teams/query`;
|
|
1240
1233
|
};
|
|
1241
1234
|
/**
|
|
1242
|
-
*
|
|
1243
|
-
|
|
1235
|
+
* Returns the teams of a project, paginated with a cursor.
|
|
1236
|
+
Teams of every lifecycle status are returned; each carries its `status`.
|
|
1244
1237
|
|
|
1245
|
-
* @summary
|
|
1238
|
+
* @summary Query teams
|
|
1246
1239
|
*/
|
|
1247
|
-
const
|
|
1248
|
-
return customFetch(
|
|
1240
|
+
const queryTeams = async (queryTeamsBody, params, options) => {
|
|
1241
|
+
return customFetch(getQueryTeamsUrl(params), {
|
|
1249
1242
|
...options,
|
|
1250
|
-
method: "POST"
|
|
1243
|
+
method: "POST",
|
|
1244
|
+
headers: {
|
|
1245
|
+
"Content-Type": "application/json",
|
|
1246
|
+
...options?.headers
|
|
1247
|
+
},
|
|
1248
|
+
body: JSON.stringify(queryTeamsBody)
|
|
1251
1249
|
});
|
|
1252
1250
|
};
|
|
1253
|
-
const
|
|
1251
|
+
const getGetTeamUrl = (teamId, params) => {
|
|
1254
1252
|
const normalizedParams = new URLSearchParams();
|
|
1255
1253
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1256
1254
|
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1257
1255
|
});
|
|
1258
1256
|
const stringifiedParams = normalizedParams.toString();
|
|
1259
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/
|
|
1257
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/${teamId}?${stringifiedParams}` : `${getProxyPath()}/teams/${teamId}`;
|
|
1260
1258
|
};
|
|
1261
1259
|
/**
|
|
1262
|
-
*
|
|
1263
|
-
Flow definitions in `draft` state cannot be used to start new flows. Existing flows that use the deactivated flow definition must gracefully handle this.
|
|
1264
|
-
Alternatively, the status of a flow definition can also be set via the `POST /flow_definitions` and `PUT /flow_definitions/{id}` endpoints by setting the `status` attribute in the flow definition payload.
|
|
1260
|
+
* Returns a Team by its id.
|
|
1265
1261
|
|
|
1266
|
-
* @summary
|
|
1262
|
+
* @summary Get team
|
|
1267
1263
|
*/
|
|
1268
|
-
const
|
|
1269
|
-
return customFetch(
|
|
1264
|
+
const getTeam = async (teamId, params, options) => {
|
|
1265
|
+
return customFetch(getGetTeamUrl(teamId, params), {
|
|
1270
1266
|
...options,
|
|
1271
|
-
method: "
|
|
1267
|
+
method: "GET"
|
|
1272
1268
|
});
|
|
1273
1269
|
};
|
|
1274
|
-
const
|
|
1270
|
+
const getDeleteTeamUrl = (teamId, params) => {
|
|
1275
1271
|
const normalizedParams = new URLSearchParams();
|
|
1276
1272
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1277
1273
|
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1278
1274
|
});
|
|
1279
1275
|
const stringifiedParams = normalizedParams.toString();
|
|
1280
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams?${stringifiedParams}` : `${getProxyPath()}/teams`;
|
|
1276
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/${teamId}?${stringifiedParams}` : `${getProxyPath()}/teams/${teamId}`;
|
|
1281
1277
|
};
|
|
1282
1278
|
/**
|
|
1283
|
-
*
|
|
1279
|
+
* Deactivates the team.
|
|
1280
|
+
|
|
1281
|
+
The team is tombstoned rather than erased: it stays readable through
|
|
1282
|
+
getTeam with status `deactivated`. Its memberships are removed and the
|
|
1283
|
+
users whose lifecycle it owns are deactivated with it.
|
|
1284
|
+
|
|
1285
|
+
The request is idempotent. Deleting a team that is already deactivated
|
|
1286
|
+
or doesn't exist succeeds without changing anything.
|
|
1287
|
+
|
|
1288
|
+
* @summary Delete team
|
|
1284
1289
|
*/
|
|
1285
|
-
const
|
|
1286
|
-
return customFetch(
|
|
1290
|
+
const deleteTeam = async (teamId, params, options) => {
|
|
1291
|
+
return customFetch(getDeleteTeamUrl(teamId, params), {
|
|
1287
1292
|
...options,
|
|
1288
|
-
method: "
|
|
1293
|
+
method: "DELETE"
|
|
1294
|
+
});
|
|
1295
|
+
};
|
|
1296
|
+
const getUpdateTeamUrl = (teamId, params) => {
|
|
1297
|
+
const normalizedParams = new URLSearchParams();
|
|
1298
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1299
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1300
|
+
});
|
|
1301
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1302
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/${teamId}?${stringifiedParams}` : `${getProxyPath()}/teams/${teamId}`;
|
|
1303
|
+
};
|
|
1304
|
+
/**
|
|
1305
|
+
* Update team. Only active teams can be updated.
|
|
1306
|
+
|
|
1307
|
+
* @summary Update team
|
|
1308
|
+
*/
|
|
1309
|
+
const updateTeam = async (teamId, updateTeamBody, params, options) => {
|
|
1310
|
+
return customFetch(getUpdateTeamUrl(teamId, params), {
|
|
1311
|
+
...options,
|
|
1312
|
+
method: "PATCH",
|
|
1289
1313
|
headers: {
|
|
1290
1314
|
"Content-Type": "application/json",
|
|
1291
1315
|
...options?.headers
|
|
1292
1316
|
},
|
|
1293
|
-
body: JSON.stringify(
|
|
1317
|
+
body: JSON.stringify(updateTeamBody)
|
|
1294
1318
|
});
|
|
1295
1319
|
};
|
|
1296
|
-
const
|
|
1320
|
+
const getCreateBrandingUrl = (params) => {
|
|
1297
1321
|
const normalizedParams = new URLSearchParams();
|
|
1298
1322
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1299
1323
|
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1300
1324
|
});
|
|
1301
1325
|
const stringifiedParams = normalizedParams.toString();
|
|
1302
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/
|
|
1326
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/branding?${stringifiedParams}` : `${getProxyPath()}/branding`;
|
|
1303
1327
|
};
|
|
1304
1328
|
/**
|
|
1305
|
-
*
|
|
1329
|
+
* Publishes a new immutable branding revision for the project. Branding
|
|
1330
|
+
revisions cannot be updated or deleted; every edit publishes a new
|
|
1331
|
+
revision, and flow responses resolve the latest revision per project
|
|
1332
|
+
(see ADR 040).
|
|
1306
1333
|
|
|
1307
|
-
|
|
1334
|
+
The `liquid_template` is validated lexically on save (size, encoding,
|
|
1335
|
+
banned patterns such as `<script>` tags, inline event handlers, and the
|
|
1336
|
+
`| raw` filter). Authoritative LiquidJS validation runs at authoring
|
|
1337
|
+
time via `zitadel plan` / `zitadel apply`.
|
|
1338
|
+
|
|
1339
|
+
* @summary Publish a new branding revision
|
|
1308
1340
|
*/
|
|
1309
|
-
const
|
|
1310
|
-
return customFetch(
|
|
1341
|
+
const createBranding = async (createBrandingBody, params, options) => {
|
|
1342
|
+
return customFetch(getCreateBrandingUrl(params), {
|
|
1311
1343
|
...options,
|
|
1312
|
-
method: "
|
|
1344
|
+
method: "POST",
|
|
1345
|
+
headers: {
|
|
1346
|
+
"Content-Type": "application/json",
|
|
1347
|
+
...options?.headers
|
|
1348
|
+
},
|
|
1349
|
+
body: JSON.stringify(createBrandingBody)
|
|
1313
1350
|
});
|
|
1314
1351
|
};
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1352
|
+
const getListBrandingUrl = (params) => {
|
|
1353
|
+
const normalizedParams = new URLSearchParams();
|
|
1354
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1355
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1356
|
+
});
|
|
1357
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1358
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/branding?${stringifiedParams}` : `${getProxyPath()}/branding`;
|
|
1359
|
+
};
|
|
1360
|
+
/**
|
|
1361
|
+
* Lists branding revisions for the project, newest first, capped at the
|
|
1362
|
+
100 most recent. The first entry is the revision flow responses
|
|
1363
|
+
currently resolve. Deliberately unpaginated in v1 — list endpoints
|
|
1364
|
+
gain a real query mechanism together (ADR 031); advertising pagination
|
|
1365
|
+
parameters the server ignores would be worse than none.
|
|
1366
|
+
|
|
1367
|
+
* @summary List branding revisions
|
|
1368
|
+
*/
|
|
1369
|
+
const listBranding = async (params, options) => {
|
|
1370
|
+
return customFetch(getListBrandingUrl(params), {
|
|
1371
|
+
...options,
|
|
1372
|
+
method: "GET"
|
|
1373
|
+
});
|
|
1374
|
+
};
|
|
1375
|
+
const getGetBrandingByIdUrl = (id, params) => {
|
|
1376
|
+
const normalizedParams = new URLSearchParams();
|
|
1377
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1378
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1379
|
+
});
|
|
1380
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1381
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/branding/${id}?${stringifiedParams}` : `${getProxyPath()}/branding/${id}`;
|
|
1382
|
+
};
|
|
1383
|
+
/**
|
|
1384
|
+
* Retrieves a single branding revision, including its stored configuration.
|
|
1385
|
+
* @summary Get a branding revision by ID
|
|
1386
|
+
*/
|
|
1387
|
+
const getBrandingById = async (id, params, options) => {
|
|
1388
|
+
return customFetch(getGetBrandingByIdUrl(id, params), {
|
|
1389
|
+
...options,
|
|
1390
|
+
method: "GET"
|
|
1391
|
+
});
|
|
1392
|
+
};
|
|
1393
|
+
//#endregion
|
|
1394
|
+
//#region ../api/dist/runtime/api-factory.mjs
|
|
1395
|
+
/**
|
|
1396
|
+
* Factory for the typed Zitadel client every consumer reaches for.
|
|
1397
|
+
*
|
|
1320
1398
|
* Wraps the orval-generated endpoints in a {@link Proxy} so each
|
|
1321
1399
|
* generated function sees the right base URL and (optionally) the
|
|
1322
1400
|
* right bearer token at call time — without the caller having to
|
|
@@ -1338,11 +1416,12 @@ const getTeam = async (teamId, params, options) => {
|
|
|
1338
1416
|
* generated orval function:
|
|
1339
1417
|
*
|
|
1340
1418
|
* const client = createZitadelClient({ baseUrl, token });
|
|
1341
|
-
* await client.createProject({
|
|
1419
|
+
* await client.createProject({ preview_origins: [] });
|
|
1342
1420
|
* await client.createSchema(body, { project_id });
|
|
1343
1421
|
*/
|
|
1344
1422
|
function createZitadelClient(opts) {
|
|
1345
|
-
|
|
1423
|
+
let baseUrl = opts.baseUrl;
|
|
1424
|
+
while (baseUrl.endsWith("/")) baseUrl = baseUrl.slice(0, -1);
|
|
1346
1425
|
return new Proxy(zitadelNextGen_exports, { get(target, prop, receiver) {
|
|
1347
1426
|
const value = Reflect.get(target, prop, receiver);
|
|
1348
1427
|
if (typeof value !== "function") return value;
|
|
@@ -1353,15 +1432,6 @@ function createZitadelClient(opts) {
|
|
|
1353
1432
|
};
|
|
1354
1433
|
} });
|
|
1355
1434
|
}
|
|
1356
|
-
/**
|
|
1357
|
-
* Legacy entry point: builds a client with only the base URL set, no
|
|
1358
|
-
* token. Components and SDKs that don't carry an auth token (browser
|
|
1359
|
-
* flows authenticated by platform cookies) still consume this name.
|
|
1360
|
-
* Equivalent to `createZitadelClient({ baseUrl: apiBase })`.
|
|
1361
|
-
*/
|
|
1362
|
-
function createApi(apiBase) {
|
|
1363
|
-
return createZitadelClient({ baseUrl: apiBase });
|
|
1364
|
-
}
|
|
1365
1435
|
//#endregion
|
|
1366
1436
|
//#region ../api/dist/runtime/config.mjs
|
|
1367
1437
|
/**
|
|
@@ -1401,7 +1471,10 @@ const apiCache = /* @__PURE__ */ new WeakMap();
|
|
|
1401
1471
|
function getApi(project) {
|
|
1402
1472
|
let api = apiCache.get(project);
|
|
1403
1473
|
if (!api) {
|
|
1404
|
-
api =
|
|
1474
|
+
api = createZitadelClient({
|
|
1475
|
+
baseUrl: project.proxyPath,
|
|
1476
|
+
token: project.publishableKey
|
|
1477
|
+
});
|
|
1405
1478
|
apiCache.set(project, api);
|
|
1406
1479
|
}
|
|
1407
1480
|
return api;
|
|
@@ -1489,183 +1562,6 @@ function resolveApi(project, attrs, element) {
|
|
|
1489
1562
|
};
|
|
1490
1563
|
}
|
|
1491
1564
|
//#endregion
|
|
1492
|
-
//#region src/orchestrator/branding-to-tokens.ts
|
|
1493
|
-
/**
|
|
1494
|
-
* Branding -> CSS token bridge.
|
|
1495
|
-
*
|
|
1496
|
-
* Per `docs/design/branding/README.md` the orchestrator owns theming. Templates
|
|
1497
|
-
* never emit `<style>` blocks; they only structure HTML. This module translates
|
|
1498
|
-
* the `Branding` JSON into a CSSStyleSheet of `:host { --zl-* }` declarations
|
|
1499
|
-
* and applies it via `shadowRoot.adoptedStyleSheets`.
|
|
1500
|
-
*
|
|
1501
|
-
* Dark-mode overrides are emitted as `:host([data-theme="dark"]) { ... }`
|
|
1502
|
-
* (matching `docs/design/branding/tokens.md`). Resolution between
|
|
1503
|
-
* `light | dark | auto` happens in `<zitadel-login>`.
|
|
1504
|
-
*
|
|
1505
|
-
* Base layer: the design-tokens package ships the full `--zl-*` set as both a
|
|
1506
|
-
* `.css` file (for host pages) and a `tokensCss` string (for shadow roots and
|
|
1507
|
-
* SSR). We adopt the string into every orchestrator shadow root so atoms
|
|
1508
|
-
* paint correctly even when the host page didn't `@import` tokens.css — the
|
|
1509
|
-
* orchestrator is meant to drop into any page.
|
|
1510
|
-
*/
|
|
1511
|
-
const RADIUS_MAP = {
|
|
1512
|
-
none: "0",
|
|
1513
|
-
sm: "0.25rem",
|
|
1514
|
-
md: "0.5rem",
|
|
1515
|
-
lg: "0.75rem",
|
|
1516
|
-
full: "9999px"
|
|
1517
|
-
};
|
|
1518
|
-
const DENSITY_MAP = {
|
|
1519
|
-
compact: {
|
|
1520
|
-
"--zl-spacing-03": "0.75rem",
|
|
1521
|
-
"--zl-spacing-05": "1.5rem"
|
|
1522
|
-
},
|
|
1523
|
-
regular: {},
|
|
1524
|
-
comfortable: {
|
|
1525
|
-
"--zl-spacing-03": "1.25rem",
|
|
1526
|
-
"--zl-spacing-05": "2.25rem"
|
|
1527
|
-
}
|
|
1528
|
-
};
|
|
1529
|
-
const PALETTE_MAP = {
|
|
1530
|
-
primary: ["--zl-color-surface-default-white"],
|
|
1531
|
-
on_primary: ["--zl-color-text-button-default"],
|
|
1532
|
-
background: ["--zl-color-surface-default-black"],
|
|
1533
|
-
surface: ["--zl-color-surface-default-primary-gray"],
|
|
1534
|
-
muted: ["--zl-color-surface-default-secondary-gray"],
|
|
1535
|
-
border: ["--zl-color-border-default-gray-200", "--zl-color-border-default-gray-100"],
|
|
1536
|
-
text: ["--zl-color-text-primary-white"],
|
|
1537
|
-
text_muted: ["--zl-color-text-secondary-gray"],
|
|
1538
|
-
link: ["--zl-color-text-subtitle-pink"],
|
|
1539
|
-
success: [
|
|
1540
|
-
"--zl-color-text-success",
|
|
1541
|
-
"--zl-color-border-success",
|
|
1542
|
-
"--zl-color-icon-success"
|
|
1543
|
-
],
|
|
1544
|
-
warning: ["--zl-color-text-subtitle-orange"],
|
|
1545
|
-
error: [
|
|
1546
|
-
"--zl-color-text-error",
|
|
1547
|
-
"--zl-color-border-error",
|
|
1548
|
-
"--zl-color-icon-error"
|
|
1549
|
-
]
|
|
1550
|
-
};
|
|
1551
|
-
/**
|
|
1552
|
-
* Build a CSS string of `:host { --zl-* }` declarations from a Branding
|
|
1553
|
-
* payload. Light values land on `:host`; dark overrides land on
|
|
1554
|
-
* `:host([data-theme="dark"])`.
|
|
1555
|
-
*/
|
|
1556
|
-
function buildBrandingStylesheet(branding, options = {}) {
|
|
1557
|
-
const lightDecls = collectDeclarations(branding);
|
|
1558
|
-
const darkPalette = branding?.theme?.dark?.palette;
|
|
1559
|
-
const darkDecls = darkPalette ? mapPalette(darkPalette) : {};
|
|
1560
|
-
const blocks = [];
|
|
1561
|
-
if (Object.keys(lightDecls).length > 0) blocks.push(formatBlock(":host", lightDecls));
|
|
1562
|
-
if (Object.keys(darkDecls).length > 0) blocks.push(formatBlock(":host([data-theme=\"dark\"])", darkDecls));
|
|
1563
|
-
if (options.resolvedTheme === "dark" && darkPalette) {
|
|
1564
|
-
const merged = {
|
|
1565
|
-
...lightDecls,
|
|
1566
|
-
...darkDecls
|
|
1567
|
-
};
|
|
1568
|
-
blocks.length = 0;
|
|
1569
|
-
blocks.push(formatBlock(":host", merged));
|
|
1570
|
-
}
|
|
1571
|
-
return blocks.join("\n");
|
|
1572
|
-
}
|
|
1573
|
-
function collectDeclarations(branding) {
|
|
1574
|
-
if (!branding) return {};
|
|
1575
|
-
const decls = {};
|
|
1576
|
-
Object.assign(decls, mapPalette(branding.palette));
|
|
1577
|
-
Object.assign(decls, mapTypography(branding.typography));
|
|
1578
|
-
Object.assign(decls, mapShape(branding.shape));
|
|
1579
|
-
return decls;
|
|
1580
|
-
}
|
|
1581
|
-
function mapPalette(palette) {
|
|
1582
|
-
if (!palette) return {};
|
|
1583
|
-
const out = {};
|
|
1584
|
-
for (const [key, varNames] of Object.entries(PALETTE_MAP)) {
|
|
1585
|
-
const value = palette[key];
|
|
1586
|
-
if (typeof value === "string" && value.length > 0) for (const varName of varNames) out[varName] = value;
|
|
1587
|
-
}
|
|
1588
|
-
return out;
|
|
1589
|
-
}
|
|
1590
|
-
function mapTypography(typography) {
|
|
1591
|
-
if (!typography) return {};
|
|
1592
|
-
const out = {};
|
|
1593
|
-
if (typography.font_family) {
|
|
1594
|
-
out["--zl-font-family-sans"] = typography.font_family;
|
|
1595
|
-
out["--zl-font-family-heading"] = typography.font_family;
|
|
1596
|
-
}
|
|
1597
|
-
if (typography.font_family_mono) out["--zl-font-family-mono"] = typography.font_family_mono;
|
|
1598
|
-
const scale = clamp(typography.scale ?? 1, .75, 1.25);
|
|
1599
|
-
if (scale !== 1) out["--zl-font-scale"] = `${scale}`;
|
|
1600
|
-
return out;
|
|
1601
|
-
}
|
|
1602
|
-
function mapShape(shape) {
|
|
1603
|
-
if (!shape) return {};
|
|
1604
|
-
const out = {};
|
|
1605
|
-
if (shape.radius && RADIUS_MAP[shape.radius]) {
|
|
1606
|
-
const radius = RADIUS_MAP[shape.radius];
|
|
1607
|
-
out["--zl-radius-s"] = radius;
|
|
1608
|
-
out["--zl-radius-m"] = radius === "0" ? "0" : `calc(${radius} * 1.5)`;
|
|
1609
|
-
out["--zl-radius-l"] = radius === "0" ? "0" : `calc(${radius} * 2)`;
|
|
1610
|
-
}
|
|
1611
|
-
if (shape.density) Object.assign(out, DENSITY_MAP[shape.density]);
|
|
1612
|
-
return out;
|
|
1613
|
-
}
|
|
1614
|
-
function formatBlock(selector, decls) {
|
|
1615
|
-
return `${selector} {\n${Object.entries(decls).map(([prop, value]) => ` ${prop}: ${value};`).join("\n")}\n}`;
|
|
1616
|
-
}
|
|
1617
|
-
function clamp(value, min, max) {
|
|
1618
|
-
if (Number.isNaN(value)) return min;
|
|
1619
|
-
return Math.min(max, Math.max(min, value));
|
|
1620
|
-
}
|
|
1621
|
-
let baseTokenSheet;
|
|
1622
|
-
function getBaseTokenSheet() {
|
|
1623
|
-
if (typeof CSSStyleSheet === "undefined") return void 0;
|
|
1624
|
-
if (!baseTokenSheet) {
|
|
1625
|
-
baseTokenSheet = new CSSStyleSheet();
|
|
1626
|
-
baseTokenSheet.replaceSync(tokensCss.replaceAll(":root,\n[data-theme=\"dark\"]", ":host,\n:host([data-theme=\"dark\"])").replaceAll("[data-theme=\"light\"]", ":host([data-theme=\"light\"])"));
|
|
1627
|
-
}
|
|
1628
|
-
return baseTokenSheet;
|
|
1629
|
-
}
|
|
1630
|
-
/**
|
|
1631
|
-
* Adopt the design-system base token layer onto a `ShadowRoot`. Safe to call
|
|
1632
|
-
* many times — the underlying constructable sheet is shared across every
|
|
1633
|
-
* orchestrator instance and de-duplicated in the adopted list.
|
|
1634
|
-
*/
|
|
1635
|
-
function applyBaseTokens(shadowRoot) {
|
|
1636
|
-
const sheet = getBaseTokenSheet();
|
|
1637
|
-
if (!sheet) return;
|
|
1638
|
-
const existing = Array.isArray(shadowRoot.adoptedStyleSheets) ? shadowRoot.adoptedStyleSheets : [];
|
|
1639
|
-
if (existing.includes(sheet)) return;
|
|
1640
|
-
try {
|
|
1641
|
-
shadowRoot.adoptedStyleSheets = [sheet, ...existing];
|
|
1642
|
-
} catch {}
|
|
1643
|
-
}
|
|
1644
|
-
/**
|
|
1645
|
-
* Apply a branding payload as a `--zl-*` overrides layer on top of the base
|
|
1646
|
-
* token sheet. Subsequent calls replace the previous override sheet so a new
|
|
1647
|
-
* branding payload paints cleanly without leaking older declarations.
|
|
1648
|
-
*
|
|
1649
|
-
* Callers should run `applyBaseTokens(shadowRoot)` first (once per shadow
|
|
1650
|
-
* root) so the base values exist before branding patches them.
|
|
1651
|
-
*/
|
|
1652
|
-
function applyBrandingTokens(shadowRoot, branding, resolvedTheme) {
|
|
1653
|
-
const css = buildBrandingStylesheet(branding, { resolvedTheme });
|
|
1654
|
-
if (typeof CSSStyleSheet === "undefined") return;
|
|
1655
|
-
const sheet = new CSSStyleSheet();
|
|
1656
|
-
sheet.replaceSync(css);
|
|
1657
|
-
const previous = shadowRoot.__zlTokenSheet;
|
|
1658
|
-
shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets.filter((s) => s !== previous), sheet];
|
|
1659
|
-
shadowRoot.__zlTokenSheet = sheet;
|
|
1660
|
-
}
|
|
1661
|
-
function resolveTheme(branding) {
|
|
1662
|
-
const mode = branding?.theme?.mode ?? "dark";
|
|
1663
|
-
if (mode === "light") return "light";
|
|
1664
|
-
if (mode === "dark") return "dark";
|
|
1665
|
-
if (typeof matchMedia === "function") return matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
|
|
1666
|
-
return "dark";
|
|
1667
|
-
}
|
|
1668
|
-
//#endregion
|
|
1669
1565
|
//#region ../api/dist/generated/model/index.mjs
|
|
1670
1566
|
const CreateFlow201BrandingLayout = {
|
|
1671
1567
|
centered: "centered",
|
|
@@ -1728,83 +1624,57 @@ function sanitiseUrl(value, field, issues) {
|
|
|
1728
1624
|
}
|
|
1729
1625
|
}
|
|
1730
1626
|
//#endregion
|
|
1731
|
-
//#region src/orchestrator/
|
|
1627
|
+
//#region src/orchestrator/exportparts.ts
|
|
1732
1628
|
/**
|
|
1733
|
-
*
|
|
1734
|
-
*
|
|
1735
|
-
* Two faces can be linked into the host document's `<head>`:
|
|
1736
|
-
*
|
|
1737
|
-
* - The **design-system default** (`applyDefaultFont`) — the brand body face
|
|
1738
|
-
* (Arimo, named first in `--zl-font-family-sans`). The component ships this
|
|
1739
|
-
* so the auth screens render in the brand font out of the box, even when the
|
|
1740
|
-
* server returns no branding. This is the "default users can override" layer.
|
|
1741
|
-
* - The **tenant override** (`applyFontUrl`) — `branding.font_url`, resolved
|
|
1742
|
-
* server-side from the app → team → project branding hierarchy.
|
|
1629
|
+
* Forwards atom-internal CSS Shadow Parts through the orchestrator boundary.
|
|
1743
1630
|
*
|
|
1744
|
-
*
|
|
1745
|
-
*
|
|
1746
|
-
*
|
|
1747
|
-
*
|
|
1748
|
-
*
|
|
1631
|
+
* Atoms stamp `part="..."` on their internals and declare the public set in
|
|
1632
|
+
* their manifest (`parts`). Those parts are visible one shadow boundary up —
|
|
1633
|
+
* inside the orchestrator's shadow root — but not to the embedding page,
|
|
1634
|
+
* because `::part()` pierces a single boundary unless each host in the chain
|
|
1635
|
+
* re-exports (`exportparts`). Templates never author the forwarding (the
|
|
1636
|
+
* sanitiser strips `exportparts`); the orchestrator stamps it after every
|
|
1637
|
+
* render commit so the mapping stays canonical for template-rendered and
|
|
1638
|
+
* gate-patched atoms alike.
|
|
1749
1639
|
*
|
|
1750
|
-
*
|
|
1751
|
-
*
|
|
1752
|
-
*
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
const DEFAULT_LINK_ID = "zl-default-font-link";
|
|
1756
|
-
/**
|
|
1757
|
-
* Design-system default brand font. Arimo is the Figma body face and leads the
|
|
1758
|
-
* `--zl-font-family-sans` stack; loading it here makes the unbranded auth UI
|
|
1759
|
-
* paint in the brand font instead of the system fallback. Tenants override it
|
|
1760
|
-
* with `branding.font_url`. Exported so a privacy- or offline-sensitive
|
|
1761
|
-
* deployment can self-host the same face and swap this single URL.
|
|
1640
|
+
* Exported names are mechanical, collision-free, and public contract
|
|
1641
|
+
* (override-ladder Tier 2, same stability rules as token names):
|
|
1642
|
+
* `<atom>-<part>` with the `zl-` prefix dropped — `zl-field`'s `input` part
|
|
1643
|
+
* becomes `zitadel-login::part(field-input)` on the host page. Manifests are
|
|
1644
|
+
* the canonical part catalogue; this module derives, never curates.
|
|
1762
1645
|
*/
|
|
1763
|
-
const
|
|
1646
|
+
const exportpartsByTag = new Map(manifestRegistry.filter((manifest) => manifest.parts.length > 0).map((manifest) => {
|
|
1647
|
+
const short = manifest.tag.replace(/^zl-/, "");
|
|
1648
|
+
const value = manifest.parts.map((part) => `${part}: ${short}-${part}`).join(", ");
|
|
1649
|
+
return [manifest.tag, value];
|
|
1650
|
+
}));
|
|
1764
1651
|
/**
|
|
1765
|
-
*
|
|
1766
|
-
*
|
|
1652
|
+
* Stamps `exportparts` onto every known atom in `scope`. Idempotent — call
|
|
1653
|
+
* after each render commit: `unsafeHTML` re-parses replace previously
|
|
1654
|
+
* stamped nodes, and the mandatory-gates patcher appends atoms after
|
|
1655
|
+
* sanitisation.
|
|
1767
1656
|
*/
|
|
1768
|
-
function
|
|
1769
|
-
|
|
1657
|
+
function stampExportparts(scope) {
|
|
1658
|
+
for (const [tag, value] of exportpartsByTag) for (const el of scope.querySelectorAll(tag)) if (el.getAttribute("exportparts") !== value) el.setAttribute("exportparts", value);
|
|
1770
1659
|
}
|
|
1660
|
+
//#endregion
|
|
1661
|
+
//#region src/internal/escape-html.ts
|
|
1771
1662
|
/**
|
|
1772
|
-
*
|
|
1773
|
-
*
|
|
1774
|
-
*
|
|
1663
|
+
* Escapes the five HTML-significant characters so a string is safe to
|
|
1664
|
+
* interpolate into either element text or a double-quoted attribute value.
|
|
1665
|
+
*
|
|
1666
|
+
* Used for the orchestrator-owned attribution markup, which is appended after
|
|
1667
|
+
* the Liquid output has already been sanitised — so it can't rely on the
|
|
1668
|
+
* DOMPurify pass and must escape its own (tenant-supplied) values.
|
|
1775
1669
|
*/
|
|
1776
|
-
function
|
|
1777
|
-
|
|
1778
|
-
}
|
|
1779
|
-
function applyLinkedStylesheet(shadowRoot, linkId, href) {
|
|
1780
|
-
const ownerDocument = shadowRoot.ownerDocument ?? document;
|
|
1781
|
-
const existing = ownerDocument.head.querySelector(`link#${linkId}`);
|
|
1782
|
-
if (!href) {
|
|
1783
|
-
existing?.remove();
|
|
1784
|
-
return;
|
|
1785
|
-
}
|
|
1786
|
-
const link = existing ?? ownerDocument.createElement("link");
|
|
1787
|
-
link.id = linkId;
|
|
1788
|
-
link.rel = "stylesheet";
|
|
1789
|
-
if (link.href !== href) link.href = href;
|
|
1790
|
-
if (!existing) ownerDocument.head.appendChild(link);
|
|
1791
|
-
}
|
|
1792
|
-
//#endregion
|
|
1793
|
-
//#region src/internal/escape-html.ts
|
|
1794
|
-
/**
|
|
1795
|
-
* Escapes the five HTML-significant characters so a string is safe to
|
|
1796
|
-
* interpolate into either element text or a double-quoted attribute value.
|
|
1797
|
-
*
|
|
1798
|
-
* Used for the orchestrator-owned attribution markup, which is appended after
|
|
1799
|
-
* the Liquid output has already been sanitised — so it can't rely on the
|
|
1800
|
-
* DOMPurify pass and must escape its own (tenant-supplied) values.
|
|
1801
|
-
*/
|
|
1802
|
-
function escapeHtml(value) {
|
|
1803
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1670
|
+
function escapeHtml(value) {
|
|
1671
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1804
1672
|
}
|
|
1805
1673
|
//#endregion
|
|
1806
1674
|
//#region src/orchestrator/mandatory-gates.ts
|
|
1807
1675
|
const MANDATORY_GATES_MARKER = "ZL_MANDATORY_GATES";
|
|
1676
|
+
/** Every atom tag that participates as a named form field. */
|
|
1677
|
+
const FIELD_ATOM_TAGS = "zl-field, zl-select, zl-checkbox";
|
|
1808
1678
|
const mandatoryGatesMarkerComment = `<!--${MANDATORY_GATES_MARKER}-->`;
|
|
1809
1679
|
function patchMandatoryGates(html, step, locale) {
|
|
1810
1680
|
const template = document.createElement("template");
|
|
@@ -1835,7 +1705,7 @@ function hasPrimaryButton(fragment) {
|
|
|
1835
1705
|
return Boolean(fragment.querySelector("zl-button[hierarchy=\"primary\"]"));
|
|
1836
1706
|
}
|
|
1837
1707
|
function hasFieldFor(fragment, name) {
|
|
1838
|
-
for (const field of fragment.querySelectorAll(
|
|
1708
|
+
for (const field of fragment.querySelectorAll(FIELD_ATOM_TAGS)) if (field.getAttribute("name") === name) return true;
|
|
1839
1709
|
return false;
|
|
1840
1710
|
}
|
|
1841
1711
|
function findMarkerComment(fragment) {
|
|
@@ -1887,11 +1757,15 @@ function lookup(locale, key) {
|
|
|
1887
1757
|
*/
|
|
1888
1758
|
const TEMPLATE_NAMES$1 = { default: "default" };
|
|
1889
1759
|
/**
|
|
1890
|
-
* Maps `error.*`
|
|
1891
|
-
*
|
|
1892
|
-
*
|
|
1893
|
-
*
|
|
1894
|
-
*
|
|
1760
|
+
* Maps catalog `error.*` keys with no `_<rule>` suffix (so
|
|
1761
|
+
* {@link localiseFlowErrorKeys} can't derive the field from the key itself)
|
|
1762
|
+
* to their field name — the credential errors with bespoke catalog copy
|
|
1763
|
+
* (`error.email_exists`, `error.invalid_credentials`, …). Rule-suffixed keys
|
|
1764
|
+
* (`error.<field>_<rule>`) derive their field from the key. Consumed only by
|
|
1765
|
+
* {@link localiseFlowErrorKeys}, which stamps the resolved `field` onto the
|
|
1766
|
+
* error; the template filters (`fieldError` / `formLevelError`) then route by
|
|
1767
|
+
* that `field`, and an error whose field the step omits is downgraded to a
|
|
1768
|
+
* fieldless banner message.
|
|
1895
1769
|
*/
|
|
1896
1770
|
const fieldErrorKeys = {
|
|
1897
1771
|
"error.email_required": "email",
|
|
@@ -1915,9 +1789,18 @@ function createLiquidEngine(options) {
|
|
|
1915
1789
|
outputEscape: "escape"
|
|
1916
1790
|
});
|
|
1917
1791
|
engine.registerFilter("raw", (value) => stringify(value));
|
|
1792
|
+
const warnedMissingKeys = /* @__PURE__ */ new Set();
|
|
1918
1793
|
engine.registerFilter("t", function tFilter(key, ...args) {
|
|
1919
1794
|
const lookupKey = stringify(key);
|
|
1920
|
-
|
|
1795
|
+
let template = options.locale[lookupKey] ?? injectedKeyFallback(options.locale, lookupKey) ?? fieldLabelFallback(lookupKey);
|
|
1796
|
+
if (template === void 0) {
|
|
1797
|
+
template = lookupKey;
|
|
1798
|
+
if (lookupKey !== "" && !warnedMissingKeys.has(lookupKey)) {
|
|
1799
|
+
warnedMissingKeys.add(lookupKey);
|
|
1800
|
+
console.warn(`[zitadel-login] missing text key "${lookupKey}" — rendering the raw key`);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
return interpolate(template, args.map(stringify));
|
|
1921
1804
|
});
|
|
1922
1805
|
/** Resolves `{text_key}.placeholder` — empty when undefined (not the raw key). */
|
|
1923
1806
|
engine.registerFilter("fieldPlaceholder", (textKey) => {
|
|
@@ -1960,8 +1843,9 @@ function createLiquidEngine(options) {
|
|
|
1960
1843
|
const name = stringify(fieldName);
|
|
1961
1844
|
if (!Array.isArray(errors)) return "";
|
|
1962
1845
|
for (const item of errors) {
|
|
1963
|
-
const
|
|
1964
|
-
if (
|
|
1846
|
+
const err = item;
|
|
1847
|
+
if (err.field !== name) continue;
|
|
1848
|
+
return err.text_key ? options.locale[err.text_key] ?? err.text_key : err.message ?? "";
|
|
1965
1849
|
}
|
|
1966
1850
|
return "";
|
|
1967
1851
|
});
|
|
@@ -1972,12 +1856,17 @@ function createLiquidEngine(options) {
|
|
|
1972
1856
|
* raw wire key. See hookName for the contract.
|
|
1973
1857
|
*/
|
|
1974
1858
|
engine.registerFilter("testid", (fieldName) => hookName(stringify(fieldName)));
|
|
1975
|
-
/**
|
|
1859
|
+
/**
|
|
1860
|
+
* True when the error should render as `<zl-alert>`, not on a field. A
|
|
1861
|
+
* field-scoped error (`field` set by {@link localiseFlowErrorKeys} when the
|
|
1862
|
+
* step renders that field) routes inline instead; everything else — engine
|
|
1863
|
+
* errors, and orphaned field errors already downgraded to a fieldless
|
|
1864
|
+
* message — falls to the banner.
|
|
1865
|
+
*/
|
|
1976
1866
|
engine.registerFilter("formLevelError", (err) => {
|
|
1977
|
-
|
|
1978
|
-
return key === "" || !(key in fieldErrorKeys);
|
|
1867
|
+
return !err?.field;
|
|
1979
1868
|
});
|
|
1980
|
-
engine.registerTag(
|
|
1869
|
+
engine.registerTag(MANDATORY_GATES_TAG, {
|
|
1981
1870
|
parse() {},
|
|
1982
1871
|
render() {
|
|
1983
1872
|
return mandatoryGatesMarkerComment;
|
|
@@ -2077,12 +1966,19 @@ function localiseFlowErrorKeys(raw, ctx) {
|
|
|
2077
1966
|
function localiseFlowErrorKey(key, ctx) {
|
|
2078
1967
|
const inlineField = fieldErrorKeys[key];
|
|
2079
1968
|
const orphanedInline = inlineField !== void 0 && ctx.fields !== void 0 && !ctx.fields.includes(inlineField);
|
|
2080
|
-
if (!orphanedInline && ctx.locale[key] !== void 0) return
|
|
1969
|
+
if (!orphanedInline && ctx.locale[key] !== void 0) return inlineField !== void 0 ? {
|
|
1970
|
+
field: inlineField,
|
|
1971
|
+
text_key: key
|
|
1972
|
+
} : { text_key: key };
|
|
2081
1973
|
for (const { suffix, generic } of FLOW_ERROR_RULE_FALLBACKS) {
|
|
2082
1974
|
if (!key.endsWith(suffix)) continue;
|
|
2083
1975
|
const field = key.slice(6, key.length - suffix.length);
|
|
2084
1976
|
if (field === "") break;
|
|
2085
|
-
|
|
1977
|
+
const message = capitaliseFirst(interpolate(ctx.locale[generic] ?? ctx.locale[FLOW_ERROR_CATCH_ALL_KEY] ?? "Please check {0}.", [fieldLabel(ctx, field)]));
|
|
1978
|
+
return ctx.fields !== void 0 && ctx.fields.includes(field) ? {
|
|
1979
|
+
field,
|
|
1980
|
+
message
|
|
1981
|
+
} : { message };
|
|
2086
1982
|
}
|
|
2087
1983
|
if (orphanedInline && ctx.locale[key] !== void 0) return { message: ctx.locale[key] };
|
|
2088
1984
|
return { text_key: key };
|
|
@@ -2161,8 +2057,8 @@ const TEMPLATE_NAMES = {
|
|
|
2161
2057
|
const en = {
|
|
2162
2058
|
"identifier.title": "Sign in",
|
|
2163
2059
|
"identifier.description": "Enter your email to continue",
|
|
2164
|
-
"identifier.field.email": "
|
|
2165
|
-
"identifier.field.email.placeholder": "you@
|
|
2060
|
+
"identifier.field.email": "Email",
|
|
2061
|
+
"identifier.field.email.placeholder": "you@example.com",
|
|
2166
2062
|
"identifier.field.password": "Password",
|
|
2167
2063
|
"identifier.action.submit": "Sign in",
|
|
2168
2064
|
"identifier.action.continue": "Sign in",
|
|
@@ -2179,8 +2075,8 @@ const en = {
|
|
|
2179
2075
|
"choose-register.action.register-with-passkey": "Sign up with passkey",
|
|
2180
2076
|
"collect-credentials.title": "Create your account",
|
|
2181
2077
|
"collect-credentials.description": "Set up your email and password",
|
|
2182
|
-
"collect-credentials.field.email": "
|
|
2183
|
-
"collect-credentials.field.email.placeholder": "you@
|
|
2078
|
+
"collect-credentials.field.email": "Email",
|
|
2079
|
+
"collect-credentials.field.email.placeholder": "you@example.com",
|
|
2184
2080
|
"collect-credentials.field.password": "Password",
|
|
2185
2081
|
"collect-credentials.action.submit": "Sign up",
|
|
2186
2082
|
"register-password.title": "Create your password",
|
|
@@ -2197,8 +2093,8 @@ const en = {
|
|
|
2197
2093
|
"passkey-upsell.action.setup": "Set up passkey",
|
|
2198
2094
|
"collect-passkey-email.title": "Create your account",
|
|
2199
2095
|
"collect-passkey-email.description": "Enter your email to set up a passkey",
|
|
2200
|
-
"collect-passkey-email.field.email": "
|
|
2201
|
-
"collect-passkey-email.field.email.placeholder": "you@
|
|
2096
|
+
"collect-passkey-email.field.email": "Email",
|
|
2097
|
+
"collect-passkey-email.field.email.placeholder": "you@example.com",
|
|
2202
2098
|
"collect-passkey-email.action.submit": "Continue",
|
|
2203
2099
|
"passkey-enroll.title": "Set up your passkey",
|
|
2204
2100
|
"passkey-enroll.description": "Use Face ID, Touch ID, or your device PIN to create a passkey.",
|
|
@@ -2215,11 +2111,12 @@ const en = {
|
|
|
2215
2111
|
"password.action.register.link": "Sign up",
|
|
2216
2112
|
"register.title": "Create your account",
|
|
2217
2113
|
"register.description": "",
|
|
2218
|
-
"register.field.email": "
|
|
2219
|
-
"register.field.email.placeholder": "you@
|
|
2114
|
+
"register.field.email": "Email",
|
|
2115
|
+
"register.field.email.placeholder": "you@example.com",
|
|
2220
2116
|
"register.field.password": "Password",
|
|
2221
2117
|
"register.field.givenName": "Given name",
|
|
2222
2118
|
"register.field.familyName": "Family name",
|
|
2119
|
+
"register.field.companyName": "Company name",
|
|
2223
2120
|
"register.field.dateOfBirth": "Date of birth",
|
|
2224
2121
|
"register.field.country": "Country",
|
|
2225
2122
|
"register.field.country.placeholder": "Select your country",
|
|
@@ -2278,7 +2175,7 @@ const de = {
|
|
|
2278
2175
|
"identifier.title": "Anmelden",
|
|
2279
2176
|
"identifier.description": "Gib deine E-Mail-Adresse ein, um fortzufahren",
|
|
2280
2177
|
"identifier.field.email": "E-Mail",
|
|
2281
|
-
"identifier.field.email.placeholder": "du@
|
|
2178
|
+
"identifier.field.email.placeholder": "du@beispiel.de",
|
|
2282
2179
|
"identifier.field.password": "Passwort",
|
|
2283
2180
|
"identifier.action.submit": "Anmelden",
|
|
2284
2181
|
"identifier.action.continue": "Anmelden",
|
|
@@ -2296,7 +2193,7 @@ const de = {
|
|
|
2296
2193
|
"collect-credentials.title": "Konto erstellen",
|
|
2297
2194
|
"collect-credentials.description": "Richte E-Mail und Passwort ein",
|
|
2298
2195
|
"collect-credentials.field.email": "E-Mail",
|
|
2299
|
-
"collect-credentials.field.email.placeholder": "du@
|
|
2196
|
+
"collect-credentials.field.email.placeholder": "du@beispiel.de",
|
|
2300
2197
|
"collect-credentials.field.password": "Passwort",
|
|
2301
2198
|
"collect-credentials.action.submit": "Registrieren",
|
|
2302
2199
|
"register-password.title": "Passwort erstellen",
|
|
@@ -2314,7 +2211,7 @@ const de = {
|
|
|
2314
2211
|
"collect-passkey-email.title": "Konto erstellen",
|
|
2315
2212
|
"collect-passkey-email.description": "Gib deine E-Mail-Adresse ein, um einen Passkey einzurichten",
|
|
2316
2213
|
"collect-passkey-email.field.email": "E-Mail",
|
|
2317
|
-
"collect-passkey-email.field.email.placeholder": "du@
|
|
2214
|
+
"collect-passkey-email.field.email.placeholder": "du@beispiel.de",
|
|
2318
2215
|
"collect-passkey-email.action.submit": "Weiter",
|
|
2319
2216
|
"passkey-enroll.title": "Passkey einrichten",
|
|
2320
2217
|
"passkey-enroll.description": "Verwende Face ID, Touch ID oder die Geräte-PIN, um einen Passkey zu erstellen.",
|
|
@@ -2332,10 +2229,11 @@ const de = {
|
|
|
2332
2229
|
"register.title": "Konto erstellen",
|
|
2333
2230
|
"register.description": "",
|
|
2334
2231
|
"register.field.email": "E-Mail",
|
|
2335
|
-
"register.field.email.placeholder": "du@
|
|
2232
|
+
"register.field.email.placeholder": "du@beispiel.de",
|
|
2336
2233
|
"register.field.password": "Passwort",
|
|
2337
2234
|
"register.field.givenName": "Vorname",
|
|
2338
2235
|
"register.field.familyName": "Nachname",
|
|
2236
|
+
"register.field.companyName": "Firmenname",
|
|
2339
2237
|
"register.field.dateOfBirth": "Geburtsdatum",
|
|
2340
2238
|
"register.field.country": "Land",
|
|
2341
2239
|
"register.field.country.placeholder": "Land auswählen",
|
|
@@ -2392,8 +2290,8 @@ const de = {
|
|
|
2392
2290
|
const it = {
|
|
2393
2291
|
"identifier.title": "Accedi",
|
|
2394
2292
|
"identifier.description": "Inserisci la tua e-mail per continuare",
|
|
2395
|
-
"identifier.field.email": "E-mail
|
|
2396
|
-
"identifier.field.email.placeholder": "tu@
|
|
2293
|
+
"identifier.field.email": "E-mail",
|
|
2294
|
+
"identifier.field.email.placeholder": "tu@esempio.com",
|
|
2397
2295
|
"identifier.field.password": "Password",
|
|
2398
2296
|
"identifier.action.submit": "Accedi",
|
|
2399
2297
|
"identifier.action.continue": "Accedi",
|
|
@@ -2410,8 +2308,8 @@ const it = {
|
|
|
2410
2308
|
"choose-register.action.register-with-passkey": "Registrati con passkey",
|
|
2411
2309
|
"collect-credentials.title": "Crea il tuo account",
|
|
2412
2310
|
"collect-credentials.description": "Configura e-mail e password",
|
|
2413
|
-
"collect-credentials.field.email": "E-mail
|
|
2414
|
-
"collect-credentials.field.email.placeholder": "tu@
|
|
2311
|
+
"collect-credentials.field.email": "E-mail",
|
|
2312
|
+
"collect-credentials.field.email.placeholder": "tu@esempio.com",
|
|
2415
2313
|
"collect-credentials.field.password": "Password",
|
|
2416
2314
|
"collect-credentials.action.submit": "Registrati",
|
|
2417
2315
|
"register-password.title": "Crea la tua password",
|
|
@@ -2428,8 +2326,8 @@ const it = {
|
|
|
2428
2326
|
"passkey-upsell.action.setup": "Configura passkey",
|
|
2429
2327
|
"collect-passkey-email.title": "Crea il tuo account",
|
|
2430
2328
|
"collect-passkey-email.description": "Inserisci la tua e-mail per configurare una passkey",
|
|
2431
|
-
"collect-passkey-email.field.email": "E-mail
|
|
2432
|
-
"collect-passkey-email.field.email.placeholder": "tu@
|
|
2329
|
+
"collect-passkey-email.field.email": "E-mail",
|
|
2330
|
+
"collect-passkey-email.field.email.placeholder": "tu@esempio.com",
|
|
2433
2331
|
"collect-passkey-email.action.submit": "Continua",
|
|
2434
2332
|
"passkey-enroll.title": "Configura la tua passkey",
|
|
2435
2333
|
"passkey-enroll.description": "Usa Face ID, Touch ID o il PIN del dispositivo per creare una passkey.",
|
|
@@ -2446,11 +2344,12 @@ const it = {
|
|
|
2446
2344
|
"password.action.register.link": "Registrati",
|
|
2447
2345
|
"register.title": "Crea il tuo account",
|
|
2448
2346
|
"register.description": "",
|
|
2449
|
-
"register.field.email": "E-mail
|
|
2450
|
-
"register.field.email.placeholder": "tu@
|
|
2347
|
+
"register.field.email": "E-mail",
|
|
2348
|
+
"register.field.email.placeholder": "tu@esempio.com",
|
|
2451
2349
|
"register.field.password": "Password",
|
|
2452
2350
|
"register.field.givenName": "Nome",
|
|
2453
2351
|
"register.field.familyName": "Cognome",
|
|
2352
|
+
"register.field.companyName": "Nome azienda",
|
|
2454
2353
|
"register.field.dateOfBirth": "Data di nascita",
|
|
2455
2354
|
"register.field.country": "Paese",
|
|
2456
2355
|
"register.field.country.placeholder": "Seleziona il tuo paese",
|
|
@@ -2503,6 +2402,40 @@ const it = {
|
|
|
2503
2402
|
"error.sign_in_server.body": "Riprova tra qualche minuto"
|
|
2504
2403
|
};
|
|
2505
2404
|
//#endregion
|
|
2405
|
+
//#region src/orchestrator/locales/business.ts
|
|
2406
|
+
const businessLocales = {
|
|
2407
|
+
en: {
|
|
2408
|
+
"identifier.field.email": "Work email",
|
|
2409
|
+
"identifier.field.email.placeholder": "you@company.com",
|
|
2410
|
+
"collect-credentials.field.email": "Work email",
|
|
2411
|
+
"collect-credentials.field.email.placeholder": "you@company.com",
|
|
2412
|
+
"collect-passkey-email.field.email": "Work email",
|
|
2413
|
+
"collect-passkey-email.field.email.placeholder": "you@company.com",
|
|
2414
|
+
"register.field.email": "Work email",
|
|
2415
|
+
"register.field.email.placeholder": "you@company.com"
|
|
2416
|
+
},
|
|
2417
|
+
de: {
|
|
2418
|
+
"identifier.field.email": "Geschäftliche E-Mail",
|
|
2419
|
+
"identifier.field.email.placeholder": "du@unternehmen.com",
|
|
2420
|
+
"collect-credentials.field.email": "Geschäftliche E-Mail",
|
|
2421
|
+
"collect-credentials.field.email.placeholder": "du@unternehmen.com",
|
|
2422
|
+
"collect-passkey-email.field.email": "Geschäftliche E-Mail",
|
|
2423
|
+
"collect-passkey-email.field.email.placeholder": "du@unternehmen.com",
|
|
2424
|
+
"register.field.email": "Geschäftliche E-Mail",
|
|
2425
|
+
"register.field.email.placeholder": "du@unternehmen.com"
|
|
2426
|
+
},
|
|
2427
|
+
it: {
|
|
2428
|
+
"identifier.field.email": "E-mail aziendale",
|
|
2429
|
+
"identifier.field.email.placeholder": "tu@azienda.com",
|
|
2430
|
+
"collect-credentials.field.email": "E-mail aziendale",
|
|
2431
|
+
"collect-credentials.field.email.placeholder": "tu@azienda.com",
|
|
2432
|
+
"collect-passkey-email.field.email": "E-mail aziendale",
|
|
2433
|
+
"collect-passkey-email.field.email.placeholder": "tu@azienda.com",
|
|
2434
|
+
"register.field.email": "E-mail aziendale",
|
|
2435
|
+
"register.field.email.placeholder": "tu@azienda.com"
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
//#endregion
|
|
2506
2439
|
//#region src/orchestrator/locales/index.ts
|
|
2507
2440
|
const builtinLocales = {
|
|
2508
2441
|
en,
|
|
@@ -2541,128 +2474,411 @@ function zitadelAttributionPillInnerHtml() {
|
|
|
2541
2474
|
* `<zl-*>` atoms. Anything outside this list and the manifest registry is
|
|
2542
2475
|
* stripped at sanitise-time.
|
|
2543
2476
|
*/
|
|
2544
|
-
const STRUCTURAL_TAGS = [
|
|
2545
|
-
"div",
|
|
2546
|
-
"span",
|
|
2547
|
-
"p",
|
|
2548
|
-
"small",
|
|
2549
|
-
"strong",
|
|
2550
|
-
"em",
|
|
2551
|
-
"br",
|
|
2552
|
-
"hr",
|
|
2553
|
-
"h1",
|
|
2554
|
-
"h2",
|
|
2555
|
-
"h3",
|
|
2556
|
-
"h4",
|
|
2557
|
-
"h5",
|
|
2558
|
-
"h6",
|
|
2559
|
-
"ul",
|
|
2560
|
-
"ol",
|
|
2561
|
-
"li",
|
|
2562
|
-
"a",
|
|
2563
|
-
"img",
|
|
2564
|
-
"section",
|
|
2565
|
-
"header",
|
|
2566
|
-
"footer",
|
|
2567
|
-
"main",
|
|
2568
|
-
"nav",
|
|
2569
|
-
"aside",
|
|
2570
|
-
"article",
|
|
2571
|
-
"figure",
|
|
2572
|
-
"figcaption",
|
|
2573
|
-
"label",
|
|
2574
|
-
"fieldset",
|
|
2575
|
-
"legend"
|
|
2576
|
-
];
|
|
2477
|
+
const STRUCTURAL_TAGS = [
|
|
2478
|
+
"div",
|
|
2479
|
+
"span",
|
|
2480
|
+
"p",
|
|
2481
|
+
"small",
|
|
2482
|
+
"strong",
|
|
2483
|
+
"em",
|
|
2484
|
+
"br",
|
|
2485
|
+
"hr",
|
|
2486
|
+
"h1",
|
|
2487
|
+
"h2",
|
|
2488
|
+
"h3",
|
|
2489
|
+
"h4",
|
|
2490
|
+
"h5",
|
|
2491
|
+
"h6",
|
|
2492
|
+
"ul",
|
|
2493
|
+
"ol",
|
|
2494
|
+
"li",
|
|
2495
|
+
"a",
|
|
2496
|
+
"img",
|
|
2497
|
+
"section",
|
|
2498
|
+
"header",
|
|
2499
|
+
"footer",
|
|
2500
|
+
"main",
|
|
2501
|
+
"nav",
|
|
2502
|
+
"aside",
|
|
2503
|
+
"article",
|
|
2504
|
+
"figure",
|
|
2505
|
+
"figcaption",
|
|
2506
|
+
"label",
|
|
2507
|
+
"fieldset",
|
|
2508
|
+
"legend"
|
|
2509
|
+
];
|
|
2510
|
+
/**
|
|
2511
|
+
* Attributes templates may carry on structural tags. Per-atom attributes are
|
|
2512
|
+
* derived from the manifest registry below — manifests are the single source
|
|
2513
|
+
* of truth for "what attributes does `<zl-field>` accept?".
|
|
2514
|
+
*/
|
|
2515
|
+
const COMMON_ATTRS = [
|
|
2516
|
+
"id",
|
|
2517
|
+
"class",
|
|
2518
|
+
"role",
|
|
2519
|
+
"slot",
|
|
2520
|
+
"tabindex",
|
|
2521
|
+
"lang",
|
|
2522
|
+
"dir",
|
|
2523
|
+
"alt",
|
|
2524
|
+
"title",
|
|
2525
|
+
"src",
|
|
2526
|
+
"srcset",
|
|
2527
|
+
"sizes",
|
|
2528
|
+
"href",
|
|
2529
|
+
"target",
|
|
2530
|
+
"rel",
|
|
2531
|
+
"style",
|
|
2532
|
+
"name",
|
|
2533
|
+
"for",
|
|
2534
|
+
"data-testid",
|
|
2535
|
+
"data-theme",
|
|
2536
|
+
"hidden"
|
|
2537
|
+
];
|
|
2538
|
+
const CUSTOM_TAG_PATTERN = /^zl-[a-z0-9-]+$/;
|
|
2539
|
+
function buildAllowedTagList() {
|
|
2540
|
+
const set = new Set(STRUCTURAL_TAGS);
|
|
2541
|
+
for (const manifest of manifestRegistry) set.add(manifest.tag);
|
|
2542
|
+
return [...set];
|
|
2543
|
+
}
|
|
2544
|
+
function buildAllowedAttrList() {
|
|
2545
|
+
const set = new Set(COMMON_ATTRS);
|
|
2546
|
+
for (const manifest of manifestRegistry) for (const attr of manifest.attrs) set.add(attr);
|
|
2547
|
+
return [...set];
|
|
2548
|
+
}
|
|
2549
|
+
/**
|
|
2550
|
+
* Returns a sanitiser bound to the current manifest registry. Callers should
|
|
2551
|
+
* cache the result; rebuilding is cheap but allocates a fresh DOMPurify config.
|
|
2552
|
+
*/
|
|
2553
|
+
function createSanitiser() {
|
|
2554
|
+
const config = {
|
|
2555
|
+
USE_PROFILES: { html: true },
|
|
2556
|
+
ADD_TAGS: buildAllowedTagList(),
|
|
2557
|
+
ADD_ATTR: buildAllowedAttrList(),
|
|
2558
|
+
FORBID_TAGS: [
|
|
2559
|
+
"script",
|
|
2560
|
+
"style",
|
|
2561
|
+
"iframe",
|
|
2562
|
+
"object",
|
|
2563
|
+
"embed",
|
|
2564
|
+
"form",
|
|
2565
|
+
"input",
|
|
2566
|
+
"button"
|
|
2567
|
+
],
|
|
2568
|
+
FORBID_ATTR: [
|
|
2569
|
+
"onerror",
|
|
2570
|
+
"onclick",
|
|
2571
|
+
"onload",
|
|
2572
|
+
"onfocus",
|
|
2573
|
+
"onblur"
|
|
2574
|
+
],
|
|
2575
|
+
CUSTOM_ELEMENT_HANDLING: {
|
|
2576
|
+
tagNameCheck: CUSTOM_TAG_PATTERN,
|
|
2577
|
+
attributeNameCheck: /^(?:[a-z][a-z0-9-]*)$/,
|
|
2578
|
+
allowCustomizedBuiltInElements: false
|
|
2579
|
+
},
|
|
2580
|
+
KEEP_CONTENT: true,
|
|
2581
|
+
ALLOW_DATA_ATTR: true,
|
|
2582
|
+
ALLOW_ARIA_ATTR: true,
|
|
2583
|
+
RETURN_TRUSTED_TYPE: false
|
|
2584
|
+
};
|
|
2585
|
+
return function sanitise(html) {
|
|
2586
|
+
const result = DOMPurify.sanitize(html, config);
|
|
2587
|
+
return typeof result === "string" ? result : String(result);
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
//#endregion
|
|
2591
|
+
//#region src/orchestrator/branding-to-tokens.ts
|
|
2592
|
+
/**
|
|
2593
|
+
* Branding -> CSS token bridge.
|
|
2594
|
+
*
|
|
2595
|
+
* Per `docs/design/branding/README.md` the orchestrator owns theming. Templates
|
|
2596
|
+
* never emit `<style>` blocks; they only structure HTML. This module translates
|
|
2597
|
+
* the `Branding` JSON into a CSSStyleSheet of `:host { --zl-* }` declarations
|
|
2598
|
+
* and applies it via `shadowRoot.adoptedStyleSheets`.
|
|
2599
|
+
*
|
|
2600
|
+
* Dark-mode overrides are emitted as `:host([data-theme="dark"]) { ... }`
|
|
2601
|
+
* (matching `docs/design/branding/tokens.md`). Resolution between
|
|
2602
|
+
* `light | dark | auto` happens in `<zitadel-login>`.
|
|
2603
|
+
*
|
|
2604
|
+
* Base layer: the design-tokens package ships the full `--zl-*` set as both a
|
|
2605
|
+
* `.css` file (for host pages) and a `tokensCss` string (for shadow roots and
|
|
2606
|
+
* SSR). We adopt the string into every orchestrator shadow root so atoms
|
|
2607
|
+
* paint correctly even when the host page didn't `@import` tokens.css — the
|
|
2608
|
+
* orchestrator is meant to drop into any page.
|
|
2609
|
+
*/
|
|
2610
|
+
const RADIUS_MAP = {
|
|
2611
|
+
none: "0",
|
|
2612
|
+
sm: "0.25rem",
|
|
2613
|
+
md: "0.5rem",
|
|
2614
|
+
lg: "0.75rem",
|
|
2615
|
+
full: "9999px"
|
|
2616
|
+
};
|
|
2617
|
+
const DENSITY_MAP = {
|
|
2618
|
+
compact: {
|
|
2619
|
+
"--zl-spacing-03": "0.75rem",
|
|
2620
|
+
"--zl-spacing-05": "1.5rem"
|
|
2621
|
+
},
|
|
2622
|
+
regular: {},
|
|
2623
|
+
comfortable: {
|
|
2624
|
+
"--zl-spacing-03": "1.25rem",
|
|
2625
|
+
"--zl-spacing-05": "2.25rem"
|
|
2626
|
+
}
|
|
2627
|
+
};
|
|
2628
|
+
const PALETTE_MAP = {
|
|
2629
|
+
primary: ["--zl-color-surface-default-white"],
|
|
2630
|
+
on_primary: ["--zl-color-text-button-default"],
|
|
2631
|
+
background: ["--zl-color-surface-default-black"],
|
|
2632
|
+
surface: ["--zl-color-surface-default-primary-gray"],
|
|
2633
|
+
muted: ["--zl-color-surface-default-secondary-gray"],
|
|
2634
|
+
border: ["--zl-color-border-default-gray-200", "--zl-color-border-default-gray-100"],
|
|
2635
|
+
text: ["--zl-color-text-primary-white"],
|
|
2636
|
+
text_muted: ["--zl-color-text-secondary-gray"],
|
|
2637
|
+
link: ["--zl-color-text-subtitle-pink"],
|
|
2638
|
+
success: [
|
|
2639
|
+
"--zl-color-text-success",
|
|
2640
|
+
"--zl-color-border-success",
|
|
2641
|
+
"--zl-color-icon-success"
|
|
2642
|
+
],
|
|
2643
|
+
warning: ["--zl-color-text-subtitle-orange"],
|
|
2644
|
+
error: [
|
|
2645
|
+
"--zl-color-text-error",
|
|
2646
|
+
"--zl-color-border-error",
|
|
2647
|
+
"--zl-color-icon-error"
|
|
2648
|
+
]
|
|
2649
|
+
};
|
|
2650
|
+
/**
|
|
2651
|
+
* Build a CSS string of `:host { --zl-* }` declarations from a Branding
|
|
2652
|
+
* payload. Light values land on `:host`; dark overrides land on
|
|
2653
|
+
* `:host([data-theme="dark"])`.
|
|
2654
|
+
*/
|
|
2655
|
+
function buildBrandingStylesheet(branding, options = {}) {
|
|
2656
|
+
const lightDecls = collectDeclarations(branding);
|
|
2657
|
+
const darkPalette = branding?.theme?.dark?.palette;
|
|
2658
|
+
const darkDecls = darkPalette ? mapPalette(darkPalette) : {};
|
|
2659
|
+
const blocks = [];
|
|
2660
|
+
if (Object.keys(lightDecls).length > 0) blocks.push(formatBlock(":host", lightDecls));
|
|
2661
|
+
if (Object.keys(darkDecls).length > 0) blocks.push(formatBlock(":host([data-theme=\"dark\"])", darkDecls));
|
|
2662
|
+
if (options.resolvedTheme === "dark" && darkPalette) {
|
|
2663
|
+
const merged = {
|
|
2664
|
+
...lightDecls,
|
|
2665
|
+
...darkDecls
|
|
2666
|
+
};
|
|
2667
|
+
blocks.length = 0;
|
|
2668
|
+
blocks.push(formatBlock(":host", merged));
|
|
2669
|
+
}
|
|
2670
|
+
return blocks.join("\n");
|
|
2671
|
+
}
|
|
2672
|
+
function collectDeclarations(branding) {
|
|
2673
|
+
if (!branding) return {};
|
|
2674
|
+
const decls = {};
|
|
2675
|
+
Object.assign(decls, mapPalette(branding.palette));
|
|
2676
|
+
Object.assign(decls, mapTypography(branding.typography));
|
|
2677
|
+
Object.assign(decls, mapShape(branding.shape));
|
|
2678
|
+
return decls;
|
|
2679
|
+
}
|
|
2680
|
+
function mapPalette(palette) {
|
|
2681
|
+
if (!palette) return {};
|
|
2682
|
+
const out = {};
|
|
2683
|
+
for (const [key, varNames] of Object.entries(PALETTE_MAP)) {
|
|
2684
|
+
const value = palette[key];
|
|
2685
|
+
if (typeof value === "string" && value.length > 0) for (const varName of varNames) out[varName] = value;
|
|
2686
|
+
}
|
|
2687
|
+
return out;
|
|
2688
|
+
}
|
|
2689
|
+
function mapTypography(typography) {
|
|
2690
|
+
if (!typography) return {};
|
|
2691
|
+
const out = {};
|
|
2692
|
+
if (typography.font_family) {
|
|
2693
|
+
out["--zl-font-family-sans"] = typography.font_family;
|
|
2694
|
+
out["--zl-font-family-heading"] = typography.font_family;
|
|
2695
|
+
}
|
|
2696
|
+
if (typography.font_family_mono) out["--zl-font-family-mono"] = typography.font_family_mono;
|
|
2697
|
+
const scale = clamp(typography.scale ?? 1, .75, 1.25);
|
|
2698
|
+
if (scale !== 1) out["--zl-font-scale"] = `${scale}`;
|
|
2699
|
+
return out;
|
|
2700
|
+
}
|
|
2701
|
+
function mapShape(shape) {
|
|
2702
|
+
if (!shape) return {};
|
|
2703
|
+
const out = {};
|
|
2704
|
+
if (shape.radius && RADIUS_MAP[shape.radius]) {
|
|
2705
|
+
const radius = RADIUS_MAP[shape.radius];
|
|
2706
|
+
out["--zl-radius-s"] = radius;
|
|
2707
|
+
out["--zl-radius-m"] = radius === "0" ? "0" : `calc(${radius} * 1.5)`;
|
|
2708
|
+
out["--zl-radius-l"] = radius === "0" ? "0" : `calc(${radius} * 2)`;
|
|
2709
|
+
}
|
|
2710
|
+
if (shape.density) Object.assign(out, DENSITY_MAP[shape.density]);
|
|
2711
|
+
return out;
|
|
2712
|
+
}
|
|
2713
|
+
function formatBlock(selector, decls) {
|
|
2714
|
+
return `${selector} {\n${Object.entries(decls).map(([prop, value]) => ` ${prop}: ${value};`).join("\n")}\n}`;
|
|
2715
|
+
}
|
|
2716
|
+
function clamp(value, min, max) {
|
|
2717
|
+
if (Number.isNaN(value)) return min;
|
|
2718
|
+
return Math.min(max, Math.max(min, value));
|
|
2719
|
+
}
|
|
2720
|
+
let baseTokenSheet;
|
|
2721
|
+
function getBaseTokenSheet() {
|
|
2722
|
+
if (typeof CSSStyleSheet === "undefined") return void 0;
|
|
2723
|
+
if (!baseTokenSheet) {
|
|
2724
|
+
baseTokenSheet = new CSSStyleSheet();
|
|
2725
|
+
baseTokenSheet.replaceSync(tokensCss.replaceAll(":root,\n[data-theme=\"dark\"]", ":host,\n:host([data-theme=\"dark\"])").replaceAll("[data-theme=\"light\"]", ":host([data-theme=\"light\"])"));
|
|
2726
|
+
}
|
|
2727
|
+
return baseTokenSheet;
|
|
2728
|
+
}
|
|
2729
|
+
/**
|
|
2730
|
+
* Adopt the design-system base token layer onto a `ShadowRoot`. Safe to call
|
|
2731
|
+
* many times — the underlying constructable sheet is shared across every
|
|
2732
|
+
* orchestrator instance and de-duplicated in the adopted list.
|
|
2733
|
+
*/
|
|
2734
|
+
function applyBaseTokens(shadowRoot) {
|
|
2735
|
+
const sheet = getBaseTokenSheet();
|
|
2736
|
+
if (!sheet) return;
|
|
2737
|
+
const existing = Array.isArray(shadowRoot.adoptedStyleSheets) ? shadowRoot.adoptedStyleSheets : [];
|
|
2738
|
+
if (existing.includes(sheet)) return;
|
|
2739
|
+
try {
|
|
2740
|
+
shadowRoot.adoptedStyleSheets = [sheet, ...existing];
|
|
2741
|
+
} catch {}
|
|
2742
|
+
}
|
|
2743
|
+
/**
|
|
2744
|
+
* Apply a branding payload as a `--zl-*` overrides layer on top of the base
|
|
2745
|
+
* token sheet. Subsequent calls replace the previous override sheet so a new
|
|
2746
|
+
* branding payload paints cleanly without leaking older declarations.
|
|
2747
|
+
*
|
|
2748
|
+
* Callers should run `applyBaseTokens(shadowRoot)` first (once per shadow
|
|
2749
|
+
* root) so the base values exist before branding patches them.
|
|
2750
|
+
*/
|
|
2751
|
+
function applyBrandingTokens(shadowRoot, branding, resolvedTheme) {
|
|
2752
|
+
const css = buildBrandingStylesheet(branding, { resolvedTheme });
|
|
2753
|
+
if (typeof CSSStyleSheet === "undefined") return;
|
|
2754
|
+
const sheet = new CSSStyleSheet();
|
|
2755
|
+
sheet.replaceSync(css);
|
|
2756
|
+
const previous = shadowRoot.__zlTokenSheet;
|
|
2757
|
+
shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets.filter((s) => s !== previous), sheet];
|
|
2758
|
+
shadowRoot.__zlTokenSheet = sheet;
|
|
2759
|
+
}
|
|
2760
|
+
/**
|
|
2761
|
+
* Standalone branding→theme resolution for callers outside the orchestrator
|
|
2762
|
+
* (SSR, tests, embedders composing atoms by hand). `<zitadel-login>` itself
|
|
2763
|
+
* uses {@link ThemeController}, which layers the element's `theme` property
|
|
2764
|
+
* and a variant-derived fallback on top of the same branding input.
|
|
2765
|
+
*
|
|
2766
|
+
* Defaults to dark: the design system's primary surface, and the mode a
|
|
2767
|
+
* hosted login page renders when a tenant states no preference.
|
|
2768
|
+
*/
|
|
2769
|
+
function resolveTheme(branding) {
|
|
2770
|
+
const mode = branding?.theme?.mode ?? "dark";
|
|
2771
|
+
if (mode === "light") return "light";
|
|
2772
|
+
if (mode === "dark") return "dark";
|
|
2773
|
+
if (typeof matchMedia === "function") return matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
|
|
2774
|
+
return "dark";
|
|
2775
|
+
}
|
|
2776
|
+
//#endregion
|
|
2777
|
+
//#region src/orchestrator/font-loader.ts
|
|
2778
|
+
/**
|
|
2779
|
+
* Document-level font stylesheet injection for the orchestrator surfaces.
|
|
2780
|
+
*
|
|
2781
|
+
* Two faces can be linked into the host document's `<head>`:
|
|
2782
|
+
*
|
|
2783
|
+
* - The **design-system default** (`applyDefaultFont`) — the brand body face
|
|
2784
|
+
* (Arimo, named first in `--zl-font-family-sans`). The component ships this
|
|
2785
|
+
* so the auth screens render in the brand font out of the box, even when the
|
|
2786
|
+
* server returns no branding. This is the "default users can override" layer.
|
|
2787
|
+
* - The **tenant override** (`applyFontUrl`) — `branding.font_url`, resolved
|
|
2788
|
+
* server-side from the app → team → project branding hierarchy.
|
|
2789
|
+
*
|
|
2790
|
+
* Both must live at document level, not inside the shadow root: browsers ignore
|
|
2791
|
+
* `@font-face` rules declared in shadow-tree stylesheets, so a shadow-scoped
|
|
2792
|
+
* link would never register the font faces and every branded font silently
|
|
2793
|
+
* falls back to the system stack. `font-family` references inside the shadow
|
|
2794
|
+
* tree resolve against document-level faces.
|
|
2795
|
+
*
|
|
2796
|
+
* Each `<link>` is shared per document but **owned collectively**: every
|
|
2797
|
+
* calling surface (identified by its shadow root) registers the href it wants,
|
|
2798
|
+
* and passing `null`/`undefined` withdraws only that surface's registration —
|
|
2799
|
+
* never the link another surface still needs. Multiple surfaces now call these
|
|
2800
|
+
* helpers on every update (`<zitadel-login>` and `<zitadel-session>` via the
|
|
2801
|
+
* shared surface base), so a widget-mode session mounting next to a page-mode
|
|
2802
|
+
* login must not strip the login's fonts. The rendered href is the most
|
|
2803
|
+
* recently registered one (matching the old single-owner behavior when only
|
|
2804
|
+
* one surface is present); the link is removed once no surface wants it.
|
|
2805
|
+
* Registrations from since-disconnected surfaces are pruned lazily on the
|
|
2806
|
+
* next call, so a removed element neither pins a link forever nor leaks its
|
|
2807
|
+
* shadow root — while the link itself stays in place until another surface
|
|
2808
|
+
* actually updates, exactly like the pre-ownership behavior.
|
|
2809
|
+
*/
|
|
2810
|
+
const TENANT_LINK_ID = "zl-font-link";
|
|
2811
|
+
const DEFAULT_LINK_ID = "zl-default-font-link";
|
|
2812
|
+
/**
|
|
2813
|
+
* Design-system default brand font. Arimo is the Figma body face and leads the
|
|
2814
|
+
* `--zl-font-family-sans` stack; loading it here makes the unbranded auth UI
|
|
2815
|
+
* paint in the brand font instead of the system fallback. Tenants override it
|
|
2816
|
+
* with `branding.font_url`. Exported so a privacy- or offline-sensitive
|
|
2817
|
+
* deployment can self-host the same face and swap this single URL.
|
|
2818
|
+
*/
|
|
2819
|
+
const DEFAULT_BRAND_FONT_HREF = "https://fonts.googleapis.com/css2?family=Arimo:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap";
|
|
2820
|
+
/** Per-document, per-link-id map of each owner's desired href. */
|
|
2821
|
+
const registries = /* @__PURE__ */ new WeakMap();
|
|
2577
2822
|
/**
|
|
2578
|
-
*
|
|
2579
|
-
*
|
|
2580
|
-
*
|
|
2823
|
+
* Inject the tenant's `branding.font_url` stylesheet. Pass `null`/`undefined`
|
|
2824
|
+
* to withdraw this surface's registration (e.g. when a flow resolves with no
|
|
2825
|
+
* tenant font); the link disappears once no surface registers a URL.
|
|
2581
2826
|
*/
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
"class",
|
|
2585
|
-
"role",
|
|
2586
|
-
"slot",
|
|
2587
|
-
"tabindex",
|
|
2588
|
-
"lang",
|
|
2589
|
-
"dir",
|
|
2590
|
-
"alt",
|
|
2591
|
-
"title",
|
|
2592
|
-
"src",
|
|
2593
|
-
"srcset",
|
|
2594
|
-
"sizes",
|
|
2595
|
-
"href",
|
|
2596
|
-
"target",
|
|
2597
|
-
"rel",
|
|
2598
|
-
"style",
|
|
2599
|
-
"name",
|
|
2600
|
-
"for",
|
|
2601
|
-
"data-testid",
|
|
2602
|
-
"data-theme",
|
|
2603
|
-
"hidden"
|
|
2604
|
-
];
|
|
2605
|
-
const CUSTOM_TAG_PATTERN = /^zl-[a-z0-9-]+$/;
|
|
2606
|
-
function buildAllowedTagList() {
|
|
2607
|
-
const set = new Set(STRUCTURAL_TAGS);
|
|
2608
|
-
for (const manifest of manifestRegistry) set.add(manifest.tag);
|
|
2609
|
-
return [...set];
|
|
2610
|
-
}
|
|
2611
|
-
function buildAllowedAttrList() {
|
|
2612
|
-
const set = new Set(COMMON_ATTRS);
|
|
2613
|
-
for (const manifest of manifestRegistry) for (const attr of manifest.attrs) set.add(attr);
|
|
2614
|
-
return [...set];
|
|
2827
|
+
function applyFontUrl(shadowRoot, fontUrl) {
|
|
2828
|
+
applyLinkedStylesheet(shadowRoot, TENANT_LINK_ID, fontUrl);
|
|
2615
2829
|
}
|
|
2616
2830
|
/**
|
|
2617
|
-
*
|
|
2618
|
-
*
|
|
2831
|
+
* Inject the design-system default font stylesheet. Defaults to
|
|
2832
|
+
* {@link DEFAULT_BRAND_FONT_HREF}; pass `null` to withdraw this surface's
|
|
2833
|
+
* registration (the orchestrator does this when a tenant `font_url` takes
|
|
2834
|
+
* over, to avoid a redundant request).
|
|
2619
2835
|
*/
|
|
2620
|
-
function
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
};
|
|
2836
|
+
function applyDefaultFont(shadowRoot, href = DEFAULT_BRAND_FONT_HREF) {
|
|
2837
|
+
applyLinkedStylesheet(shadowRoot, DEFAULT_LINK_ID, href);
|
|
2838
|
+
}
|
|
2839
|
+
function applyLinkedStylesheet(shadowRoot, linkId, href) {
|
|
2840
|
+
const ownerDocument = shadowRoot.ownerDocument ?? document;
|
|
2841
|
+
let registry = registries.get(ownerDocument);
|
|
2842
|
+
if (!registry) {
|
|
2843
|
+
registry = /* @__PURE__ */ new Map();
|
|
2844
|
+
registries.set(ownerDocument, registry);
|
|
2845
|
+
}
|
|
2846
|
+
let desires = registry.get(linkId);
|
|
2847
|
+
if (!desires) {
|
|
2848
|
+
desires = /* @__PURE__ */ new Map();
|
|
2849
|
+
registry.set(linkId, desires);
|
|
2850
|
+
}
|
|
2851
|
+
for (const owner of desires.keys()) if (!owner.host.isConnected) desires.delete(owner);
|
|
2852
|
+
if (href) {
|
|
2853
|
+
desires.delete(shadowRoot);
|
|
2854
|
+
desires.set(shadowRoot, href);
|
|
2855
|
+
} else desires.delete(shadowRoot);
|
|
2856
|
+
const effective = [...desires.values()].at(-1) ?? null;
|
|
2857
|
+
const existing = ownerDocument.head.querySelector(`link#${linkId}`);
|
|
2858
|
+
if (!effective) {
|
|
2859
|
+
existing?.remove();
|
|
2860
|
+
return;
|
|
2861
|
+
}
|
|
2862
|
+
if (!href) {
|
|
2863
|
+
if (existing && existing.href !== effective) existing.href = effective;
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
const link = existing ?? ownerDocument.createElement("link");
|
|
2867
|
+
link.id = linkId;
|
|
2868
|
+
link.rel = "stylesheet";
|
|
2869
|
+
if (link.href !== effective) link.href = effective;
|
|
2870
|
+
if (!existing) ownerDocument.head.appendChild(link);
|
|
2656
2871
|
}
|
|
2657
|
-
//#endregion
|
|
2658
|
-
//#region src/orchestrator/templates/layout-chrome.css?inline
|
|
2659
|
-
var layout_chrome_default = ":host {\n background: var(--zl-color-surface-default-black, #0f0f11);\n min-height: 100%;\n color: var(--zl-color-text-primary-white, #f4f4f6);\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n display: block;\n}\n\n.zl-mount {\n background: inherit;\n min-height: 100vh;\n color: inherit;\n flex-direction: column;\n display: flex;\n}\n\n.zl-mount > zl-page-shell, .zl-mount > [data-zl-template-root] {\n flex: auto;\n}\n\n.zl-attribution {\n background: none;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.zl-attribution[hidden] {\n display: none;\n}\n\n.zl-card-title {\n font-family: var(--zl-font-family-heading, var(--zl-font-family-sans, system-ui, sans-serif));\n letter-spacing: -.02em;\n color: var(--zl-color-text-primary-white, #f4f4f6);\n text-align: left;\n margin: 0;\n font-size: 2rem;\n font-weight: 700;\n line-height: 2.5rem;\n}\n\n.zl-card-subtitle {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-text-primary-white, #f4f4f6);\n margin: 0;\n font-size: .875rem;\n font-weight: 400;\n line-height: 1.25rem;\n}\n\n.zl-card-nav {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-text-primary-white, #f4f4f6);\n text-align: left;\n margin: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5rem;\n}\n\n.zl-card-nav__link {\n font: inherit;\n line-height: inherit;\n color: var(--zl-color-icon-default-purple, #bba5e4);\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-nav__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-nav__link:focus-visible {\n outline: var(--zl-focus-width, 2px) solid var(--zl-focus-color, #f4f4f6);\n outline-offset: var(--zl-focus-offset, 2px);\n}\n\n.zl-card-forgot {\n text-align: left;\n margin: 0;\n}\n\n.zl-card-forgot__link {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-icon-default-purple, #bba5e4);\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5rem;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-forgot__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-forgot__link:focus-visible {\n outline: var(--zl-focus-width, 2px) solid var(--zl-focus-color, #f4f4f6);\n outline-offset: var(--zl-focus-offset, 2px);\n}\n";
|
|
2660
2872
|
//#endregion
|
|
2661
2873
|
//#region src/orchestrator/theme-controller.ts
|
|
2662
2874
|
const DARK_QUERY = "(prefers-color-scheme: dark)";
|
|
2663
2875
|
var ThemeController = class {
|
|
2664
2876
|
host;
|
|
2665
2877
|
branding;
|
|
2878
|
+
/** Element-level `theme` property — the embedding developer's explicit call. */
|
|
2879
|
+
explicitMode;
|
|
2880
|
+
/** Used when neither the element nor the branding payload names a mode. */
|
|
2881
|
+
fallbackMode = "dark";
|
|
2666
2882
|
mediaQuery = null;
|
|
2667
2883
|
_theme = "dark";
|
|
2668
2884
|
constructor(host) {
|
|
@@ -2676,6 +2892,19 @@ var ThemeController = class {
|
|
|
2676
2892
|
this.branding = branding;
|
|
2677
2893
|
this.refresh();
|
|
2678
2894
|
}
|
|
2895
|
+
/**
|
|
2896
|
+
* Sets the element-level preference and the fallback used when neither the
|
|
2897
|
+
* element nor the branding payload names a mode. Precedence, strongest
|
|
2898
|
+
* first: element `theme` property → `branding.theme.mode` → fallback. The
|
|
2899
|
+
* element wins because the page embedding the widget knows its own surface
|
|
2900
|
+
* better than the tenant's stored branding does.
|
|
2901
|
+
*/
|
|
2902
|
+
setModePreference(explicit, fallback) {
|
|
2903
|
+
if (this.explicitMode === explicit && this.fallbackMode === fallback) return;
|
|
2904
|
+
this.explicitMode = explicit;
|
|
2905
|
+
this.fallbackMode = fallback;
|
|
2906
|
+
this.refresh();
|
|
2907
|
+
}
|
|
2679
2908
|
hostConnected() {
|
|
2680
2909
|
this.refresh();
|
|
2681
2910
|
}
|
|
@@ -2683,7 +2912,7 @@ var ThemeController = class {
|
|
|
2683
2912
|
this.detach();
|
|
2684
2913
|
}
|
|
2685
2914
|
refresh() {
|
|
2686
|
-
const mode = this.branding?.theme?.mode ??
|
|
2915
|
+
const mode = this.explicitMode ?? this.branding?.theme?.mode ?? this.fallbackMode;
|
|
2687
2916
|
if (mode === "auto") {
|
|
2688
2917
|
this.attach();
|
|
2689
2918
|
this.update(this.mediaQuery?.matches === false ? "light" : "dark");
|
|
@@ -2712,8 +2941,72 @@ var ThemeController = class {
|
|
|
2712
2941
|
}
|
|
2713
2942
|
};
|
|
2714
2943
|
//#endregion
|
|
2944
|
+
//#region src/orchestrator/surface.ts
|
|
2945
|
+
/**
|
|
2946
|
+
* Shared base for the orchestrator host elements (`<zitadel-login>`,
|
|
2947
|
+
* `<zitadel-session>`) that render a themable page/widget surface.
|
|
2948
|
+
*
|
|
2949
|
+
* Owns the `variant` / `theme` public properties, the {@link ThemeController},
|
|
2950
|
+
* and the per-update surface application (base tokens, branding overrides,
|
|
2951
|
+
* font links, and the `data-theme` host stamp). Subclasses call
|
|
2952
|
+
* {@link applySurfaceTheme} from their `willUpdate` so the resolved theme is
|
|
2953
|
+
* committed in the same update cycle that reads it.
|
|
2954
|
+
*
|
|
2955
|
+
* Not a registered custom element — purely an implementation-sharing base.
|
|
2956
|
+
*/
|
|
2957
|
+
var ZitadelSurface = class extends LitElement {
|
|
2958
|
+
#_variant_accessor_storage = "widget";
|
|
2959
|
+
get variant() {
|
|
2960
|
+
return this.#_variant_accessor_storage;
|
|
2961
|
+
}
|
|
2962
|
+
set variant(value) {
|
|
2963
|
+
this.#_variant_accessor_storage = value;
|
|
2964
|
+
}
|
|
2965
|
+
#_theme_accessor_storage = "";
|
|
2966
|
+
get theme() {
|
|
2967
|
+
return this.#_theme_accessor_storage;
|
|
2968
|
+
}
|
|
2969
|
+
set theme(value) {
|
|
2970
|
+
this.#_theme_accessor_storage = value;
|
|
2971
|
+
}
|
|
2972
|
+
themeController = new ThemeController(this);
|
|
2973
|
+
/**
|
|
2974
|
+
* Resolve the active theme and commit it to this element's surface: adopt
|
|
2975
|
+
* the base token sheet, layer the branding overrides, manage the
|
|
2976
|
+
* document-level font links, and stamp `data-theme` / `data-theme-dark` on
|
|
2977
|
+
* the host so token lookups (and host-page selectors) flip with the mode.
|
|
2978
|
+
* `branding` is `undefined` for surfaces without a tenant payload — the
|
|
2979
|
+
* theme then resolves through the element preference and variant fallback.
|
|
2980
|
+
*/
|
|
2981
|
+
applySurfaceTheme(branding) {
|
|
2982
|
+
this.themeController.setModePreference(this.theme === "" ? void 0 : this.theme, this.variant === "page" ? "dark" : "auto");
|
|
2983
|
+
const root = this.shadowRoot;
|
|
2984
|
+
if (root) {
|
|
2985
|
+
applyBaseTokens(root);
|
|
2986
|
+
applyBrandingTokens(root, branding, this.themeController.theme);
|
|
2987
|
+
const tenantFontUrl = branding?.font_url ?? null;
|
|
2988
|
+
applyDefaultFont(root, this.variant === "page" && !tenantFontUrl ? void 0 : null);
|
|
2989
|
+
applyFontUrl(root, tenantFontUrl);
|
|
2990
|
+
}
|
|
2991
|
+
this.dataset.theme = this.themeController.theme;
|
|
2992
|
+
this.toggleAttribute("data-theme-dark", this.themeController.theme === "dark");
|
|
2993
|
+
}
|
|
2994
|
+
};
|
|
2995
|
+
__decorate([property({
|
|
2996
|
+
type: String,
|
|
2997
|
+
reflect: true
|
|
2998
|
+
})], ZitadelSurface.prototype, "variant", null);
|
|
2999
|
+
__decorate([property({ type: String })], ZitadelSurface.prototype, "theme", null);
|
|
3000
|
+
//#endregion
|
|
3001
|
+
//#region src/orchestrator/templates/layout-chrome.css?inline
|
|
3002
|
+
var layout_chrome_default = ":host {\n color: var(--zl-color-text-primary-white, #f4f4f6);\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n background: none;\n display: block;\n}\n\n:host([variant=\"page\"]) {\n background: var(--zl-color-surface-default-black, #0f0f11);\n min-height: 100%;\n}\n\n.zl-mount {\n min-height: var(--zl-page-min-height, auto);\n background: inherit;\n color: inherit;\n flex-direction: column;\n display: flex;\n container-type: inline-size;\n}\n\n:host([variant=\"page\"]) .zl-mount {\n min-height: var(--zl-page-min-height, 100vh);\n}\n\n.zl-mount > zl-page-shell, .zl-mount > [data-zl-template-root] {\n flex: auto;\n}\n\n.zl-attribution {\n background: none;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.zl-attribution[hidden] {\n display: none;\n}\n\n.zl-card-title {\n font-family: var(--zl-font-family-heading, var(--zl-font-family-sans, system-ui, sans-serif));\n letter-spacing: -.02em;\n color: var(--zl-color-text-primary-white, #f4f4f6);\n text-align: left;\n margin: 0;\n font-size: 2rem;\n font-weight: 700;\n line-height: 2.5rem;\n}\n\n.zl-card-subtitle {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-text-primary-white, #f4f4f6);\n margin: 0;\n font-size: .875rem;\n font-weight: 400;\n line-height: 1.25rem;\n}\n\n.zl-card-nav {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-text-primary-white, #f4f4f6);\n text-align: left;\n margin: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5rem;\n}\n\n.zl-card-nav__link {\n font: inherit;\n line-height: inherit;\n color: var(--zl-color-icon-default-purple, #bba5e4);\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-nav__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-nav__link:focus-visible {\n outline: var(--zl-focus-width, 2px) solid var(--zl-focus-color, #f4f4f6);\n outline-offset: var(--zl-focus-offset, 2px);\n}\n\n.zl-card-forgot {\n text-align: left;\n margin: 0;\n}\n\n.zl-card-forgot__link {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-icon-default-purple, #bba5e4);\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5rem;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-forgot__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-forgot__link:focus-visible {\n outline: var(--zl-focus-width, 2px) solid var(--zl-focus-color, #f4f4f6);\n outline-offset: var(--zl-focus-offset, 2px);\n}\n\n.zl-split {\n grid-template-columns: var(--zl-split-columns, minmax(0, 1fr) minmax(0, 1fr));\n align-items: var(--zl-split-align, center);\n gap: var(--zl-spacing-05, 2rem);\n min-height: 100%;\n display: grid;\n}\n\n.zl-split__brand {\n justify-content: center;\n align-items: center;\n gap: var(--zl-spacing-04, 1.5rem);\n flex-direction: column;\n padding: 52px 16px;\n display: flex;\n}\n\n.zl-split__logo {\n max-width: min(16rem, 60%);\n}\n\n.zl-split__hero {\n border-radius: var(--zl-radius-m, .75rem);\n object-fit: cover;\n max-width: 100%;\n}\n\n.zl-split__form {\n justify-content: center;\n align-items: center;\n padding: 16px;\n display: flex;\n}\n\n.zl-split__compact {\n max-width: min(12rem, 60%);\n display: none;\n}\n\nimg.zl-split__compact {\n max-height: 2.5rem;\n}\n\nimg.zl-split__compact--hero {\n border-radius: var(--zl-radius-m, .75rem);\n object-fit: cover;\n width: 100%;\n max-width: 100%;\n max-height: 6rem;\n}\n\n.zl-split--right .zl-split__brand {\n order: 2;\n}\n\n.zl-split--right .zl-split__form {\n order: 1;\n}\n\n@container (width <= 48rem) {\n .zl-split {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .zl-split__brand {\n display: var(--zl-split-brand-mobile, none);\n }\n\n .zl-split__form {\n gap: var(--zl-spacing-04, 1.5rem);\n flex-direction: column;\n }\n\n .zl-split__compact {\n display: block;\n }\n}\n\n.zl-hero {\n text-align: left;\n width: 100%;\n max-width: 34rem;\n color: var(--zl-color-text-primary-white, #f4f4f6);\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n flex-direction: column;\n display: flex;\n}\n\n.zl-hero__nav {\n margin-bottom: var(--zl-spacing-05, 2rem);\n justify-content: space-between;\n align-items: center;\n display: flex;\n}\n\n.zl-hero__brand {\n align-items: center;\n gap: var(--zl-spacing-02, .5rem);\n letter-spacing: -.01em;\n font-size: 1.125rem;\n font-weight: 700;\n display: inline-flex;\n}\n\n.zl-hero__brand-logo {\n max-height: 1.75rem;\n}\n\n.zl-hero__brand-mark {\n border-radius: var(--zl-radius-s, .5rem);\n background: linear-gradient(135deg,\n var(--zl-color-icon-default-purple, #bba5e4),\n var(--zl-color-icon-default-pink, #f48aa0));\n width: 1.75rem;\n height: 1.75rem;\n display: inline-flex;\n}\n\n.zl-hero__compact-brand {\n overflow-wrap: anywhere;\n letter-spacing: -.01em;\n max-width: 100%;\n color: var(--zl-color-text-primary-white, #f4f4f6);\n margin: 0;\n font-size: 1.125rem;\n font-weight: 700;\n}\n\n.zl-hero__nav-links {\n gap: var(--zl-spacing-04, 1.5rem);\n color: var(--zl-color-text-secondary-gray, #bfbfcf);\n font-size: .9375rem;\n display: flex;\n}\n\n.zl-hero__nav-links a {\n color: inherit;\n text-decoration: none;\n}\n\n.zl-hero__nav-links a:hover {\n text-decoration: underline;\n}\n\n.zl-hero__eyebrow {\n margin: 0 0 var(--zl-spacing-03, 1rem);\n letter-spacing: .08em;\n text-transform: uppercase;\n color: var(--zl-color-text-subtitle-purple, #bba5e4);\n font-size: .8125rem;\n font-weight: 600;\n}\n\n.zl-hero__headline {\n margin: 0 0 var(--zl-spacing-03, 1rem);\n font-family: var(--zl-font-family-heading, var(--zl-font-family-sans, system-ui, sans-serif));\n letter-spacing: -.02em;\n font-size: 2.75rem;\n font-weight: 700;\n line-height: 1.1;\n}\n\n.zl-hero__sub {\n margin: 0 0 var(--zl-spacing-05, 2rem);\n max-width: 30rem;\n color: var(--zl-color-text-secondary-gray, #bfbfcf);\n font-size: 1.0625rem;\n line-height: 1.6;\n}\n\n.zl-hero__bullets {\n gap: var(--zl-spacing-02, .5rem);\n margin: 0 0 var(--zl-spacing-05, 2rem);\n flex-direction: column;\n padding: 0;\n list-style: none;\n display: flex;\n}\n\n.zl-hero__bullets li {\n align-items: center;\n gap: var(--zl-spacing-02, .5rem);\n font-size: 1rem;\n display: flex;\n}\n\n.zl-hero__bullets li:before {\n content: \"✓\";\n color: var(--zl-color-icon-default-purple, #bba5e4);\n font-weight: 700;\n}\n\n.zl-hero__footnote {\n padding-top: var(--zl-spacing-04, 1.5rem);\n border-top: 1px solid var(--zl-color-border-default-black, #252528);\n color: var(--zl-color-text-disabled, #686883);\n margin: 0;\n font-size: .8125rem;\n}\n\n.zl-hero__footnote strong {\n color: var(--zl-color-text-secondary-gray, #bfbfcf);\n font-weight: 600;\n}\n\n.zl-minimal {\n gap: var(--zl-spacing-04, 1.5rem);\n flex-direction: column;\n width: min(24rem, 100%);\n margin-inline: auto;\n display: flex;\n}\n\n:host([variant=\"page\"]) .zl-minimal {\n padding: 52px 16px;\n}\n";
|
|
3003
|
+
//#endregion
|
|
2715
3004
|
//#region src/orchestrator/zitadel-login.ts
|
|
2716
|
-
|
|
3005
|
+
/** Narrow a rendered named element to the `formValue` field-atom contract. */
|
|
3006
|
+
function isFieldAtom(el) {
|
|
3007
|
+
return typeof el.formValue === "string";
|
|
3008
|
+
}
|
|
3009
|
+
let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
|
|
2717
3010
|
static shadowRootOptions = {
|
|
2718
3011
|
...LitElement.shadowRootOptions,
|
|
2719
3012
|
delegatesFocus: true
|
|
@@ -2722,7 +3015,6 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2722
3015
|
:host {
|
|
2723
3016
|
display: block;
|
|
2724
3017
|
width: 100%;
|
|
2725
|
-
min-height: 100vh;
|
|
2726
3018
|
}
|
|
2727
3019
|
`;
|
|
2728
3020
|
#_purpose_accessor_storage = "login";
|
|
@@ -2830,7 +3122,6 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2830
3122
|
set formValues(value) {
|
|
2831
3123
|
this.#_formValues_accessor_storage = value;
|
|
2832
3124
|
}
|
|
2833
|
-
themeController = new ThemeController(this);
|
|
2834
3125
|
engine = null;
|
|
2835
3126
|
sanitise = createSanitiser();
|
|
2836
3127
|
/**
|
|
@@ -2838,6 +3129,25 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2838
3129
|
* every `formValues` change otherwise re-parses the same template.
|
|
2839
3130
|
*/
|
|
2840
3131
|
tenantTemplateCache = null;
|
|
3132
|
+
/**
|
|
3133
|
+
* Whether the widget currently owns a same-document history entry (the
|
|
3134
|
+
* "sentinel"). The sentinel exists so the browser's back gesture fires
|
|
3135
|
+
* `popstate` instead of leaving the page. Exactly one sentinel is on
|
|
3136
|
+
* the stack at a time: it is pushed when a step with a `kind: "back"`
|
|
3137
|
+
* action renders, re-armed by `onPopState` after the browser consumes
|
|
3138
|
+
* it, and retired by `applyResponse` when a step without a back action
|
|
3139
|
+
* renders. The entry reuses the current URL, so the host page's
|
|
3140
|
+
* location (including any hash-router fragment) is never modified.
|
|
3141
|
+
*/
|
|
3142
|
+
armed = false;
|
|
3143
|
+
/**
|
|
3144
|
+
* Set immediately before a self-initiated `history.back()` so the
|
|
3145
|
+
* resulting `popstate` is ignored instead of being interpreted as a
|
|
3146
|
+
* user back gesture.
|
|
3147
|
+
*/
|
|
3148
|
+
ignoreNextPop = false;
|
|
3149
|
+
/** Bound `popstate` handler stored for cleanup in `disconnectedCallback`. */
|
|
3150
|
+
handlePopState = this.onPopState.bind(this);
|
|
2841
3151
|
createRenderRoot() {
|
|
2842
3152
|
const root = super.createRenderRoot();
|
|
2843
3153
|
if (root instanceof ShadowRoot) {
|
|
@@ -2856,6 +3166,14 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2856
3166
|
}
|
|
2857
3167
|
return root;
|
|
2858
3168
|
}
|
|
3169
|
+
connectedCallback() {
|
|
3170
|
+
super.connectedCallback();
|
|
3171
|
+
if (typeof window !== "undefined") window.addEventListener("popstate", this.handlePopState);
|
|
3172
|
+
}
|
|
3173
|
+
disconnectedCallback() {
|
|
3174
|
+
super.disconnectedCallback();
|
|
3175
|
+
if (typeof window !== "undefined") window.removeEventListener("popstate", this.handlePopState);
|
|
3176
|
+
}
|
|
2859
3177
|
/**
|
|
2860
3178
|
* Start the flow after the first render rather than in `connectedCallback`.
|
|
2861
3179
|
* Frameworks that wrap web components (e.g. `@lit/react` in the console)
|
|
@@ -2882,28 +3200,25 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2882
3200
|
const primary = ((this.lang || (typeof navigator !== "undefined" ? navigator.language : "") || (typeof document !== "undefined" ? document.documentElement.lang : "")).split("-")[0] ?? "").toLowerCase();
|
|
2883
3201
|
const builtin = builtinLocales[primary] ?? en;
|
|
2884
3202
|
const custom = this.locales?.[primary];
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
3203
|
+
if (!custom) return builtin;
|
|
3204
|
+
const merged = { ...builtin };
|
|
3205
|
+
for (const [key, value] of Object.entries(custom)) if (value !== void 0) merged[key] = value;
|
|
3206
|
+
return merged;
|
|
2889
3207
|
}
|
|
2890
3208
|
willUpdate(changed) {
|
|
2891
3209
|
if (!this.engine || changed.has("locales") || changed.has("lang")) this.engine = createLiquidEngine({ locale: this.resolveLocale() });
|
|
2892
|
-
|
|
2893
|
-
if (root) {
|
|
2894
|
-
applyBaseTokens(root);
|
|
2895
|
-
applyBrandingTokens(root, this.branding, this.themeController.theme);
|
|
2896
|
-
const tenantFontUrl = this.branding?.font_url ?? null;
|
|
2897
|
-
applyDefaultFont(root, tenantFontUrl ? null : void 0);
|
|
2898
|
-
applyFontUrl(root, tenantFontUrl);
|
|
2899
|
-
}
|
|
2900
|
-
this.dataset.theme = this.themeController.theme;
|
|
2901
|
-
this.toggleAttribute("data-theme-dark", this.themeController.theme === "dark");
|
|
3210
|
+
this.applySurfaceTheme(this.branding);
|
|
2902
3211
|
this.setAttribute("aria-busy", this.loading ? "true" : "false");
|
|
2903
3212
|
}
|
|
2904
3213
|
updated(changed) {
|
|
2905
|
-
if (
|
|
2906
|
-
|
|
3214
|
+
if (this.shadowRoot) {
|
|
3215
|
+
stampExportparts(this.shadowRoot);
|
|
3216
|
+
const widget = this.variant !== "page";
|
|
3217
|
+
for (const shell of this.shadowRoot.querySelectorAll("zl-page-shell")) shell.toggleAttribute("data-widget", widget);
|
|
3218
|
+
}
|
|
3219
|
+
const props = changed;
|
|
3220
|
+
if (!props.has("response")) return;
|
|
3221
|
+
this.hydrateStepAfterRender(props.get("response") == null);
|
|
2907
3222
|
}
|
|
2908
3223
|
/**
|
|
2909
3224
|
* Apply captured values and move focus once the new step has fully
|
|
@@ -2911,13 +3226,18 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2911
3226
|
* but those render their own shadow DOM on a later microtask — so await
|
|
2912
3227
|
* this element's update *and* the child atoms' first render before touching
|
|
2913
3228
|
* them, rather than guessing a frame with `requestAnimationFrame`.
|
|
3229
|
+
*
|
|
3230
|
+
* Focus on the *initial* response is page-mode-only: a dedicated login
|
|
3231
|
+
* route should focus its first field, but a widget embedded further down
|
|
3232
|
+
* an arbitrary page must not steal focus and scroll-jump on load. Step
|
|
3233
|
+
* swaps are user-initiated, so focus moves in both modes.
|
|
2914
3234
|
*/
|
|
2915
|
-
async hydrateStepAfterRender() {
|
|
3235
|
+
async hydrateStepAfterRender(initial = false) {
|
|
2916
3236
|
await this.updateComplete;
|
|
2917
3237
|
const atoms = this.shadowRoot?.querySelectorAll("zl-field, zl-button");
|
|
2918
3238
|
if (atoms) await Promise.all(Array.from(atoms).map((atom) => atom.updateComplete));
|
|
2919
3239
|
this.applyValuesToFields();
|
|
2920
|
-
this.moveFocusToFirstField();
|
|
3240
|
+
if (!initial || this.variant === "page") this.moveFocusToFirstField();
|
|
2921
3241
|
}
|
|
2922
3242
|
render() {
|
|
2923
3243
|
if (this.startupError) return html`<form class="zl-mount" novalidate>
|
|
@@ -2991,6 +3311,7 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2991
3311
|
});
|
|
2992
3312
|
}
|
|
2993
3313
|
this.applyResponse(wire);
|
|
3314
|
+
emit(this, "zitadel-flow-step", { step: wire.step });
|
|
2994
3315
|
} catch (error) {
|
|
2995
3316
|
this.handleTransportError(this.describeFlowSelectionError(error));
|
|
2996
3317
|
} finally {
|
|
@@ -3021,6 +3342,22 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
3021
3342
|
...collectInitialValues(wire.step),
|
|
3022
3343
|
...this.formValues
|
|
3023
3344
|
};
|
|
3345
|
+
if (typeof window !== "undefined") {
|
|
3346
|
+
const hasBack = Boolean(wire.step.actions?.some((a) => a.kind === "back"));
|
|
3347
|
+
if (hasBack && !this.armed) {
|
|
3348
|
+
history.pushState({
|
|
3349
|
+
...history.state,
|
|
3350
|
+
zl: true
|
|
3351
|
+
}, "");
|
|
3352
|
+
this.armed = true;
|
|
3353
|
+
} else if (!hasBack && this.armed) {
|
|
3354
|
+
this.armed = false;
|
|
3355
|
+
if (history.state?.zl === true) {
|
|
3356
|
+
this.ignoreNextPop = true;
|
|
3357
|
+
history.back();
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3024
3361
|
this.maybeCompleteFlow(wire);
|
|
3025
3362
|
}
|
|
3026
3363
|
/**
|
|
@@ -3127,42 +3464,89 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
3127
3464
|
...display ? { display_name: display } : email ? { display_name: email } : {}
|
|
3128
3465
|
};
|
|
3129
3466
|
}
|
|
3130
|
-
|
|
3467
|
+
/** All rendered input atoms exposing the `formValue` contract. */
|
|
3468
|
+
fieldAtoms() {
|
|
3131
3469
|
const root = this.shadowRoot;
|
|
3132
|
-
if (!root) return;
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3470
|
+
if (!root) return [];
|
|
3471
|
+
return Array.from(root.querySelectorAll("[name]")).filter(isFieldAtom);
|
|
3472
|
+
}
|
|
3473
|
+
applyValuesToFields() {
|
|
3474
|
+
for (const atom of this.fieldAtoms()) {
|
|
3475
|
+
const name = atom.getAttribute("name");
|
|
3476
|
+
if (!name) continue;
|
|
3138
3477
|
const next = this.formValues[name];
|
|
3139
|
-
if (
|
|
3478
|
+
if (next !== void 0 && atom.formValue !== next) atom.formValue = next;
|
|
3140
3479
|
}
|
|
3141
3480
|
}
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
const
|
|
3152
|
-
if (
|
|
3153
|
-
if (field.value !== value) field.value = value;
|
|
3154
|
-
if (next[name] !== value) {
|
|
3155
|
-
next[name] = value;
|
|
3156
|
-
changed = true;
|
|
3157
|
-
}
|
|
3481
|
+
/**
|
|
3482
|
+
* Names of the current step's `required` fields whose captured value is
|
|
3483
|
+
* empty. Reads each atom's live `formValue` (the getter reflects the native
|
|
3484
|
+
* control, so autofill that skipped `input` events is still seen), so this
|
|
3485
|
+
* is browser-independent and does not rely on native constraint validation.
|
|
3486
|
+
*/
|
|
3487
|
+
missingRequiredFields() {
|
|
3488
|
+
const values = /* @__PURE__ */ new Map();
|
|
3489
|
+
for (const atom of this.fieldAtoms()) {
|
|
3490
|
+
const name = atom.getAttribute("name");
|
|
3491
|
+
if (name) values.set(name, atom.formValue);
|
|
3158
3492
|
}
|
|
3159
|
-
|
|
3160
|
-
|
|
3493
|
+
const missing = [];
|
|
3494
|
+
for (const field of this.response?.step.fields ?? []) {
|
|
3495
|
+
if (field.type === "checkbox") continue;
|
|
3496
|
+
if (field.required && (values.get(field.name) ?? "") === "") missing.push(field.name);
|
|
3497
|
+
}
|
|
3498
|
+
return missing;
|
|
3499
|
+
}
|
|
3500
|
+
/**
|
|
3501
|
+
* Surface a client-side required-field error using the server's own
|
|
3502
|
+
* validation dialect (`error.<field>_required`, "; "-joined), so it flows
|
|
3503
|
+
* through the same localisation and inline/banner routing as a real
|
|
3504
|
+
* backend rejection — no native browser bubble. Idempotent: re-running with
|
|
3505
|
+
* the same keys (both submit entry points fire on one click) is a no-op.
|
|
3506
|
+
*/
|
|
3507
|
+
reportRequiredErrors(fields) {
|
|
3508
|
+
if (!this.response) return;
|
|
3509
|
+
const errorKey = fields.map((name) => `error.${name}_required`).join("; ");
|
|
3510
|
+
if (this.response.step.error === errorKey) return;
|
|
3511
|
+
this.stepErrorDismissed = false;
|
|
3512
|
+
this.response = {
|
|
3513
|
+
...this.response,
|
|
3514
|
+
step: {
|
|
3515
|
+
...this.response.step,
|
|
3516
|
+
error: errorKey
|
|
3517
|
+
}
|
|
3518
|
+
};
|
|
3161
3519
|
}
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3520
|
+
/**
|
|
3521
|
+
* Snapshot the current step's field values straight from the rendered input
|
|
3522
|
+
* atoms through their uniform `formValue` contract. Tag-agnostic: every
|
|
3523
|
+
* form-participating atom is read the same way, so a new field type needs no
|
|
3524
|
+
* change here. Declared fields default to "" so the backend still runs its
|
|
3525
|
+
* required-checks and challenge dispatch instead of silently advancing on a
|
|
3526
|
+
* field-less payload. Captured values are folded into `formValues` for
|
|
3527
|
+
* cross-step identity (the signed-in greeting) and post-error restoration.
|
|
3528
|
+
*/
|
|
3529
|
+
collectSubmitFields() {
|
|
3530
|
+
const current = /* @__PURE__ */ new Map();
|
|
3531
|
+
for (const atom of this.fieldAtoms()) {
|
|
3532
|
+
const name = atom.getAttribute("name");
|
|
3533
|
+
if (name) current.set(name, atom.formValue);
|
|
3534
|
+
}
|
|
3535
|
+
if (current.size > 0) this.formValues = {
|
|
3536
|
+
...this.formValues,
|
|
3537
|
+
...Object.fromEntries(current)
|
|
3538
|
+
};
|
|
3539
|
+
const fields = {};
|
|
3540
|
+
for (const f of this.response?.step.fields ?? []) {
|
|
3541
|
+
const value = current.get(f.name) ?? "";
|
|
3542
|
+
if (f.type === "checkbox") {
|
|
3543
|
+
fields[f.name] = value !== "";
|
|
3544
|
+
continue;
|
|
3545
|
+
}
|
|
3546
|
+
if (f.type === "select" && !isAllowedSelectValue(f, value)) continue;
|
|
3547
|
+
fields[f.name] = value;
|
|
3548
|
+
}
|
|
3549
|
+
return fields;
|
|
3166
3550
|
}
|
|
3167
3551
|
/**
|
|
3168
3552
|
* True once the user retired the current step error by editing a field or
|
|
@@ -3190,10 +3574,25 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
3190
3574
|
value
|
|
3191
3575
|
});
|
|
3192
3576
|
};
|
|
3193
|
-
/**
|
|
3577
|
+
/**
|
|
3578
|
+
* `zl-change` from <zl-checkbox>/<zl-select>. Persist the atom's value into
|
|
3579
|
+
* `formValues` — mirroring `handleAtomInput` for text fields — so a later
|
|
3580
|
+
* step re-render (a validation error re-parses the template via `unsafeHTML`
|
|
3581
|
+
* and rebuilds the atoms) restores the selection/checked state through
|
|
3582
|
+
* `applyValuesToFields` instead of dropping back to the template default.
|
|
3583
|
+
* Reads the live `formValue` off the atom rather than the event's `value`
|
|
3584
|
+
* token, because an unchecked checkbox reports "" there but keeps its token
|
|
3585
|
+
* in the detail. Also clears stale errors on the edited field.
|
|
3586
|
+
*/
|
|
3194
3587
|
handleAtomEdited = (event) => {
|
|
3195
3588
|
const name = event.detail?.name;
|
|
3196
|
-
if (name)
|
|
3589
|
+
if (!name) return;
|
|
3590
|
+
const atom = event.target;
|
|
3591
|
+
if (atom instanceof HTMLElement && isFieldAtom(atom)) this.formValues = {
|
|
3592
|
+
...this.formValues,
|
|
3593
|
+
[name]: atom.formValue
|
|
3594
|
+
};
|
|
3595
|
+
this.clearStaleErrors(name);
|
|
3197
3596
|
};
|
|
3198
3597
|
/** Explicit dismiss of the step-error alert (it removes itself). */
|
|
3199
3598
|
handleAlertDismiss = (event) => {
|
|
@@ -3219,16 +3618,9 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
3219
3618
|
field.error = "";
|
|
3220
3619
|
}
|
|
3221
3620
|
}
|
|
3621
|
+
/** Mirror a value onto the matching rendered atom (used after atom events). */
|
|
3222
3622
|
syncFieldElementValue(name, value) {
|
|
3223
|
-
const
|
|
3224
|
-
if (!root) return;
|
|
3225
|
-
const fields = root.querySelectorAll("zl-field");
|
|
3226
|
-
for (const field of fields) {
|
|
3227
|
-
if (field.getAttribute("name") !== name) continue;
|
|
3228
|
-
if (field.value !== value) field.value = value;
|
|
3229
|
-
const native = field.shadowRoot?.querySelector("input");
|
|
3230
|
-
if (native && native.value !== value) native.value = value;
|
|
3231
|
-
}
|
|
3623
|
+
for (const atom of this.fieldAtoms()) if (atom.getAttribute("name") === name && atom.formValue !== value) atom.formValue = value;
|
|
3232
3624
|
}
|
|
3233
3625
|
handleAtomSubmit = (event) => {
|
|
3234
3626
|
if (this.loading) return;
|
|
@@ -3246,6 +3638,11 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
3246
3638
|
handleFormSubmit = (event) => {
|
|
3247
3639
|
event.preventDefault();
|
|
3248
3640
|
if (this.loading) return;
|
|
3641
|
+
const missing = this.missingRequiredFields();
|
|
3642
|
+
if (missing.length > 0) {
|
|
3643
|
+
this.reportRequiredErrors(missing);
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3249
3646
|
const action = event.submitter?.getAttribute?.("action") ?? null ?? this.findPrimaryAction();
|
|
3250
3647
|
this.submit(action);
|
|
3251
3648
|
};
|
|
@@ -3308,13 +3705,7 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
3308
3705
|
const { id, session_token } = this.response;
|
|
3309
3706
|
this.loading = true;
|
|
3310
3707
|
try {
|
|
3311
|
-
const
|
|
3312
|
-
const stepFields = this.response.step.fields ?? [];
|
|
3313
|
-
const fields = {};
|
|
3314
|
-
for (const f of stepFields) {
|
|
3315
|
-
const value = formValues[f.name];
|
|
3316
|
-
if (value !== void 0) fields[f.name] = value;
|
|
3317
|
-
}
|
|
3708
|
+
const fields = this.collectSubmitFields();
|
|
3318
3709
|
const body = {
|
|
3319
3710
|
session_token,
|
|
3320
3711
|
action: action ?? "submit",
|
|
@@ -3331,6 +3722,47 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
3331
3722
|
this.loading = false;
|
|
3332
3723
|
}
|
|
3333
3724
|
}
|
|
3725
|
+
/**
|
|
3726
|
+
* Handle the browser's back/forward gesture (ADR 022). When `popstate`
|
|
3727
|
+
* fires:
|
|
3728
|
+
*
|
|
3729
|
+
* - **Self-initiated** (`ignoreNextPop`) → `applyResponse` is retiring
|
|
3730
|
+
* the sentinel; ignore.
|
|
3731
|
+
* - **Back press while armed** → the browser consumed the sentinel.
|
|
3732
|
+
* Re-arm it immediately — so the stack shape is identical on every
|
|
3733
|
+
* step and repeated presses behave the same at any flow depth — then
|
|
3734
|
+
* submit the step's `kind: "back"` action.
|
|
3735
|
+
* - **Landing on the sentinel while armed** → the host page pushed an
|
|
3736
|
+
* entry above the sentinel (e.g. an in-page `#anchor` click) and the
|
|
3737
|
+
* user backed out of it. They are back where the widget expects them
|
|
3738
|
+
* — not asking the flow to go back; do nothing.
|
|
3739
|
+
* - **Forward press onto a retired sentinel** (it survives as a forward
|
|
3740
|
+
* entry after `history.back()`) → bounce back: flow state is
|
|
3741
|
+
* server-authoritative, the browser cannot skip ahead
|
|
3742
|
+
* (ADR 022 §Edge cases).
|
|
3743
|
+
* - Anything else is host-page traversal — leave the browser alone.
|
|
3744
|
+
*/
|
|
3745
|
+
onPopState(event) {
|
|
3746
|
+
if (this.ignoreNextPop) {
|
|
3747
|
+
this.ignoreNextPop = false;
|
|
3748
|
+
return;
|
|
3749
|
+
}
|
|
3750
|
+
if (this.armed) {
|
|
3751
|
+
if (event.state?.zl === true) return;
|
|
3752
|
+
this.armed = false;
|
|
3753
|
+
const backAction = this.response?.step?.actions?.find((a) => a.kind === "back");
|
|
3754
|
+
if (backAction) {
|
|
3755
|
+
history.pushState({ zl: true }, "");
|
|
3756
|
+
this.armed = true;
|
|
3757
|
+
this.submit(backAction.name);
|
|
3758
|
+
}
|
|
3759
|
+
return;
|
|
3760
|
+
}
|
|
3761
|
+
if (event.state?.zl === true) {
|
|
3762
|
+
this.ignoreNextPop = true;
|
|
3763
|
+
history.back();
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3334
3766
|
handleTransportError(error) {
|
|
3335
3767
|
const message = error instanceof ApiError ? apiErrorMessage(error) : error instanceof Error ? error.message : "Unexpected error contacting the Flow API.";
|
|
3336
3768
|
this.startupError = message;
|
|
@@ -3369,6 +3801,16 @@ __decorate([state()], ZitadelLogin.prototype, "loading", null);
|
|
|
3369
3801
|
__decorate([state()], ZitadelLogin.prototype, "startupError", null);
|
|
3370
3802
|
__decorate([state()], ZitadelLogin.prototype, "formValues", null);
|
|
3371
3803
|
ZitadelLogin = __decorate([customElement("zitadel-login")], ZitadelLogin);
|
|
3804
|
+
/**
|
|
3805
|
+
* Whether `value` is a member of a select field's closed `enum`. A select
|
|
3806
|
+
* with no explicit enum has no submittable value, so this returns `false`
|
|
3807
|
+
* and the caller omits the field. Because the enum never contains "" unless
|
|
3808
|
+
* the schema deliberately lists it, an untouched placeholder ("") is omitted
|
|
3809
|
+
* rather than sent and rejected by the server's enum validation.
|
|
3810
|
+
*/
|
|
3811
|
+
function isAllowedSelectValue(field, value) {
|
|
3812
|
+
return field.validation?.enum?.includes(value) ?? false;
|
|
3813
|
+
}
|
|
3372
3814
|
function collectInitialValues(step) {
|
|
3373
3815
|
const values = {};
|
|
3374
3816
|
if (!step.fields) return values;
|
|
@@ -3550,12 +3992,12 @@ let ZitadelLogout = class ZitadelLogout extends LitElement {
|
|
|
3550
3992
|
set postSignOutUrl(value) {
|
|
3551
3993
|
this.#_postSignOutUrl_accessor_storage = value;
|
|
3552
3994
|
}
|
|
3553
|
-
#
|
|
3554
|
-
get
|
|
3555
|
-
return this.#
|
|
3995
|
+
#_theme_accessor_storage = "";
|
|
3996
|
+
get theme() {
|
|
3997
|
+
return this.#_theme_accessor_storage;
|
|
3556
3998
|
}
|
|
3557
|
-
set
|
|
3558
|
-
this.#
|
|
3999
|
+
set theme(value) {
|
|
4000
|
+
this.#_theme_accessor_storage = value;
|
|
3559
4001
|
}
|
|
3560
4002
|
#_displayName_accessor_storage = "";
|
|
3561
4003
|
get displayName() {
|
|
@@ -3603,9 +4045,9 @@ let ZitadelLogout = class ZitadelLogout extends LitElement {
|
|
|
3603
4045
|
pendingTemplate = null;
|
|
3604
4046
|
projectedContainer = null;
|
|
3605
4047
|
identityRequested = false;
|
|
4048
|
+
themeController = new ThemeController(this);
|
|
3606
4049
|
connectedCallback() {
|
|
3607
4050
|
super.connectedCallback();
|
|
3608
|
-
this.dataset.theme = "dark";
|
|
3609
4051
|
const tmpl = this.querySelector("template");
|
|
3610
4052
|
if (tmpl instanceof HTMLTemplateElement) {
|
|
3611
4053
|
this.templateMode = true;
|
|
@@ -3621,9 +4063,17 @@ let ZitadelLogout = class ZitadelLogout extends LitElement {
|
|
|
3621
4063
|
document.removeEventListener("click", this.handleDocumentClick);
|
|
3622
4064
|
document.removeEventListener("keydown", this.handleDocumentKeydown);
|
|
3623
4065
|
}
|
|
3624
|
-
|
|
4066
|
+
willUpdate() {
|
|
4067
|
+
this.themeController.setModePreference(this.theme === "" ? void 0 : this.theme, "auto");
|
|
3625
4068
|
const root = this.shadowRoot;
|
|
3626
|
-
if (root && !this.templateMode)
|
|
4069
|
+
if (root && !this.templateMode) {
|
|
4070
|
+
applyBaseTokens(root);
|
|
4071
|
+
applyBrandingTokens(root, void 0, this.themeController.theme);
|
|
4072
|
+
}
|
|
4073
|
+
this.dataset.theme = this.themeController.theme;
|
|
4074
|
+
this.toggleAttribute("data-theme-dark", this.themeController.theme === "dark");
|
|
4075
|
+
}
|
|
4076
|
+
updated() {
|
|
3627
4077
|
this.maybeLoadIdentity();
|
|
3628
4078
|
}
|
|
3629
4079
|
/** Declarative config read from this element's attributes. */
|
|
@@ -3734,20 +4184,6 @@ let ZitadelLogout = class ZitadelLogout extends LitElement {
|
|
|
3734
4184
|
});
|
|
3735
4185
|
if (this.postSignOutUrl && typeof window !== "undefined") window.location.href = this.postSignOutUrl;
|
|
3736
4186
|
}
|
|
3737
|
-
/**
|
|
3738
|
-
* Returns the absolute URL the end-session request will hit. Useful for
|
|
3739
|
-
* test assertions and for consumers that prefer to navigate the browser
|
|
3740
|
-
* directly (instead of fetching) so the OIDC session-end redirect is
|
|
3741
|
-
* driven by the user agent.
|
|
3742
|
-
*/
|
|
3743
|
-
getEndSessionUrl() {
|
|
3744
|
-
const params = {
|
|
3745
|
-
...this.clientId ? { client_id: this.clientId } : {},
|
|
3746
|
-
...this.postSignOutUrl ? { post_logout_redirect_uri: this.postSignOutUrl } : {}
|
|
3747
|
-
};
|
|
3748
|
-
const { api } = resolveApi(this.project, this.projectAttrs, "<zitadel-logout>");
|
|
3749
|
-
return api.getEndSessionUrl(params);
|
|
3750
|
-
}
|
|
3751
4187
|
handleSignOutClick(event) {
|
|
3752
4188
|
event.preventDefault();
|
|
3753
4189
|
this.doLogout();
|
|
@@ -3826,10 +4262,7 @@ __decorate([property({
|
|
|
3826
4262
|
type: String,
|
|
3827
4263
|
attribute: "post-sign-out-url"
|
|
3828
4264
|
})], ZitadelLogout.prototype, "postSignOutUrl", null);
|
|
3829
|
-
__decorate([property({
|
|
3830
|
-
type: String,
|
|
3831
|
-
attribute: "client-id"
|
|
3832
|
-
})], ZitadelLogout.prototype, "clientId", null);
|
|
4265
|
+
__decorate([property({ type: String })], ZitadelLogout.prototype, "theme", null);
|
|
3833
4266
|
__decorate([state()], ZitadelLogout.prototype, "displayName", null);
|
|
3834
4267
|
__decorate([state()], ZitadelLogout.prototype, "displayEmail", null);
|
|
3835
4268
|
__decorate([state()], ZitadelLogout.prototype, "displayUserId", null);
|
|
@@ -3852,13 +4285,11 @@ function fillTemplateTokens(fragment, name, email, initial) {
|
|
|
3852
4285
|
}
|
|
3853
4286
|
//#endregion
|
|
3854
4287
|
//#region src/orchestrator/zitadel-session.ts
|
|
3855
|
-
let ZitadelSession = class ZitadelSession extends
|
|
4288
|
+
let ZitadelSession = class ZitadelSession extends ZitadelSurface {
|
|
3856
4289
|
static styles = [baseHostStyles, css`
|
|
3857
4290
|
:host {
|
|
3858
4291
|
display: block;
|
|
3859
|
-
|
|
3860
|
-
background: ${t.color.surface.defaultBlack};
|
|
3861
|
-
color: ${t.color.text.primaryWhite};
|
|
4292
|
+
width: 100%;
|
|
3862
4293
|
}
|
|
3863
4294
|
.title {
|
|
3864
4295
|
margin: 0;
|
|
@@ -3974,16 +4405,16 @@ let ZitadelSession = class ZitadelSession extends LitElement {
|
|
|
3974
4405
|
identityRequested = false;
|
|
3975
4406
|
connectedCallback() {
|
|
3976
4407
|
super.connectedCallback();
|
|
3977
|
-
this.dataset.theme = "dark";
|
|
3978
4408
|
this.maybeLoadIdentity();
|
|
3979
4409
|
}
|
|
3980
4410
|
/** Single identity line: human-readable name, then email, then user_id. */
|
|
3981
4411
|
get identityLabel() {
|
|
3982
4412
|
return this.displayName || this.displayEmail || this.displayUserId;
|
|
3983
4413
|
}
|
|
4414
|
+
willUpdate() {
|
|
4415
|
+
this.applySurfaceTheme(void 0);
|
|
4416
|
+
}
|
|
3984
4417
|
updated() {
|
|
3985
|
-
const root = this.shadowRoot;
|
|
3986
|
-
if (root) applyBaseTokens(root);
|
|
3987
4418
|
this.maybeLoadIdentity();
|
|
3988
4419
|
}
|
|
3989
4420
|
/** Declarative config read from this element's attributes. */
|
|
@@ -4048,7 +4479,7 @@ let ZitadelSession = class ZitadelSession extends LitElement {
|
|
|
4048
4479
|
}
|
|
4049
4480
|
render() {
|
|
4050
4481
|
return html`
|
|
4051
|
-
<zl-page-shell>
|
|
4482
|
+
<zl-page-shell ?data-widget=${this.variant !== "page"}>
|
|
4052
4483
|
<zl-card>
|
|
4053
4484
|
<h1 slot="header" class="title">${this.heading}</h1>
|
|
4054
4485
|
${this.identityLabel ? html`<p slot="header" class="identity">${this.identityLabel}</p>` : nothing}
|
|
@@ -4095,6 +4526,6 @@ __decorate([state()], ZitadelSession.prototype, "loading", null);
|
|
|
4095
4526
|
__decorate([state()], ZitadelSession.prototype, "errorMessage", null);
|
|
4096
4527
|
ZitadelSession = __decorate([customElement("zitadel-session")], ZitadelSession);
|
|
4097
4528
|
//#endregion
|
|
4098
|
-
export { submitStep as S,
|
|
4529
|
+
export { submitStep as C, startFlow as S, MANDATORY_GATES_MARKER as _, ThemeController as a, validateBranding as b, buildBrandingStylesheet as c, builtinLocales as d, businessLocales as f, TEMPLATE_NAMES as g, en as h, layout_chrome_default as i, resolveTheme as l, de as m, ZitadelLogout as n, applyFontUrl as o, it as p, ZitadelLogin as r, applyBrandingTokens as s, ZitadelSession as t, createSanitiser as u, mandatoryGatesMarkerComment as v, getCurrentStep as x, patchMandatoryGates as y };
|
|
4099
4530
|
|
|
4100
|
-
//# sourceMappingURL=orchestrator-
|
|
4531
|
+
//# sourceMappingURL=orchestrator-CctLkpmB.mjs.map
|