@zitadel/components 0.1.0-alpha.16 → 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 +89 -20
- package/dist/atoms/index.d.mts +1 -1
- package/dist/atoms/index.mjs +1 -1
- package/dist/{atoms-C-zXJjaz.mjs → atoms-5-wYIl88.mjs} +295 -190
- 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-D1eMaWbY.d.mts → index-CsJiPZtJ.d.mts} +438 -210
- package/dist/index-CsJiPZtJ.d.mts.map +1 -0
- package/dist/{index-BwQLFs9R.d.mts → index-DIGjtG4z.d.mts} +1642 -1027
- 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-BN2nT9FZ.mjs → orchestrator-CctLkpmB.mjs} +1229 -767
- package/dist/orchestrator-CctLkpmB.mjs.map +1 -0
- package/dist/standalone.mjs +2552 -1291
- 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-C-zXJjaz.mjs.map +0 -1
- package/dist/default-BoC3JEsO.mjs +0 -6
- package/dist/default-BoC3JEsO.mjs.map +0 -1
- package/dist/index-BniwXaIC.d.mts.map +0 -1
- package/dist/index-BwQLFs9R.d.mts.map +0 -1
- package/dist/index-D1eMaWbY.d.mts.map +0 -1
- package/dist/orchestrator-BN2nT9FZ.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
|
|
2
|
-
import { r as tokensCss } from "./tokens-
|
|
3
|
-
import { t as default_default } from "./default-BoC3JEsO.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,17 +1843,30 @@ 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
|
});
|
|
1968
|
-
/**
|
|
1852
|
+
/**
|
|
1853
|
+
* Automation-hook token for a field name: strips the
|
|
1854
|
+
* `x-auth-methods#` credential prefix so testids stay method-named
|
|
1855
|
+
* (`zitadel-field-password`), while the `name` attribute keeps the
|
|
1856
|
+
* raw wire key. See hookName for the contract.
|
|
1857
|
+
*/
|
|
1858
|
+
engine.registerFilter("testid", (fieldName) => hookName(stringify(fieldName)));
|
|
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
|
+
*/
|
|
1969
1866
|
engine.registerFilter("formLevelError", (err) => {
|
|
1970
|
-
|
|
1971
|
-
return key === "" || !(key in fieldErrorKeys);
|
|
1867
|
+
return !err?.field;
|
|
1972
1868
|
});
|
|
1973
|
-
engine.registerTag(
|
|
1869
|
+
engine.registerTag(MANDATORY_GATES_TAG, {
|
|
1974
1870
|
parse() {},
|
|
1975
1871
|
render() {
|
|
1976
1872
|
return mandatoryGatesMarkerComment;
|
|
@@ -1993,6 +1889,22 @@ function injectedKeyFallback(locale, key) {
|
|
|
1993
1889
|
for (const { suffix, fallback } of INJECTED_KEY_FALLBACKS) if (key.endsWith(suffix)) return locale[fallback];
|
|
1994
1890
|
}
|
|
1995
1891
|
/**
|
|
1892
|
+
* Fallback for field-label keys (`<step>.field.<name>` — see
|
|
1893
|
+
* `FlowField.TextKey` in `internal/domain/flow_field_resolver.go`). Both
|
|
1894
|
+
* halves are tenant-chosen (step names and schema property names), so no
|
|
1895
|
+
* catalog can enumerate the keys; a miss renders a humanised property name
|
|
1896
|
+
* ("dateOfBirth" → "Date of birth") instead of leaking the raw key into the
|
|
1897
|
+
* form. Sub-keys like `.placeholder`/`.help` are excluded — they resolve
|
|
1898
|
+
* through their own filters, which stay empty on a miss.
|
|
1899
|
+
*/
|
|
1900
|
+
function fieldLabelFallback(key) {
|
|
1901
|
+
const index = key.lastIndexOf(".field.");
|
|
1902
|
+
if (index === -1) return void 0;
|
|
1903
|
+
const field = key.slice(index + 7);
|
|
1904
|
+
if (field === "" || field.includes(".")) return void 0;
|
|
1905
|
+
return capitaliseFirst(humaniseFieldName(field));
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1996
1908
|
* Rule-suffix fallbacks for the server's field-validation keys
|
|
1997
1909
|
* (`error.<field>_<rule>` — see `FlowFieldValidationError.TextKey` in
|
|
1998
1910
|
* `internal/domain/flow_field_resolver.go`). Field names come from the
|
|
@@ -2031,10 +1943,12 @@ const FLOW_ERROR_RULE_FALLBACKS = [
|
|
|
2031
1943
|
];
|
|
2032
1944
|
const FLOW_ERROR_CATCH_ALL_KEY = "error.field_invalid";
|
|
2033
1945
|
/**
|
|
2034
|
-
* Localises a `step.error` payload of
|
|
1946
|
+
* Localises a `step.error` payload of `error.*` catalog keys —
|
|
1947
|
+
* field-validation violations and general engine failures (e.g.
|
|
1948
|
+
* `error.invalid_credentials`, `error.passkey_invalid`) alike.
|
|
2035
1949
|
* Returns `null` unless EVERY `"; "`-joined segment is an `error.*` key
|
|
2036
|
-
* — the caller keeps other payloads (outcome
|
|
2037
|
-
*
|
|
1950
|
+
* — the caller keeps other payloads (outcome tokens such as
|
|
1951
|
+
* `user_not_found`) verbatim.
|
|
2038
1952
|
*
|
|
2039
1953
|
* Keys the locale knows pass through as `text_key` entries: the template
|
|
2040
1954
|
* localises them via `| t`, and `fieldErrorKeys` routes the known ones
|
|
@@ -2052,12 +1966,19 @@ function localiseFlowErrorKeys(raw, ctx) {
|
|
|
2052
1966
|
function localiseFlowErrorKey(key, ctx) {
|
|
2053
1967
|
const inlineField = fieldErrorKeys[key];
|
|
2054
1968
|
const orphanedInline = inlineField !== void 0 && ctx.fields !== void 0 && !ctx.fields.includes(inlineField);
|
|
2055
|
-
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 };
|
|
2056
1973
|
for (const { suffix, generic } of FLOW_ERROR_RULE_FALLBACKS) {
|
|
2057
1974
|
if (!key.endsWith(suffix)) continue;
|
|
2058
1975
|
const field = key.slice(6, key.length - suffix.length);
|
|
2059
1976
|
if (field === "") break;
|
|
2060
|
-
|
|
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 };
|
|
2061
1982
|
}
|
|
2062
1983
|
if (orphanedInline && ctx.locale[key] !== void 0) return { message: ctx.locale[key] };
|
|
2063
1984
|
return { text_key: key };
|
|
@@ -2136,8 +2057,8 @@ const TEMPLATE_NAMES = {
|
|
|
2136
2057
|
const en = {
|
|
2137
2058
|
"identifier.title": "Sign in",
|
|
2138
2059
|
"identifier.description": "Enter your email to continue",
|
|
2139
|
-
"identifier.field.email": "
|
|
2140
|
-
"identifier.field.email.placeholder": "you@
|
|
2060
|
+
"identifier.field.email": "Email",
|
|
2061
|
+
"identifier.field.email.placeholder": "you@example.com",
|
|
2141
2062
|
"identifier.field.password": "Password",
|
|
2142
2063
|
"identifier.action.submit": "Sign in",
|
|
2143
2064
|
"identifier.action.continue": "Sign in",
|
|
@@ -2154,8 +2075,8 @@ const en = {
|
|
|
2154
2075
|
"choose-register.action.register-with-passkey": "Sign up with passkey",
|
|
2155
2076
|
"collect-credentials.title": "Create your account",
|
|
2156
2077
|
"collect-credentials.description": "Set up your email and password",
|
|
2157
|
-
"collect-credentials.field.email": "
|
|
2158
|
-
"collect-credentials.field.email.placeholder": "you@
|
|
2078
|
+
"collect-credentials.field.email": "Email",
|
|
2079
|
+
"collect-credentials.field.email.placeholder": "you@example.com",
|
|
2159
2080
|
"collect-credentials.field.password": "Password",
|
|
2160
2081
|
"collect-credentials.action.submit": "Sign up",
|
|
2161
2082
|
"register-password.title": "Create your password",
|
|
@@ -2172,8 +2093,8 @@ const en = {
|
|
|
2172
2093
|
"passkey-upsell.action.setup": "Set up passkey",
|
|
2173
2094
|
"collect-passkey-email.title": "Create your account",
|
|
2174
2095
|
"collect-passkey-email.description": "Enter your email to set up a passkey",
|
|
2175
|
-
"collect-passkey-email.field.email": "
|
|
2176
|
-
"collect-passkey-email.field.email.placeholder": "you@
|
|
2096
|
+
"collect-passkey-email.field.email": "Email",
|
|
2097
|
+
"collect-passkey-email.field.email.placeholder": "you@example.com",
|
|
2177
2098
|
"collect-passkey-email.action.submit": "Continue",
|
|
2178
2099
|
"passkey-enroll.title": "Set up your passkey",
|
|
2179
2100
|
"passkey-enroll.description": "Use Face ID, Touch ID, or your device PIN to create a passkey.",
|
|
@@ -2190,11 +2111,12 @@ const en = {
|
|
|
2190
2111
|
"password.action.register.link": "Sign up",
|
|
2191
2112
|
"register.title": "Create your account",
|
|
2192
2113
|
"register.description": "",
|
|
2193
|
-
"register.field.email": "
|
|
2194
|
-
"register.field.email.placeholder": "you@
|
|
2114
|
+
"register.field.email": "Email",
|
|
2115
|
+
"register.field.email.placeholder": "you@example.com",
|
|
2195
2116
|
"register.field.password": "Password",
|
|
2196
2117
|
"register.field.givenName": "Given name",
|
|
2197
2118
|
"register.field.familyName": "Family name",
|
|
2119
|
+
"register.field.companyName": "Company name",
|
|
2198
2120
|
"register.field.dateOfBirth": "Date of birth",
|
|
2199
2121
|
"register.field.country": "Country",
|
|
2200
2122
|
"register.field.country.placeholder": "Select your country",
|
|
@@ -2229,6 +2151,8 @@ const en = {
|
|
|
2229
2151
|
"error.passkey_setup_failed": "Passkey registration did not complete. Please try again.",
|
|
2230
2152
|
"error.passkey_unsupported": "This device does not support passkeys",
|
|
2231
2153
|
"error.passkey_failed": "Something went wrong. Please try again.",
|
|
2154
|
+
"error.passkey_invalid": "This passkey could not be verified. Please try again.",
|
|
2155
|
+
"error.passkey_registration_invalid": "The new passkey could not be verified. Please try registering it again.",
|
|
2232
2156
|
"error.email_required": "Please enter an email address",
|
|
2233
2157
|
"error.email_invalid": "Please enter a valid email",
|
|
2234
2158
|
"error.password_required": "Please enter a password",
|
|
@@ -2251,7 +2175,7 @@ const de = {
|
|
|
2251
2175
|
"identifier.title": "Anmelden",
|
|
2252
2176
|
"identifier.description": "Gib deine E-Mail-Adresse ein, um fortzufahren",
|
|
2253
2177
|
"identifier.field.email": "E-Mail",
|
|
2254
|
-
"identifier.field.email.placeholder": "du@
|
|
2178
|
+
"identifier.field.email.placeholder": "du@beispiel.de",
|
|
2255
2179
|
"identifier.field.password": "Passwort",
|
|
2256
2180
|
"identifier.action.submit": "Anmelden",
|
|
2257
2181
|
"identifier.action.continue": "Anmelden",
|
|
@@ -2269,7 +2193,7 @@ const de = {
|
|
|
2269
2193
|
"collect-credentials.title": "Konto erstellen",
|
|
2270
2194
|
"collect-credentials.description": "Richte E-Mail und Passwort ein",
|
|
2271
2195
|
"collect-credentials.field.email": "E-Mail",
|
|
2272
|
-
"collect-credentials.field.email.placeholder": "du@
|
|
2196
|
+
"collect-credentials.field.email.placeholder": "du@beispiel.de",
|
|
2273
2197
|
"collect-credentials.field.password": "Passwort",
|
|
2274
2198
|
"collect-credentials.action.submit": "Registrieren",
|
|
2275
2199
|
"register-password.title": "Passwort erstellen",
|
|
@@ -2287,7 +2211,7 @@ const de = {
|
|
|
2287
2211
|
"collect-passkey-email.title": "Konto erstellen",
|
|
2288
2212
|
"collect-passkey-email.description": "Gib deine E-Mail-Adresse ein, um einen Passkey einzurichten",
|
|
2289
2213
|
"collect-passkey-email.field.email": "E-Mail",
|
|
2290
|
-
"collect-passkey-email.field.email.placeholder": "du@
|
|
2214
|
+
"collect-passkey-email.field.email.placeholder": "du@beispiel.de",
|
|
2291
2215
|
"collect-passkey-email.action.submit": "Weiter",
|
|
2292
2216
|
"passkey-enroll.title": "Passkey einrichten",
|
|
2293
2217
|
"passkey-enroll.description": "Verwende Face ID, Touch ID oder die Geräte-PIN, um einen Passkey zu erstellen.",
|
|
@@ -2305,10 +2229,11 @@ const de = {
|
|
|
2305
2229
|
"register.title": "Konto erstellen",
|
|
2306
2230
|
"register.description": "",
|
|
2307
2231
|
"register.field.email": "E-Mail",
|
|
2308
|
-
"register.field.email.placeholder": "du@
|
|
2232
|
+
"register.field.email.placeholder": "du@beispiel.de",
|
|
2309
2233
|
"register.field.password": "Passwort",
|
|
2310
2234
|
"register.field.givenName": "Vorname",
|
|
2311
2235
|
"register.field.familyName": "Nachname",
|
|
2236
|
+
"register.field.companyName": "Firmenname",
|
|
2312
2237
|
"register.field.dateOfBirth": "Geburtsdatum",
|
|
2313
2238
|
"register.field.country": "Land",
|
|
2314
2239
|
"register.field.country.placeholder": "Land auswählen",
|
|
@@ -2343,6 +2268,8 @@ const de = {
|
|
|
2343
2268
|
"error.passkey_setup_failed": "Passkey-Registrierung wurde nicht abgeschlossen. Bitte versuche es erneut.",
|
|
2344
2269
|
"error.passkey_unsupported": "Dieses Gerät unterstützt keine Passkeys",
|
|
2345
2270
|
"error.passkey_failed": "Etwas ist schiefgelaufen. Bitte versuche es erneut.",
|
|
2271
|
+
"error.passkey_invalid": "Dieser Passkey konnte nicht bestätigt werden. Bitte versuche es erneut.",
|
|
2272
|
+
"error.passkey_registration_invalid": "Der neue Passkey konnte nicht bestätigt werden. Bitte registriere ihn erneut.",
|
|
2346
2273
|
"error.email_required": "Bitte gib eine E-Mail-Adresse ein",
|
|
2347
2274
|
"error.email_invalid": "Bitte gib eine gültige E-Mail-Adresse ein",
|
|
2348
2275
|
"error.password_required": "Bitte gib ein Passwort ein",
|
|
@@ -2363,8 +2290,8 @@ const de = {
|
|
|
2363
2290
|
const it = {
|
|
2364
2291
|
"identifier.title": "Accedi",
|
|
2365
2292
|
"identifier.description": "Inserisci la tua e-mail per continuare",
|
|
2366
|
-
"identifier.field.email": "E-mail
|
|
2367
|
-
"identifier.field.email.placeholder": "tu@
|
|
2293
|
+
"identifier.field.email": "E-mail",
|
|
2294
|
+
"identifier.field.email.placeholder": "tu@esempio.com",
|
|
2368
2295
|
"identifier.field.password": "Password",
|
|
2369
2296
|
"identifier.action.submit": "Accedi",
|
|
2370
2297
|
"identifier.action.continue": "Accedi",
|
|
@@ -2381,8 +2308,8 @@ const it = {
|
|
|
2381
2308
|
"choose-register.action.register-with-passkey": "Registrati con passkey",
|
|
2382
2309
|
"collect-credentials.title": "Crea il tuo account",
|
|
2383
2310
|
"collect-credentials.description": "Configura e-mail e password",
|
|
2384
|
-
"collect-credentials.field.email": "E-mail
|
|
2385
|
-
"collect-credentials.field.email.placeholder": "tu@
|
|
2311
|
+
"collect-credentials.field.email": "E-mail",
|
|
2312
|
+
"collect-credentials.field.email.placeholder": "tu@esempio.com",
|
|
2386
2313
|
"collect-credentials.field.password": "Password",
|
|
2387
2314
|
"collect-credentials.action.submit": "Registrati",
|
|
2388
2315
|
"register-password.title": "Crea la tua password",
|
|
@@ -2399,8 +2326,8 @@ const it = {
|
|
|
2399
2326
|
"passkey-upsell.action.setup": "Configura passkey",
|
|
2400
2327
|
"collect-passkey-email.title": "Crea il tuo account",
|
|
2401
2328
|
"collect-passkey-email.description": "Inserisci la tua e-mail per configurare una passkey",
|
|
2402
|
-
"collect-passkey-email.field.email": "E-mail
|
|
2403
|
-
"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",
|
|
2404
2331
|
"collect-passkey-email.action.submit": "Continua",
|
|
2405
2332
|
"passkey-enroll.title": "Configura la tua passkey",
|
|
2406
2333
|
"passkey-enroll.description": "Usa Face ID, Touch ID o il PIN del dispositivo per creare una passkey.",
|
|
@@ -2417,11 +2344,12 @@ const it = {
|
|
|
2417
2344
|
"password.action.register.link": "Registrati",
|
|
2418
2345
|
"register.title": "Crea il tuo account",
|
|
2419
2346
|
"register.description": "",
|
|
2420
|
-
"register.field.email": "E-mail
|
|
2421
|
-
"register.field.email.placeholder": "tu@
|
|
2347
|
+
"register.field.email": "E-mail",
|
|
2348
|
+
"register.field.email.placeholder": "tu@esempio.com",
|
|
2422
2349
|
"register.field.password": "Password",
|
|
2423
2350
|
"register.field.givenName": "Nome",
|
|
2424
2351
|
"register.field.familyName": "Cognome",
|
|
2352
|
+
"register.field.companyName": "Nome azienda",
|
|
2425
2353
|
"register.field.dateOfBirth": "Data di nascita",
|
|
2426
2354
|
"register.field.country": "Paese",
|
|
2427
2355
|
"register.field.country.placeholder": "Seleziona il tuo paese",
|
|
@@ -2456,6 +2384,8 @@ const it = {
|
|
|
2456
2384
|
"error.passkey_setup_failed": "La registrazione della passkey non è stata completata. Riprova.",
|
|
2457
2385
|
"error.passkey_unsupported": "Questo dispositivo non supporta le passkey",
|
|
2458
2386
|
"error.passkey_failed": "Qualcosa è andato storto. Riprova.",
|
|
2387
|
+
"error.passkey_invalid": "Non è stato possibile verificare questa passkey. Riprova.",
|
|
2388
|
+
"error.passkey_registration_invalid": "Non è stato possibile verificare la nuova passkey. Riprova a registrarla.",
|
|
2459
2389
|
"error.email_required": "Inserisci un indirizzo e-mail",
|
|
2460
2390
|
"error.email_invalid": "Inserisci un indirizzo e-mail valido",
|
|
2461
2391
|
"error.password_required": "Inserisci una password",
|
|
@@ -2472,6 +2402,40 @@ const it = {
|
|
|
2472
2402
|
"error.sign_in_server.body": "Riprova tra qualche minuto"
|
|
2473
2403
|
};
|
|
2474
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
|
|
2475
2439
|
//#region src/orchestrator/locales/index.ts
|
|
2476
2440
|
const builtinLocales = {
|
|
2477
2441
|
en,
|
|
@@ -2510,128 +2474,411 @@ function zitadelAttributionPillInnerHtml() {
|
|
|
2510
2474
|
* `<zl-*>` atoms. Anything outside this list and the manifest registry is
|
|
2511
2475
|
* stripped at sanitise-time.
|
|
2512
2476
|
*/
|
|
2513
|
-
const STRUCTURAL_TAGS = [
|
|
2514
|
-
"div",
|
|
2515
|
-
"span",
|
|
2516
|
-
"p",
|
|
2517
|
-
"small",
|
|
2518
|
-
"strong",
|
|
2519
|
-
"em",
|
|
2520
|
-
"br",
|
|
2521
|
-
"hr",
|
|
2522
|
-
"h1",
|
|
2523
|
-
"h2",
|
|
2524
|
-
"h3",
|
|
2525
|
-
"h4",
|
|
2526
|
-
"h5",
|
|
2527
|
-
"h6",
|
|
2528
|
-
"ul",
|
|
2529
|
-
"ol",
|
|
2530
|
-
"li",
|
|
2531
|
-
"a",
|
|
2532
|
-
"img",
|
|
2533
|
-
"section",
|
|
2534
|
-
"header",
|
|
2535
|
-
"footer",
|
|
2536
|
-
"main",
|
|
2537
|
-
"nav",
|
|
2538
|
-
"aside",
|
|
2539
|
-
"article",
|
|
2540
|
-
"figure",
|
|
2541
|
-
"figcaption",
|
|
2542
|
-
"label",
|
|
2543
|
-
"fieldset",
|
|
2544
|
-
"legend"
|
|
2545
|
-
];
|
|
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();
|
|
2546
2822
|
/**
|
|
2547
|
-
*
|
|
2548
|
-
*
|
|
2549
|
-
*
|
|
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.
|
|
2550
2826
|
*/
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
"class",
|
|
2554
|
-
"role",
|
|
2555
|
-
"slot",
|
|
2556
|
-
"tabindex",
|
|
2557
|
-
"lang",
|
|
2558
|
-
"dir",
|
|
2559
|
-
"alt",
|
|
2560
|
-
"title",
|
|
2561
|
-
"src",
|
|
2562
|
-
"srcset",
|
|
2563
|
-
"sizes",
|
|
2564
|
-
"href",
|
|
2565
|
-
"target",
|
|
2566
|
-
"rel",
|
|
2567
|
-
"style",
|
|
2568
|
-
"name",
|
|
2569
|
-
"for",
|
|
2570
|
-
"data-testid",
|
|
2571
|
-
"data-theme",
|
|
2572
|
-
"hidden"
|
|
2573
|
-
];
|
|
2574
|
-
const CUSTOM_TAG_PATTERN = /^zl-[a-z0-9-]+$/;
|
|
2575
|
-
function buildAllowedTagList() {
|
|
2576
|
-
const set = new Set(STRUCTURAL_TAGS);
|
|
2577
|
-
for (const manifest of manifestRegistry) set.add(manifest.tag);
|
|
2578
|
-
return [...set];
|
|
2579
|
-
}
|
|
2580
|
-
function buildAllowedAttrList() {
|
|
2581
|
-
const set = new Set(COMMON_ATTRS);
|
|
2582
|
-
for (const manifest of manifestRegistry) for (const attr of manifest.attrs) set.add(attr);
|
|
2583
|
-
return [...set];
|
|
2827
|
+
function applyFontUrl(shadowRoot, fontUrl) {
|
|
2828
|
+
applyLinkedStylesheet(shadowRoot, TENANT_LINK_ID, fontUrl);
|
|
2584
2829
|
}
|
|
2585
2830
|
/**
|
|
2586
|
-
*
|
|
2587
|
-
*
|
|
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).
|
|
2588
2835
|
*/
|
|
2589
|
-
function
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
};
|
|
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);
|
|
2625
2871
|
}
|
|
2626
|
-
//#endregion
|
|
2627
|
-
//#region src/orchestrator/templates/layout-chrome.css?inline
|
|
2628
|
-
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";
|
|
2629
2872
|
//#endregion
|
|
2630
2873
|
//#region src/orchestrator/theme-controller.ts
|
|
2631
2874
|
const DARK_QUERY = "(prefers-color-scheme: dark)";
|
|
2632
2875
|
var ThemeController = class {
|
|
2633
2876
|
host;
|
|
2634
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";
|
|
2635
2882
|
mediaQuery = null;
|
|
2636
2883
|
_theme = "dark";
|
|
2637
2884
|
constructor(host) {
|
|
@@ -2645,6 +2892,19 @@ var ThemeController = class {
|
|
|
2645
2892
|
this.branding = branding;
|
|
2646
2893
|
this.refresh();
|
|
2647
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
|
+
}
|
|
2648
2908
|
hostConnected() {
|
|
2649
2909
|
this.refresh();
|
|
2650
2910
|
}
|
|
@@ -2652,7 +2912,7 @@ var ThemeController = class {
|
|
|
2652
2912
|
this.detach();
|
|
2653
2913
|
}
|
|
2654
2914
|
refresh() {
|
|
2655
|
-
const mode = this.branding?.theme?.mode ??
|
|
2915
|
+
const mode = this.explicitMode ?? this.branding?.theme?.mode ?? this.fallbackMode;
|
|
2656
2916
|
if (mode === "auto") {
|
|
2657
2917
|
this.attach();
|
|
2658
2918
|
this.update(this.mediaQuery?.matches === false ? "light" : "dark");
|
|
@@ -2681,8 +2941,72 @@ var ThemeController = class {
|
|
|
2681
2941
|
}
|
|
2682
2942
|
};
|
|
2683
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
|
|
2684
3004
|
//#region src/orchestrator/zitadel-login.ts
|
|
2685
|
-
|
|
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 {
|
|
2686
3010
|
static shadowRootOptions = {
|
|
2687
3011
|
...LitElement.shadowRootOptions,
|
|
2688
3012
|
delegatesFocus: true
|
|
@@ -2691,7 +3015,6 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2691
3015
|
:host {
|
|
2692
3016
|
display: block;
|
|
2693
3017
|
width: 100%;
|
|
2694
|
-
min-height: 100vh;
|
|
2695
3018
|
}
|
|
2696
3019
|
`;
|
|
2697
3020
|
#_purpose_accessor_storage = "login";
|
|
@@ -2799,7 +3122,6 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2799
3122
|
set formValues(value) {
|
|
2800
3123
|
this.#_formValues_accessor_storage = value;
|
|
2801
3124
|
}
|
|
2802
|
-
themeController = new ThemeController(this);
|
|
2803
3125
|
engine = null;
|
|
2804
3126
|
sanitise = createSanitiser();
|
|
2805
3127
|
/**
|
|
@@ -2807,6 +3129,25 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2807
3129
|
* every `formValues` change otherwise re-parses the same template.
|
|
2808
3130
|
*/
|
|
2809
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);
|
|
2810
3151
|
createRenderRoot() {
|
|
2811
3152
|
const root = super.createRenderRoot();
|
|
2812
3153
|
if (root instanceof ShadowRoot) {
|
|
@@ -2825,6 +3166,14 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2825
3166
|
}
|
|
2826
3167
|
return root;
|
|
2827
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
|
+
}
|
|
2828
3177
|
/**
|
|
2829
3178
|
* Start the flow after the first render rather than in `connectedCallback`.
|
|
2830
3179
|
* Frameworks that wrap web components (e.g. `@lit/react` in the console)
|
|
@@ -2851,28 +3200,25 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2851
3200
|
const primary = ((this.lang || (typeof navigator !== "undefined" ? navigator.language : "") || (typeof document !== "undefined" ? document.documentElement.lang : "")).split("-")[0] ?? "").toLowerCase();
|
|
2852
3201
|
const builtin = builtinLocales[primary] ?? en;
|
|
2853
3202
|
const custom = this.locales?.[primary];
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
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;
|
|
2858
3207
|
}
|
|
2859
3208
|
willUpdate(changed) {
|
|
2860
3209
|
if (!this.engine || changed.has("locales") || changed.has("lang")) this.engine = createLiquidEngine({ locale: this.resolveLocale() });
|
|
2861
|
-
|
|
2862
|
-
if (root) {
|
|
2863
|
-
applyBaseTokens(root);
|
|
2864
|
-
applyBrandingTokens(root, this.branding, this.themeController.theme);
|
|
2865
|
-
const tenantFontUrl = this.branding?.font_url ?? null;
|
|
2866
|
-
applyDefaultFont(root, tenantFontUrl ? null : void 0);
|
|
2867
|
-
applyFontUrl(root, tenantFontUrl);
|
|
2868
|
-
}
|
|
2869
|
-
this.dataset.theme = this.themeController.theme;
|
|
2870
|
-
this.toggleAttribute("data-theme-dark", this.themeController.theme === "dark");
|
|
3210
|
+
this.applySurfaceTheme(this.branding);
|
|
2871
3211
|
this.setAttribute("aria-busy", this.loading ? "true" : "false");
|
|
2872
3212
|
}
|
|
2873
3213
|
updated(changed) {
|
|
2874
|
-
if (
|
|
2875
|
-
|
|
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);
|
|
2876
3222
|
}
|
|
2877
3223
|
/**
|
|
2878
3224
|
* Apply captured values and move focus once the new step has fully
|
|
@@ -2880,13 +3226,18 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2880
3226
|
* but those render their own shadow DOM on a later microtask — so await
|
|
2881
3227
|
* this element's update *and* the child atoms' first render before touching
|
|
2882
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.
|
|
2883
3234
|
*/
|
|
2884
|
-
async hydrateStepAfterRender() {
|
|
3235
|
+
async hydrateStepAfterRender(initial = false) {
|
|
2885
3236
|
await this.updateComplete;
|
|
2886
3237
|
const atoms = this.shadowRoot?.querySelectorAll("zl-field, zl-button");
|
|
2887
3238
|
if (atoms) await Promise.all(Array.from(atoms).map((atom) => atom.updateComplete));
|
|
2888
3239
|
this.applyValuesToFields();
|
|
2889
|
-
this.moveFocusToFirstField();
|
|
3240
|
+
if (!initial || this.variant === "page") this.moveFocusToFirstField();
|
|
2890
3241
|
}
|
|
2891
3242
|
render() {
|
|
2892
3243
|
if (this.startupError) return html`<form class="zl-mount" novalidate>
|
|
@@ -2960,6 +3311,7 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2960
3311
|
});
|
|
2961
3312
|
}
|
|
2962
3313
|
this.applyResponse(wire);
|
|
3314
|
+
emit(this, "zitadel-flow-step", { step: wire.step });
|
|
2963
3315
|
} catch (error) {
|
|
2964
3316
|
this.handleTransportError(this.describeFlowSelectionError(error));
|
|
2965
3317
|
} finally {
|
|
@@ -2990,6 +3342,22 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
2990
3342
|
...collectInitialValues(wire.step),
|
|
2991
3343
|
...this.formValues
|
|
2992
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
|
+
}
|
|
2993
3361
|
this.maybeCompleteFlow(wire);
|
|
2994
3362
|
}
|
|
2995
3363
|
/**
|
|
@@ -3096,42 +3464,89 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
3096
3464
|
...display ? { display_name: display } : email ? { display_name: email } : {}
|
|
3097
3465
|
};
|
|
3098
3466
|
}
|
|
3099
|
-
|
|
3467
|
+
/** All rendered input atoms exposing the `formValue` contract. */
|
|
3468
|
+
fieldAtoms() {
|
|
3100
3469
|
const root = this.shadowRoot;
|
|
3101
|
-
if (!root) return;
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
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;
|
|
3107
3477
|
const next = this.formValues[name];
|
|
3108
|
-
if (
|
|
3478
|
+
if (next !== void 0 && atom.formValue !== next) atom.formValue = next;
|
|
3109
3479
|
}
|
|
3110
3480
|
}
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
const
|
|
3121
|
-
if (
|
|
3122
|
-
if (field.value !== value) field.value = value;
|
|
3123
|
-
if (next[name] !== value) {
|
|
3124
|
-
next[name] = value;
|
|
3125
|
-
changed = true;
|
|
3126
|
-
}
|
|
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);
|
|
3127
3492
|
}
|
|
3128
|
-
|
|
3129
|
-
|
|
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
|
+
};
|
|
3130
3519
|
}
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
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;
|
|
3135
3550
|
}
|
|
3136
3551
|
/**
|
|
3137
3552
|
* True once the user retired the current step error by editing a field or
|
|
@@ -3159,10 +3574,25 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
3159
3574
|
value
|
|
3160
3575
|
});
|
|
3161
3576
|
};
|
|
3162
|
-
/**
|
|
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
|
+
*/
|
|
3163
3587
|
handleAtomEdited = (event) => {
|
|
3164
3588
|
const name = event.detail?.name;
|
|
3165
|
-
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);
|
|
3166
3596
|
};
|
|
3167
3597
|
/** Explicit dismiss of the step-error alert (it removes itself). */
|
|
3168
3598
|
handleAlertDismiss = (event) => {
|
|
@@ -3188,16 +3618,9 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
3188
3618
|
field.error = "";
|
|
3189
3619
|
}
|
|
3190
3620
|
}
|
|
3621
|
+
/** Mirror a value onto the matching rendered atom (used after atom events). */
|
|
3191
3622
|
syncFieldElementValue(name, value) {
|
|
3192
|
-
const
|
|
3193
|
-
if (!root) return;
|
|
3194
|
-
const fields = root.querySelectorAll("zl-field");
|
|
3195
|
-
for (const field of fields) {
|
|
3196
|
-
if (field.getAttribute("name") !== name) continue;
|
|
3197
|
-
if (field.value !== value) field.value = value;
|
|
3198
|
-
const native = field.shadowRoot?.querySelector("input");
|
|
3199
|
-
if (native && native.value !== value) native.value = value;
|
|
3200
|
-
}
|
|
3623
|
+
for (const atom of this.fieldAtoms()) if (atom.getAttribute("name") === name && atom.formValue !== value) atom.formValue = value;
|
|
3201
3624
|
}
|
|
3202
3625
|
handleAtomSubmit = (event) => {
|
|
3203
3626
|
if (this.loading) return;
|
|
@@ -3215,6 +3638,11 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
3215
3638
|
handleFormSubmit = (event) => {
|
|
3216
3639
|
event.preventDefault();
|
|
3217
3640
|
if (this.loading) return;
|
|
3641
|
+
const missing = this.missingRequiredFields();
|
|
3642
|
+
if (missing.length > 0) {
|
|
3643
|
+
this.reportRequiredErrors(missing);
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3218
3646
|
const action = event.submitter?.getAttribute?.("action") ?? null ?? this.findPrimaryAction();
|
|
3219
3647
|
this.submit(action);
|
|
3220
3648
|
};
|
|
@@ -3277,13 +3705,7 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
3277
3705
|
const { id, session_token } = this.response;
|
|
3278
3706
|
this.loading = true;
|
|
3279
3707
|
try {
|
|
3280
|
-
const
|
|
3281
|
-
const stepFields = this.response.step.fields ?? [];
|
|
3282
|
-
const fields = {};
|
|
3283
|
-
for (const f of stepFields) {
|
|
3284
|
-
const value = formValues[f.name];
|
|
3285
|
-
if (value !== void 0) fields[f.name] = value;
|
|
3286
|
-
}
|
|
3708
|
+
const fields = this.collectSubmitFields();
|
|
3287
3709
|
const body = {
|
|
3288
3710
|
session_token,
|
|
3289
3711
|
action: action ?? "submit",
|
|
@@ -3300,6 +3722,47 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
|
|
|
3300
3722
|
this.loading = false;
|
|
3301
3723
|
}
|
|
3302
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
|
+
}
|
|
3303
3766
|
handleTransportError(error) {
|
|
3304
3767
|
const message = error instanceof ApiError ? apiErrorMessage(error) : error instanceof Error ? error.message : "Unexpected error contacting the Flow API.";
|
|
3305
3768
|
this.startupError = message;
|
|
@@ -3338,6 +3801,16 @@ __decorate([state()], ZitadelLogin.prototype, "loading", null);
|
|
|
3338
3801
|
__decorate([state()], ZitadelLogin.prototype, "startupError", null);
|
|
3339
3802
|
__decorate([state()], ZitadelLogin.prototype, "formValues", null);
|
|
3340
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
|
+
}
|
|
3341
3814
|
function collectInitialValues(step) {
|
|
3342
3815
|
const values = {};
|
|
3343
3816
|
if (!step.fields) return values;
|
|
@@ -3519,12 +3992,12 @@ let ZitadelLogout = class ZitadelLogout extends LitElement {
|
|
|
3519
3992
|
set postSignOutUrl(value) {
|
|
3520
3993
|
this.#_postSignOutUrl_accessor_storage = value;
|
|
3521
3994
|
}
|
|
3522
|
-
#
|
|
3523
|
-
get
|
|
3524
|
-
return this.#
|
|
3995
|
+
#_theme_accessor_storage = "";
|
|
3996
|
+
get theme() {
|
|
3997
|
+
return this.#_theme_accessor_storage;
|
|
3525
3998
|
}
|
|
3526
|
-
set
|
|
3527
|
-
this.#
|
|
3999
|
+
set theme(value) {
|
|
4000
|
+
this.#_theme_accessor_storage = value;
|
|
3528
4001
|
}
|
|
3529
4002
|
#_displayName_accessor_storage = "";
|
|
3530
4003
|
get displayName() {
|
|
@@ -3572,9 +4045,9 @@ let ZitadelLogout = class ZitadelLogout extends LitElement {
|
|
|
3572
4045
|
pendingTemplate = null;
|
|
3573
4046
|
projectedContainer = null;
|
|
3574
4047
|
identityRequested = false;
|
|
4048
|
+
themeController = new ThemeController(this);
|
|
3575
4049
|
connectedCallback() {
|
|
3576
4050
|
super.connectedCallback();
|
|
3577
|
-
this.dataset.theme = "dark";
|
|
3578
4051
|
const tmpl = this.querySelector("template");
|
|
3579
4052
|
if (tmpl instanceof HTMLTemplateElement) {
|
|
3580
4053
|
this.templateMode = true;
|
|
@@ -3590,9 +4063,17 @@ let ZitadelLogout = class ZitadelLogout extends LitElement {
|
|
|
3590
4063
|
document.removeEventListener("click", this.handleDocumentClick);
|
|
3591
4064
|
document.removeEventListener("keydown", this.handleDocumentKeydown);
|
|
3592
4065
|
}
|
|
3593
|
-
|
|
4066
|
+
willUpdate() {
|
|
4067
|
+
this.themeController.setModePreference(this.theme === "" ? void 0 : this.theme, "auto");
|
|
3594
4068
|
const root = this.shadowRoot;
|
|
3595
|
-
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() {
|
|
3596
4077
|
this.maybeLoadIdentity();
|
|
3597
4078
|
}
|
|
3598
4079
|
/** Declarative config read from this element's attributes. */
|
|
@@ -3703,20 +4184,6 @@ let ZitadelLogout = class ZitadelLogout extends LitElement {
|
|
|
3703
4184
|
});
|
|
3704
4185
|
if (this.postSignOutUrl && typeof window !== "undefined") window.location.href = this.postSignOutUrl;
|
|
3705
4186
|
}
|
|
3706
|
-
/**
|
|
3707
|
-
* Returns the absolute URL the end-session request will hit. Useful for
|
|
3708
|
-
* test assertions and for consumers that prefer to navigate the browser
|
|
3709
|
-
* directly (instead of fetching) so the OIDC session-end redirect is
|
|
3710
|
-
* driven by the user agent.
|
|
3711
|
-
*/
|
|
3712
|
-
getEndSessionUrl() {
|
|
3713
|
-
const params = {
|
|
3714
|
-
...this.clientId ? { client_id: this.clientId } : {},
|
|
3715
|
-
...this.postSignOutUrl ? { post_logout_redirect_uri: this.postSignOutUrl } : {}
|
|
3716
|
-
};
|
|
3717
|
-
const { api } = resolveApi(this.project, this.projectAttrs, "<zitadel-logout>");
|
|
3718
|
-
return api.getEndSessionUrl(params);
|
|
3719
|
-
}
|
|
3720
4187
|
handleSignOutClick(event) {
|
|
3721
4188
|
event.preventDefault();
|
|
3722
4189
|
this.doLogout();
|
|
@@ -3795,10 +4262,7 @@ __decorate([property({
|
|
|
3795
4262
|
type: String,
|
|
3796
4263
|
attribute: "post-sign-out-url"
|
|
3797
4264
|
})], ZitadelLogout.prototype, "postSignOutUrl", null);
|
|
3798
|
-
__decorate([property({
|
|
3799
|
-
type: String,
|
|
3800
|
-
attribute: "client-id"
|
|
3801
|
-
})], ZitadelLogout.prototype, "clientId", null);
|
|
4265
|
+
__decorate([property({ type: String })], ZitadelLogout.prototype, "theme", null);
|
|
3802
4266
|
__decorate([state()], ZitadelLogout.prototype, "displayName", null);
|
|
3803
4267
|
__decorate([state()], ZitadelLogout.prototype, "displayEmail", null);
|
|
3804
4268
|
__decorate([state()], ZitadelLogout.prototype, "displayUserId", null);
|
|
@@ -3821,13 +4285,11 @@ function fillTemplateTokens(fragment, name, email, initial) {
|
|
|
3821
4285
|
}
|
|
3822
4286
|
//#endregion
|
|
3823
4287
|
//#region src/orchestrator/zitadel-session.ts
|
|
3824
|
-
let ZitadelSession = class ZitadelSession extends
|
|
4288
|
+
let ZitadelSession = class ZitadelSession extends ZitadelSurface {
|
|
3825
4289
|
static styles = [baseHostStyles, css`
|
|
3826
4290
|
:host {
|
|
3827
4291
|
display: block;
|
|
3828
|
-
|
|
3829
|
-
background: ${t.color.surface.defaultBlack};
|
|
3830
|
-
color: ${t.color.text.primaryWhite};
|
|
4292
|
+
width: 100%;
|
|
3831
4293
|
}
|
|
3832
4294
|
.title {
|
|
3833
4295
|
margin: 0;
|
|
@@ -3943,16 +4405,16 @@ let ZitadelSession = class ZitadelSession extends LitElement {
|
|
|
3943
4405
|
identityRequested = false;
|
|
3944
4406
|
connectedCallback() {
|
|
3945
4407
|
super.connectedCallback();
|
|
3946
|
-
this.dataset.theme = "dark";
|
|
3947
4408
|
this.maybeLoadIdentity();
|
|
3948
4409
|
}
|
|
3949
4410
|
/** Single identity line: human-readable name, then email, then user_id. */
|
|
3950
4411
|
get identityLabel() {
|
|
3951
4412
|
return this.displayName || this.displayEmail || this.displayUserId;
|
|
3952
4413
|
}
|
|
4414
|
+
willUpdate() {
|
|
4415
|
+
this.applySurfaceTheme(void 0);
|
|
4416
|
+
}
|
|
3953
4417
|
updated() {
|
|
3954
|
-
const root = this.shadowRoot;
|
|
3955
|
-
if (root) applyBaseTokens(root);
|
|
3956
4418
|
this.maybeLoadIdentity();
|
|
3957
4419
|
}
|
|
3958
4420
|
/** Declarative config read from this element's attributes. */
|
|
@@ -4017,7 +4479,7 @@ let ZitadelSession = class ZitadelSession extends LitElement {
|
|
|
4017
4479
|
}
|
|
4018
4480
|
render() {
|
|
4019
4481
|
return html`
|
|
4020
|
-
<zl-page-shell>
|
|
4482
|
+
<zl-page-shell ?data-widget=${this.variant !== "page"}>
|
|
4021
4483
|
<zl-card>
|
|
4022
4484
|
<h1 slot="header" class="title">${this.heading}</h1>
|
|
4023
4485
|
${this.identityLabel ? html`<p slot="header" class="identity">${this.identityLabel}</p>` : nothing}
|
|
@@ -4064,6 +4526,6 @@ __decorate([state()], ZitadelSession.prototype, "loading", null);
|
|
|
4064
4526
|
__decorate([state()], ZitadelSession.prototype, "errorMessage", null);
|
|
4065
4527
|
ZitadelSession = __decorate([customElement("zitadel-session")], ZitadelSession);
|
|
4066
4528
|
//#endregion
|
|
4067
|
-
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 };
|
|
4068
4530
|
|
|
4069
|
-
//# sourceMappingURL=orchestrator-
|
|
4531
|
+
//# sourceMappingURL=orchestrator-CctLkpmB.mjs.map
|