@zitadel/components 0.1.0-alpha.18 → 0.1.0-alpha.19
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 +14 -7
- package/dist/atoms/index.d.mts +1 -1
- package/dist/atoms/index.mjs +1 -1
- package/dist/{atoms-5-wYIl88.mjs → atoms-B6QmUAzo.mjs} +4 -4
- package/dist/atoms-B6QmUAzo.mjs.map +1 -0
- package/dist/{index-DIGjtG4z.d.mts → index-CAi5gsHK.d.mts} +2333 -334
- package/dist/index-CAi5gsHK.d.mts.map +1 -0
- package/dist/{index-CsJiPZtJ.d.mts → index-Ce_81oFH.d.mts} +88 -41
- package/dist/index-Ce_81oFH.d.mts.map +1 -0
- package/dist/{index-BRGZPZ2y.d.mts → index-Dgc94IMN.d.mts} +4 -2
- package/dist/index-Dgc94IMN.d.mts.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +3 -3
- package/dist/jsx.d.ts +4 -0
- package/dist/manifests.mjs +1 -1
- package/dist/orchestrator/index.d.mts +2 -2
- package/dist/orchestrator/index.mjs +1 -1
- package/dist/{orchestrator-CctLkpmB.mjs → orchestrator-CsHP57zo.mjs} +2129 -1985
- package/dist/orchestrator-CsHP57zo.mjs.map +1 -0
- package/dist/standalone.mjs +6556 -6362
- package/dist/tokens/index.d.mts +1 -1
- package/dist/tokens/index.mjs +1 -1
- package/dist/{tokens-BV-PPbQd.mjs → tokens-DSnSf1mf.mjs} +7 -5
- package/dist/tokens-DSnSf1mf.mjs.map +1 -0
- package/package.json +6 -6
- package/dist/atoms-5-wYIl88.mjs.map +0 -1
- package/dist/index-BRGZPZ2y.d.mts.map +0 -1
- package/dist/index-CsJiPZtJ.d.mts.map +0 -1
- package/dist/index-DIGjtG4z.d.mts.map +0 -1
- package/dist/orchestrator-CctLkpmB.mjs.map +0 -1
- package/dist/tokens-BV-PPbQd.mjs.map +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { C as focusVisibleStyles, E as emit, S as __decorate, T as t, d as hookName, w as baseHostStyles } from "./atoms-
|
|
2
|
-
import { r as tokensCss } from "./tokens-
|
|
1
|
+
import { C as focusVisibleStyles, E as emit, S as __decorate, T as t, d as hookName, w as baseHostStyles } from "./atoms-B6QmUAzo.mjs";
|
|
2
|
+
import { r as tokensCss } from "./tokens-DSnSf1mf.mjs";
|
|
3
3
|
import { manifestRegistry } from "./manifests.mjs";
|
|
4
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 { isCanonicalLoopbackHttpUrl } from "@zitadel/config/branding-url";
|
|
8
9
|
import { MANDATORY_GATES_TAG } from "@zitadel/config/template";
|
|
9
10
|
import { Liquid } from "liquidjs";
|
|
10
11
|
import DOMPurify from "dompurify";
|
|
@@ -130,6 +131,30 @@ function isRecord(value) {
|
|
|
130
131
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
131
132
|
}
|
|
132
133
|
//#endregion
|
|
134
|
+
//#region ../shared-component-styles/dist/attribution-markup.mjs
|
|
135
|
+
/**
|
|
136
|
+
* Figma sign-in attribution chip (`6593:141767`): "Secured with" + 65×16 logotype.
|
|
137
|
+
* Shared by Lit orchestrator HTML, dev playground, and paired React playgrounds.
|
|
138
|
+
*/
|
|
139
|
+
const ZITADEL_ATTRIBUTION_LOGOTYPE_SVG = "<svg class=\"zr-pill__logotype-svg\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 67.3501 15.1111\" width=\"65\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M2.04839 4.265C2.06224 4.1284 2.1748 3.98591 2.32756 3.91161L8.98593 0.0494262C9.227 -0.0678242 9.45274 0.0334116 9.43089 0.248972V4.52681C9.41705 4.66341 9.30448 4.80589 9.15172 4.88019L2.49335 8.66426C2.25228 8.78151 2.02654 8.68027 2.04839 8.46471V4.265Z\"/><path d=\"M14.0086 5.25359C14.12 5.33388 14.1871 5.50261 14.1751 5.67205V13.2643C14.1561 13.5317 13.9555 13.6766 13.7798 13.5499L10.1005 11.4468C9.98908 11.3666 9.92196 11.1978 9.934 11.0284V3.32695C9.95299 3.05956 10.1535 2.91467 10.3293 3.04138L14.0086 5.25359Z\"/><path d=\"M7.37428 15.0817C7.24906 15.138 7.06938 15.1118 6.92865 15.0167L0.183465 11.1405C-0.038611 10.9903 -0.0638098 10.7442 0.133796 10.6554L3.76278 8.53833C3.888 8.48203 4.06767 8.50827 4.2084 8.60341L10.9046 12.5341C11.1267 12.6843 11.1557 12.9125 10.9543 13.0193L7.37428 15.0817Z\"/><path d=\"M65.6687 13.1043V2.00077H67.3501V13.1043H65.6687Z\"/><path d=\"M60.4255 13.2947C59.6535 13.2947 58.9715 13.1202 58.3793 12.7712C57.7871 12.4222 57.3218 11.9358 56.9834 11.3119C56.6556 10.688 56.4917 9.97418 56.4917 9.1705C56.4917 8.33509 56.6556 7.61073 56.9834 6.99739C57.3112 6.38405 57.7712 5.90819 58.3634 5.56979C58.9662 5.22083 59.6588 5.04634 60.4414 5.04634C61.2027 5.04634 61.869 5.21554 62.44 5.55393C63.011 5.88175 63.4552 6.33118 63.7724 6.90222C64.1002 7.47326 64.2642 8.12359 64.2642 8.85325C64.2642 9.0013 64.2589 9.1282 64.2483 9.23395C64.2483 9.33969 64.2377 9.46659 64.2166 9.61464H58.1731C58.2048 10.0588 58.3158 10.4553 58.5062 10.8043C58.6965 11.1427 58.9556 11.4124 59.2834 11.6133C59.6112 11.8036 59.9866 11.8988 60.4096 11.8988C60.8644 11.8988 61.2503 11.7983 61.5676 11.5974C61.8954 11.3859 62.1386 11.0898 62.2972 10.7091H64.0421C63.9046 11.185 63.672 11.6186 63.3441 12.0098C63.0269 12.4011 62.6251 12.7131 62.1386 12.9457C61.6522 13.1784 61.0811 13.2947 60.4255 13.2947ZM58.1889 8.42497H62.551C62.5299 7.81163 62.3131 7.3305 61.9007 6.98153C61.4883 6.62199 60.986 6.44222 60.3938 6.44222C59.865 6.44222 59.3839 6.60613 58.9503 6.93394C58.5168 7.26176 58.263 7.75876 58.1889 8.42497Z\"/><path d=\"M50.5389 13.2947C49.7563 13.2947 49.0796 13.1096 48.5085 12.7395C47.9481 12.3588 47.5145 11.8565 47.2078 11.2326C46.9117 10.6087 46.7637 9.91073 46.7637 9.13877C46.7637 8.37739 46.9117 7.69004 47.2078 7.0767C47.5145 6.45279 47.9481 5.96106 48.5085 5.60152C49.0796 5.2314 49.7563 5.04634 50.5389 5.04634C51.1205 5.04634 51.6492 5.16795 52.1251 5.41117C52.601 5.64382 52.9869 5.97692 53.283 6.41049V2.00077H54.9803V13.1043H53.3465L53.283 11.8829C53.0292 12.2954 52.6644 12.6337 52.1886 12.8981C51.7127 13.1625 51.1628 13.2947 50.5389 13.2947ZM50.872 11.8195C51.369 11.8195 51.7973 11.7032 52.1568 11.4705C52.5164 11.2379 52.7913 10.9206 52.9817 10.5188C53.172 10.1169 53.2672 9.66751 53.2672 9.1705C53.2672 8.59946 53.1561 8.1183 52.9341 7.72704C52.7226 7.33577 52.4318 7.0344 52.0617 6.82291C51.7021 6.61141 51.3003 6.50567 50.8561 6.50567C50.4014 6.50567 49.9943 6.6167 49.6347 6.83877C49.2752 7.06084 48.9897 7.36749 48.7782 7.75876C48.5773 8.15003 48.4768 8.61532 48.4768 9.15463C48.4768 9.68337 48.5825 10.1487 48.794 10.5505C49.0055 10.9524 49.2911 11.2643 49.6506 11.4864C50.0101 11.7085 50.4173 11.8195 50.872 11.8195Z\"/><path d=\"M40.7882 13.2947C40.2806 13.2947 39.8153 13.2048 39.3923 13.025C38.9693 12.8347 38.6309 12.5597 38.3771 12.2002C38.1233 11.8406 37.9965 11.4071 37.9965 10.8995C37.9965 10.2967 38.1445 9.81556 38.4406 9.45602C38.7473 9.0859 39.1544 8.81624 39.662 8.64704C40.1801 8.46727 40.7459 8.37739 41.3592 8.37739H43.3579C43.3579 7.92267 43.2839 7.55786 43.1358 7.28291C42.9878 6.99739 42.7868 6.79118 42.533 6.66429C42.2792 6.52681 41.9832 6.45808 41.6448 6.45808C41.2006 6.45808 40.8094 6.56383 40.471 6.77532C40.1431 6.97624 39.9369 7.29876 39.8523 7.7429H38.1551C38.2079 7.17186 38.3983 6.68544 38.7261 6.28359C39.0645 5.88175 39.4928 5.57508 40.011 5.36358C40.5291 5.15209 41.079 5.04634 41.6606 5.04634C42.422 5.04634 43.0512 5.1891 43.5482 5.47462C44.0558 5.74957 44.4312 6.13555 44.6744 6.63256C44.9282 7.12958 45.0551 7.69531 45.0551 8.3298V10.7409C45.0657 11.0475 45.1292 11.2749 45.2455 11.4229C45.3724 11.5604 45.5945 11.6397 45.9117 11.6609V13.1043C45.4887 13.1043 45.1186 13.0567 44.8013 12.9616C44.4947 12.8664 44.2409 12.7236 44.04 12.5333C43.839 12.3324 43.6804 12.0786 43.5641 11.7719C43.3103 12.1949 42.9402 12.5544 42.4537 12.8505C41.9673 13.1466 41.4121 13.2947 40.7882 13.2947ZM41.1848 11.9147C41.6183 11.9147 41.999 11.8089 42.3268 11.5974C42.6547 11.3753 42.9084 11.0898 43.0882 10.7409C43.268 10.3813 43.3579 9.99533 43.3579 9.58291V9.55119H41.4861C41.1795 9.55119 40.8887 9.5882 40.6137 9.66223C40.3493 9.73625 40.1326 9.86315 39.9634 10.0429C39.8047 10.2121 39.7254 10.45 39.7254 10.7567C39.7254 11.1586 39.8682 11.4547 40.1537 11.645C40.4392 11.8248 40.7829 11.9147 41.1848 11.9147Z\"/><path d=\"M35.5197 13.1043C34.7372 13.1043 34.1344 12.914 33.7114 12.5333C33.2884 12.1526 33.0769 11.5287 33.0769 10.6615V6.64842H31.8873V5.23669H33.0769V3.3332H34.7583V5.23669H36.757V6.64842H34.7583V10.6615C34.7583 11.0634 34.8482 11.333 35.028 11.4705C35.2078 11.608 35.488 11.6767 35.8687 11.6767H36.757V13.1043H35.5197Z\"/><path d=\"M29.6602 3.85668C29.3536 3.85668 29.0892 3.7509 28.8671 3.53941C28.6556 3.31734 28.5499 3.04768 28.5499 2.73043C28.5499 2.42376 28.6556 2.16468 28.8671 1.95319C29.0892 1.74169 29.3536 1.63594 29.6602 1.63594C29.9775 1.63594 30.2419 1.74169 30.4533 1.95319C30.6754 2.16468 30.7865 2.42376 30.7865 2.73043C30.7865 3.04768 30.6754 3.31734 30.4533 3.53941C30.2419 3.7509 29.9775 3.85668 29.6602 3.85668ZM28.8195 13.1043V5.23669H30.5009V13.1043H28.8195Z\"/><path d=\"M19.8276 13.1043V11.8036L25.1098 3.42837H19.8911V2.00077H27.0608V3.30147L21.7628 11.6609H27.1084V13.1043H19.8276Z\"/></svg>";
|
|
140
|
+
/** Inner markup for the default Zitadel attribution pill (label + logotype). */
|
|
141
|
+
function zitadelAttributionPillInnerHtml() {
|
|
142
|
+
return `Secured with <span class="zr-pill__logotype">${ZITADEL_ATTRIBUTION_LOGOTYPE_SVG}</span>`;
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/internal/escape-html.ts
|
|
146
|
+
/**
|
|
147
|
+
* Escapes the five HTML-significant characters so a string is safe to
|
|
148
|
+
* interpolate into either element text or a double-quoted attribute value.
|
|
149
|
+
*
|
|
150
|
+
* Used for the orchestrator-owned attribution markup, which is appended after
|
|
151
|
+
* the Liquid output has already been sanitised — so it can't rely on the
|
|
152
|
+
* DOMPurify pass and must escape its own (tenant-supplied) values.
|
|
153
|
+
*/
|
|
154
|
+
function escapeHtml(value) {
|
|
155
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
133
158
|
//#region src/orchestrator/api-client.ts
|
|
134
159
|
const apiRequestInit = { credentials: "include" };
|
|
135
160
|
async function startFlow(api, input) {
|
|
@@ -175,2283 +200,2342 @@ async function revokeSession$1(api) {
|
|
|
175
200
|
await api.revokeMySession(apiRequestInit);
|
|
176
201
|
}
|
|
177
202
|
//#endregion
|
|
178
|
-
//#region
|
|
179
|
-
let proxyPath = "";
|
|
180
|
-
function getProxyPath() {
|
|
181
|
-
return proxyPath;
|
|
182
|
-
}
|
|
183
|
-
function setProxyPath(path) {
|
|
184
|
-
proxyPath = path;
|
|
185
|
-
}
|
|
186
|
-
//#endregion
|
|
187
|
-
//#region ../api/dist/chunk-CfYAbeIz.mjs
|
|
188
|
-
var __defProp = Object.defineProperty;
|
|
189
|
-
var __exportAll = (all, no_symbols) => {
|
|
190
|
-
let target = {};
|
|
191
|
-
for (var name in all) __defProp(target, name, {
|
|
192
|
-
get: all[name],
|
|
193
|
-
enumerable: true
|
|
194
|
-
});
|
|
195
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
196
|
-
return target;
|
|
197
|
-
};
|
|
198
|
-
//#endregion
|
|
199
|
-
//#region ../api/dist/generated/endpoints/zitadelNextGen.mjs
|
|
200
|
-
/**
|
|
201
|
-
* Generated by orval v8.10.0 🍺
|
|
202
|
-
* Do not edit manually.
|
|
203
|
-
* Zitadel NextGen
|
|
204
|
-
* This is the next generation of the Zitadel identity platform.
|
|
205
|
-
* OpenAPI spec version: 0.0.1
|
|
206
|
-
*/
|
|
207
|
-
var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
|
|
208
|
-
completeClaim: () => completeClaim,
|
|
209
|
-
createAuthAttempt: () => createAuthAttempt,
|
|
210
|
-
createBranding: () => createBranding,
|
|
211
|
-
createFlow: () => createFlow,
|
|
212
|
-
createFlowDefinition: () => createFlowDefinition,
|
|
213
|
-
createHandoff: () => createHandoff,
|
|
214
|
-
createProject: () => createProject,
|
|
215
|
-
createSchema: () => createSchema,
|
|
216
|
-
createSession: () => createSession,
|
|
217
|
-
createTeam: () => createTeam,
|
|
218
|
-
createUser: () => createUser,
|
|
219
|
-
deleteFlowDefinition: () => deleteFlowDefinition,
|
|
220
|
-
deleteTeam: () => deleteTeam,
|
|
221
|
-
deleteUserByID: () => deleteUserByID,
|
|
222
|
-
exchangeHandoff: () => exchangeHandoff,
|
|
223
|
-
getAuthAttempt: () => getAuthAttempt,
|
|
224
|
-
getBrandingById: () => getBrandingById,
|
|
225
|
-
getClaimStatus: () => getClaimStatus,
|
|
226
|
-
getCompleteClaimUrl: () => getCompleteClaimUrl,
|
|
227
|
-
getCreateAuthAttemptUrl: () => getCreateAuthAttemptUrl,
|
|
228
|
-
getCreateBrandingUrl: () => getCreateBrandingUrl,
|
|
229
|
-
getCreateFlowDefinitionUrl: () => getCreateFlowDefinitionUrl,
|
|
230
|
-
getCreateFlowUrl: () => getCreateFlowUrl,
|
|
231
|
-
getCreateHandoffUrl: () => getCreateHandoffUrl,
|
|
232
|
-
getCreateProjectUrl: () => getCreateProjectUrl,
|
|
233
|
-
getCreateSchemaUrl: () => getCreateSchemaUrl,
|
|
234
|
-
getCreateSessionUrl: () => getCreateSessionUrl,
|
|
235
|
-
getCreateTeamUrl: () => getCreateTeamUrl,
|
|
236
|
-
getCreateUserUrl: () => getCreateUserUrl,
|
|
237
|
-
getDeleteFlowDefinitionUrl: () => getDeleteFlowDefinitionUrl,
|
|
238
|
-
getDeleteTeamUrl: () => getDeleteTeamUrl,
|
|
239
|
-
getDeleteUserByIDUrl: () => getDeleteUserByIDUrl,
|
|
240
|
-
getExchangeHandoffUrl: () => getExchangeHandoffUrl,
|
|
241
|
-
getFlowDefinition: () => getFlowDefinition,
|
|
242
|
-
getFlowStep: () => getFlowStep,
|
|
243
|
-
getGetAuthAttemptUrl: () => getGetAuthAttemptUrl,
|
|
244
|
-
getGetBrandingByIdUrl: () => getGetBrandingByIdUrl,
|
|
245
|
-
getGetClaimStatusUrl: () => getGetClaimStatusUrl,
|
|
246
|
-
getGetFlowDefinitionUrl: () => getGetFlowDefinitionUrl,
|
|
247
|
-
getGetFlowStepUrl: () => getGetFlowStepUrl,
|
|
248
|
-
getGetHealthUrl: () => getGetHealthUrl,
|
|
249
|
-
getGetLiveUrl: () => getGetLiveUrl,
|
|
250
|
-
getGetMySessionUrl: () => getGetMySessionUrl,
|
|
251
|
-
getGetMyUserUrl: () => getGetMyUserUrl,
|
|
252
|
-
getGetProjectUrl: () => getGetProjectUrl,
|
|
253
|
-
getGetReadyUrl: () => getGetReadyUrl,
|
|
254
|
-
getGetSchemaByIdUrl: () => getGetSchemaByIdUrl,
|
|
255
|
-
getGetSessionUrl: () => getGetSessionUrl,
|
|
256
|
-
getGetTeamUrl: () => getGetTeamUrl,
|
|
257
|
-
getGetUserByIDUrl: () => getGetUserByIDUrl,
|
|
258
|
-
getHealth: () => getHealth,
|
|
259
|
-
getInitClaimUrl: () => getInitClaimUrl,
|
|
260
|
-
getIssueChallengeUrl: () => getIssueChallengeUrl,
|
|
261
|
-
getListBrandingUrl: () => getListBrandingUrl,
|
|
262
|
-
getListFlowDefinitionsUrl: () => getListFlowDefinitionsUrl,
|
|
263
|
-
getListSchemasUrl: () => getListSchemasUrl,
|
|
264
|
-
getListUserPasskeysUrl: () => getListUserPasskeysUrl,
|
|
265
|
-
getListUserTeamsUrl: () => getListUserTeamsUrl,
|
|
266
|
-
getListUsersUrl: () => getListUsersUrl,
|
|
267
|
-
getLive: () => getLive,
|
|
268
|
-
getMySession: () => getMySession,
|
|
269
|
-
getMyUser: () => getMyUser,
|
|
270
|
-
getPatchProjectUrl: () => getPatchProjectUrl,
|
|
271
|
-
getProject: () => getProject,
|
|
272
|
-
getQueryProjectsUrl: () => getQueryProjectsUrl,
|
|
273
|
-
getQuerySessionsUrl: () => getQuerySessionsUrl,
|
|
274
|
-
getQueryTeamsUrl: () => getQueryTeamsUrl,
|
|
275
|
-
getReady: () => getReady,
|
|
276
|
-
getRevokeMySessionUrl: () => getRevokeMySessionUrl,
|
|
277
|
-
getRevokeSessionUrl: () => getRevokeSessionUrl,
|
|
278
|
-
getSchemaById: () => getSchemaById,
|
|
279
|
-
getSession: () => getSession,
|
|
280
|
-
getSetUserPasswordUrl: () => getSetUserPasswordUrl,
|
|
281
|
-
getSubmitFlowStepUrl: () => getSubmitFlowStepUrl,
|
|
282
|
-
getTeam: () => getTeam,
|
|
283
|
-
getUpdateFlowDefinitionUrl: () => getUpdateFlowDefinitionUrl,
|
|
284
|
-
getUpdateTeamUrl: () => getUpdateTeamUrl,
|
|
285
|
-
getUserByID: () => getUserByID,
|
|
286
|
-
getVerifyChallengeProofUrl: () => getVerifyChallengeProofUrl,
|
|
287
|
-
initClaim: () => initClaim,
|
|
288
|
-
issueChallenge: () => issueChallenge,
|
|
289
|
-
listBranding: () => listBranding,
|
|
290
|
-
listFlowDefinitions: () => listFlowDefinitions,
|
|
291
|
-
listSchemas: () => listSchemas,
|
|
292
|
-
listUserPasskeys: () => listUserPasskeys,
|
|
293
|
-
listUserTeams: () => listUserTeams,
|
|
294
|
-
listUsers: () => listUsers,
|
|
295
|
-
patchProject: () => patchProject,
|
|
296
|
-
queryProjects: () => queryProjects,
|
|
297
|
-
querySessions: () => querySessions,
|
|
298
|
-
queryTeams: () => queryTeams,
|
|
299
|
-
revokeMySession: () => revokeMySession,
|
|
300
|
-
revokeSession: () => revokeSession,
|
|
301
|
-
setUserPassword: () => setUserPassword,
|
|
302
|
-
submitFlowStep: () => submitFlowStep,
|
|
303
|
-
updateFlowDefinition: () => updateFlowDefinition,
|
|
304
|
-
updateTeam: () => updateTeam,
|
|
305
|
-
verifyChallengeProof: () => verifyChallengeProof
|
|
306
|
-
});
|
|
307
|
-
const getGetHealthUrl = () => {
|
|
308
|
-
return `${getProxyPath()}/healthz`;
|
|
309
|
-
};
|
|
203
|
+
//#region src/orchestrator/asset-fallback.ts
|
|
310
204
|
/**
|
|
311
|
-
*
|
|
312
|
-
*
|
|
205
|
+
* Broken-branding-asset degradation.
|
|
206
|
+
*
|
|
207
|
+
* A `logo_url` / `hero_url` that is well-formed but unreachable clears every
|
|
208
|
+
* gate on the way in — the CLI's zod shape check, the server's save gate —
|
|
209
|
+
* and then renders as a 0×0 `<img>`: a silent hole where the brand should
|
|
210
|
+
* be, with nothing in the console and nothing in `plan` output. The split
|
|
211
|
+
* designs are the worst case, because their `.zl-split__placeholder` is
|
|
212
|
+
* keyed on *no asset being configured*, so a configured-but-dead asset
|
|
213
|
+
* leaves the whole brand pane blank.
|
|
214
|
+
*
|
|
215
|
+
* The templates cannot fix this themselves. They go through DOMPurify
|
|
216
|
+
* (`sanitiser.ts`), which strips `onerror` along with every other inline
|
|
217
|
+
* handler, so the recovery has to be orchestrator-side: arm a real `error`
|
|
218
|
+
* listener on each rendered image after every commit, hide what fails, and
|
|
219
|
+
* put the placeholder back when that empties a brand pane.
|
|
313
220
|
*/
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
};
|
|
221
|
+
/** Marks an image whose `error` listener is already attached. */
|
|
222
|
+
const ARMED_ATTR = "data-zl-asset-armed";
|
|
223
|
+
/** Marks an image that failed to load; `layout-chrome.css` hides it. */
|
|
224
|
+
const BROKEN_ATTR = "data-zl-asset-broken";
|
|
225
|
+
const BRAND_PANE_SELECTOR = ".zl-split__brand";
|
|
226
|
+
const PLACEHOLDER_CLASS = "zl-split__placeholder";
|
|
227
|
+
const AUTHORED_FALLBACK_ATTR = "data-zl-asset-fallback";
|
|
228
|
+
const AUTHORED_FALLBACK_CONTENT_ATTR = "data-zl-asset-fallback-content";
|
|
323
229
|
/**
|
|
324
|
-
*
|
|
325
|
-
*
|
|
230
|
+
* Arm every not-yet-armed `<img>` under `root`, and repair anything already
|
|
231
|
+
* broken. Idempotent: safe to call on every Lit commit, since `unsafeHTML`
|
|
232
|
+
* only re-parses when the rendered string actually changes and re-parsed
|
|
233
|
+
* nodes arrive without the marker attribute.
|
|
326
234
|
*/
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
235
|
+
function armAssetFallbacks(root) {
|
|
236
|
+
let repairNeeded = false;
|
|
237
|
+
for (const img of root.querySelectorAll("img")) {
|
|
238
|
+
if (img.hasAttribute(ARMED_ATTR)) continue;
|
|
239
|
+
img.setAttribute(ARMED_ATTR, "");
|
|
240
|
+
if (img.getAttribute("src") && img.complete && img.naturalWidth === 0) {
|
|
241
|
+
markBroken(img);
|
|
242
|
+
repairNeeded = true;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
img.addEventListener("error", () => {
|
|
246
|
+
markBroken(img);
|
|
247
|
+
repairBrandPanes(root);
|
|
248
|
+
}, { once: true });
|
|
249
|
+
}
|
|
250
|
+
if (repairNeeded) repairBrandPanes(root);
|
|
251
|
+
}
|
|
252
|
+
function markBroken(img) {
|
|
253
|
+
img.setAttribute(BROKEN_ATTR, "");
|
|
254
|
+
revealAuthoredFallback(img);
|
|
255
|
+
console.warn(`[zitadel-login] branding asset failed to load, hiding it: ${img.getAttribute("src") ?? ""}`);
|
|
256
|
+
}
|
|
336
257
|
/**
|
|
337
|
-
*
|
|
338
|
-
*
|
|
258
|
+
* Some shipped templates have a richer no-asset branch than the generic split
|
|
259
|
+
* placeholder (the hero design's brand mark and compact text). They render that
|
|
260
|
+
* branch hidden beside the image and opt in with data attributes, so the
|
|
261
|
+
* orchestrator can reveal the exact authored fallback without knowing template
|
|
262
|
+
* copy or structure.
|
|
339
263
|
*/
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
};
|
|
346
|
-
const getCreateUserUrl = (params) => {
|
|
347
|
-
const normalizedParams = new URLSearchParams();
|
|
348
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
349
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
350
|
-
});
|
|
351
|
-
const stringifiedParams = normalizedParams.toString();
|
|
352
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/users?${stringifiedParams}` : `${getProxyPath()}/users`;
|
|
353
|
-
};
|
|
264
|
+
function revealAuthoredFallback(img) {
|
|
265
|
+
if (!img.hasAttribute(AUTHORED_FALLBACK_ATTR)) return;
|
|
266
|
+
const fallback = img.nextElementSibling;
|
|
267
|
+
if (fallback?.hasAttribute(AUTHORED_FALLBACK_CONTENT_ATTR)) fallback.removeAttribute("hidden");
|
|
268
|
+
}
|
|
354
269
|
/**
|
|
355
|
-
*
|
|
270
|
+
* Restore `.zl-split__placeholder` in any split brand pane whose only content
|
|
271
|
+
* was assets that all failed. Deliberately narrow: a pane is only repaired
|
|
272
|
+
* when it actually holds a broken image, so a pane an author left empty on
|
|
273
|
+
* purpose — or a hero design whose landing copy still renders — is never
|
|
274
|
+
* given decoration it did not ask for.
|
|
356
275
|
*/
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
276
|
+
function repairBrandPanes(root) {
|
|
277
|
+
for (const pane of root.querySelectorAll(BRAND_PANE_SELECTOR)) {
|
|
278
|
+
if (pane.querySelector(`.${PLACEHOLDER_CLASS}`)) continue;
|
|
279
|
+
const children = Array.from(pane.children);
|
|
280
|
+
if (!children.some(isBrokenImage)) continue;
|
|
281
|
+
if (children.some((child) => !isBrokenImage(child))) continue;
|
|
282
|
+
const placeholder = pane.ownerDocument.createElement("div");
|
|
283
|
+
placeholder.className = PLACEHOLDER_CLASS;
|
|
284
|
+
placeholder.setAttribute("aria-hidden", "true");
|
|
285
|
+
pane.append(placeholder);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function isBrokenImage(node) {
|
|
289
|
+
return node.tagName === "IMG" && node.hasAttribute(BROKEN_ATTR);
|
|
290
|
+
}
|
|
291
|
+
//#endregion
|
|
292
|
+
//#region ../api/dist/generated/model/index.mjs
|
|
293
|
+
const CreateFlow201BrandingLayout = {
|
|
294
|
+
centered: "centered",
|
|
295
|
+
split: "split"
|
|
375
296
|
};
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region src/orchestrator/branding-validator.ts
|
|
376
299
|
/**
|
|
377
|
-
*
|
|
300
|
+
* Lightweight paint-time validator for `Branding` payloads.
|
|
301
|
+
*
|
|
302
|
+
* Per `docs/design/branding/schema.md` §Shape invariants:
|
|
303
|
+
*
|
|
304
|
+
* 1. Every referenced URL is https, except logo/hero assets may use canonical
|
|
305
|
+
* loopback HTTP while the component itself renders on a loopback HTTP
|
|
306
|
+
* origin.
|
|
307
|
+
* 2. `layout` is in the documented enum.
|
|
308
|
+
* 3. `liquid_template` shape checks happen elsewhere (security pipeline runs
|
|
309
|
+
* server-side; structural validator is deferred until the full atom set
|
|
310
|
+
* lands).
|
|
311
|
+
*
|
|
312
|
+
* Failures are non-fatal: we collect issues, log a dev-build warning, and
|
|
313
|
+
* return a sanitised payload with the offending fields stripped (set to
|
|
314
|
+
* `undefined`) so the orchestrator falls back to the bundled defaults.
|
|
378
315
|
*/
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
};
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
};
|
|
316
|
+
const VALID_LAYOUTS = new Set(Object.values(CreateFlow201BrandingLayout));
|
|
317
|
+
function validateBranding(input, context = {}) {
|
|
318
|
+
if (!input) return {
|
|
319
|
+
branding: void 0,
|
|
320
|
+
issues: []
|
|
321
|
+
};
|
|
322
|
+
const issues = [];
|
|
323
|
+
const out = { ...input };
|
|
324
|
+
const allowLoopbackHttp = context.renderingOrigin !== void 0 && isCanonicalLoopbackHttpUrl(context.renderingOrigin);
|
|
325
|
+
if (out.layout && !VALID_LAYOUTS.has(out.layout)) {
|
|
326
|
+
issues.push(`Unknown layout "${out.layout}" — falling back to "centered".`);
|
|
327
|
+
out.layout = CreateFlow201BrandingLayout.centered;
|
|
328
|
+
}
|
|
329
|
+
out.logo_url = sanitiseUrl(out.logo_url, "logo_url", issues, { allowed: allowLoopbackHttp });
|
|
330
|
+
out.font_url = sanitiseUrl(out.font_url, "font_url", issues);
|
|
331
|
+
out.hero_url = sanitiseUrl(out.hero_url, "hero_url", issues, { allowed: allowLoopbackHttp });
|
|
332
|
+
if (out.assets) out.assets = {
|
|
333
|
+
logo_dark: sanitiseUrl(out.assets.logo_dark, "assets.logo_dark", issues),
|
|
334
|
+
favicon: sanitiseUrl(out.assets.favicon, "assets.favicon", issues),
|
|
335
|
+
background_image: sanitiseUrl(out.assets.background_image, "assets.background_image", issues)
|
|
336
|
+
};
|
|
337
|
+
return {
|
|
338
|
+
branding: out,
|
|
339
|
+
issues
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function sanitiseUrl(value, field, issues, loopbackHttp) {
|
|
343
|
+
if (value == null || value === "") return;
|
|
344
|
+
try {
|
|
345
|
+
const parsed = new URL(value);
|
|
346
|
+
if (parsed.protocol === "http:" && loopbackHttp?.allowed && isCanonicalLoopbackHttpUrl(value)) return parsed.toString();
|
|
347
|
+
if (parsed.protocol !== "https:") {
|
|
348
|
+
const loopbackHint = loopbackHttp ? "; canonical loopback http is allowed only on loopback development pages" : "";
|
|
349
|
+
issues.push(`${field} must use https (got "${parsed.protocol}"${loopbackHint}) — dropped.`);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
return parsed.toString();
|
|
353
|
+
} catch {
|
|
354
|
+
issues.push(`${field} is not a valid URL — dropped.`);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
//#endregion
|
|
359
|
+
//#region src/orchestrator/exportparts.ts
|
|
393
360
|
/**
|
|
394
|
-
*
|
|
361
|
+
* Forwards atom-internal CSS Shadow Parts through the orchestrator boundary.
|
|
362
|
+
*
|
|
363
|
+
* Atoms stamp `part="..."` on their internals and declare the public set in
|
|
364
|
+
* their manifest (`parts`). Those parts are visible one shadow boundary up —
|
|
365
|
+
* inside the orchestrator's shadow root — but not to the embedding page,
|
|
366
|
+
* because `::part()` pierces a single boundary unless each host in the chain
|
|
367
|
+
* re-exports (`exportparts`). Templates never author the forwarding (the
|
|
368
|
+
* sanitiser strips `exportparts`); the orchestrator stamps it after every
|
|
369
|
+
* render commit so the mapping stays canonical for template-rendered and
|
|
370
|
+
* gate-patched atoms alike.
|
|
371
|
+
*
|
|
372
|
+
* Exported names are mechanical, collision-free, and public contract
|
|
373
|
+
* (override-ladder Tier 2, same stability rules as token names):
|
|
374
|
+
* `<atom>-<part>` with the `zl-` prefix dropped — `zl-field`'s `input` part
|
|
375
|
+
* becomes `zitadel-login::part(field-input)` on the host page. Manifests are
|
|
376
|
+
* the canonical part catalogue; this module derives, never curates.
|
|
395
377
|
*/
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
};
|
|
378
|
+
const exportpartsByTag = new Map(manifestRegistry.filter((manifest) => manifest.parts.length > 0).map((manifest) => {
|
|
379
|
+
const short = manifest.tag.replace(/^zl-/, "");
|
|
380
|
+
const value = manifest.parts.map((part) => `${part}: ${short}-${part}`).join(", ");
|
|
381
|
+
return [manifest.tag, value];
|
|
382
|
+
}));
|
|
410
383
|
/**
|
|
411
|
-
*
|
|
384
|
+
* Stamps `exportparts` onto every known atom in `scope`. Idempotent — call
|
|
385
|
+
* after each render commit: `unsafeHTML` re-parses replace previously
|
|
386
|
+
* stamped nodes, and the mandatory-gates patcher appends atoms after
|
|
387
|
+
* sanitisation.
|
|
412
388
|
*/
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
389
|
+
function stampExportparts(scope) {
|
|
390
|
+
for (const [tag, value] of exportpartsByTag) for (const el of scope.querySelectorAll(tag)) if (el.getAttribute("exportparts") !== value) el.setAttribute("exportparts", value);
|
|
391
|
+
}
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/orchestrator/mandatory-gates.ts
|
|
394
|
+
const MANDATORY_GATES_MARKER = "ZL_MANDATORY_GATES";
|
|
395
|
+
/** Every atom tag that participates as a named form field. */
|
|
396
|
+
const FIELD_ATOM_TAGS = "zl-field, zl-select, zl-checkbox";
|
|
397
|
+
const mandatoryGatesMarkerComment = `<!--${MANDATORY_GATES_MARKER}-->`;
|
|
398
|
+
function patchMandatoryGates(html, step, locale) {
|
|
399
|
+
const template = document.createElement("template");
|
|
400
|
+
template.innerHTML = html;
|
|
401
|
+
const fragment = template.content;
|
|
402
|
+
const additions = collectMissingAtoms(fragment, step, locale);
|
|
403
|
+
const marker = findMarkerComment(fragment);
|
|
404
|
+
if (marker?.parentNode) {
|
|
405
|
+
for (const node of additions) marker.parentNode.insertBefore(node, marker);
|
|
406
|
+
marker.remove();
|
|
407
|
+
} else for (const node of additions) fragment.appendChild(node);
|
|
408
|
+
return template.innerHTML;
|
|
409
|
+
}
|
|
410
|
+
function collectMissingAtoms(fragment, step, locale) {
|
|
411
|
+
const additions = [];
|
|
412
|
+
if (step.fields) for (const field of step.fields) {
|
|
413
|
+
if (!field.required) continue;
|
|
414
|
+
if (hasFieldFor(fragment, field.name)) continue;
|
|
415
|
+
additions.push(buildField(field.name, field.text_key, field.type, locale, field.required));
|
|
416
|
+
}
|
|
417
|
+
if (step.actions && !hasPrimaryButton(fragment)) {
|
|
418
|
+
const primary = step.actions.find((action) => action.primary);
|
|
419
|
+
if (primary) additions.push(buildSubmit(primary.name, primary.text_key, locale));
|
|
420
|
+
}
|
|
421
|
+
return additions;
|
|
422
|
+
}
|
|
423
|
+
function hasPrimaryButton(fragment) {
|
|
424
|
+
return Boolean(fragment.querySelector("zl-button[hierarchy=\"primary\"]"));
|
|
425
|
+
}
|
|
426
|
+
function hasFieldFor(fragment, name) {
|
|
427
|
+
for (const field of fragment.querySelectorAll(FIELD_ATOM_TAGS)) if (field.getAttribute("name") === name) return true;
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
function findMarkerComment(fragment) {
|
|
431
|
+
const walker = (fragment.ownerDocument ?? document).createTreeWalker(fragment, NodeFilter.SHOW_COMMENT);
|
|
432
|
+
let node = walker.nextNode();
|
|
433
|
+
while (node) {
|
|
434
|
+
if (node.nodeValue?.trim() === "ZL_MANDATORY_GATES") return node;
|
|
435
|
+
node = walker.nextNode();
|
|
436
|
+
}
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
function buildField(name, textKey, type, locale, required) {
|
|
440
|
+
const el = document.createElement("zl-field");
|
|
441
|
+
el.setAttribute("name", name);
|
|
442
|
+
el.setAttribute("label", lookup(locale, textKey ?? name));
|
|
443
|
+
el.setAttribute("type", type);
|
|
444
|
+
if (required) el.setAttribute("required", "");
|
|
445
|
+
return el;
|
|
446
|
+
}
|
|
447
|
+
function buildSubmit(name, textKey, locale) {
|
|
448
|
+
const el = document.createElement("zl-button");
|
|
449
|
+
el.setAttribute("hierarchy", "primary");
|
|
450
|
+
el.setAttribute("size", "medium");
|
|
451
|
+
el.setAttribute("type", "submit");
|
|
452
|
+
el.setAttribute("block", "");
|
|
453
|
+
el.setAttribute("action", name);
|
|
454
|
+
el.setAttribute("label", lookup(locale, textKey ?? "submit.continue"));
|
|
455
|
+
return el;
|
|
456
|
+
}
|
|
457
|
+
function lookup(locale, key) {
|
|
458
|
+
return locale[key] ?? key;
|
|
459
|
+
}
|
|
460
|
+
//#endregion
|
|
461
|
+
//#region src/orchestrator/liquid.ts
|
|
427
462
|
/**
|
|
428
|
-
*
|
|
463
|
+
* LiquidJS engine factory for the `<zitadel-login>` orchestrator.
|
|
464
|
+
*
|
|
465
|
+
* Configures the engine per the security pipeline in
|
|
466
|
+
* `docs/design/flowengine/template-security.md` and registers the
|
|
467
|
+
* `| t` filter and `{% mandatory_gates %}` tag from
|
|
468
|
+
* `docs/design/branding/templates.md`.
|
|
469
|
+
*
|
|
470
|
+
* Hard rules:
|
|
471
|
+
*
|
|
472
|
+
* - `{{ }}` output is HTML-escaped (LiquidJS default since v10).
|
|
473
|
+
* - The `| raw` filter is overridden to a no-op string passthrough that still
|
|
474
|
+
* escapes (Layer 1 of the security pipeline).
|
|
475
|
+
* - Partials are loaded from an in-memory map; no filesystem access.
|
|
429
476
|
*/
|
|
430
|
-
const
|
|
431
|
-
return customFetch(getListUserPasskeysUrl(userId, params), {
|
|
432
|
-
...options,
|
|
433
|
-
method: "GET"
|
|
434
|
-
});
|
|
435
|
-
};
|
|
436
|
-
const getSetUserPasswordUrl = (userId, params) => {
|
|
437
|
-
const normalizedParams = new URLSearchParams();
|
|
438
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
439
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
440
|
-
});
|
|
441
|
-
const stringifiedParams = normalizedParams.toString();
|
|
442
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}/password?${stringifiedParams}` : `${getProxyPath()}/users/${userId}/password`;
|
|
443
|
-
};
|
|
477
|
+
const TEMPLATE_NAMES$1 = { default: "default" };
|
|
444
478
|
/**
|
|
445
|
-
*
|
|
479
|
+
* Maps catalog `error.*` keys with no `_<rule>` suffix (so
|
|
480
|
+
* {@link localiseFlowErrorKeys} can't derive the field from the key itself)
|
|
481
|
+
* to their field name — the credential errors with bespoke catalog copy
|
|
482
|
+
* (`error.email_exists`, `error.invalid_credentials`, …). Rule-suffixed keys
|
|
483
|
+
* (`error.<field>_<rule>`) derive their field from the key. Consumed only by
|
|
484
|
+
* {@link localiseFlowErrorKeys}, which stamps the resolved `field` onto the
|
|
485
|
+
* error; the template filters (`fieldError` / `formLevelError`) then route by
|
|
486
|
+
* that `field`, and an error whose field the step omits is downgraded to a
|
|
487
|
+
* fieldless banner message.
|
|
446
488
|
*/
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
489
|
+
const fieldErrorKeys = {
|
|
490
|
+
"error.email_required": "email",
|
|
491
|
+
"error.email_invalid": "email",
|
|
492
|
+
"error.email_exists": "email",
|
|
493
|
+
"error.password_required": "password",
|
|
494
|
+
"error.password_incorrect": "password",
|
|
495
|
+
"error.invalid_credentials": "password"
|
|
496
|
+
};
|
|
497
|
+
function createLiquidEngine(options) {
|
|
498
|
+
const engine = new Liquid({
|
|
499
|
+
templates: {
|
|
500
|
+
[TEMPLATE_NAMES$1.default]: default_default,
|
|
501
|
+
...options.templates ?? {}
|
|
454
502
|
},
|
|
455
|
-
|
|
503
|
+
cache: true,
|
|
504
|
+
jsTruthy: true,
|
|
505
|
+
relativeReference: false,
|
|
506
|
+
strictFilters: false,
|
|
507
|
+
strictVariables: false,
|
|
508
|
+
outputEscape: "escape"
|
|
456
509
|
});
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
510
|
+
engine.registerFilter("raw", (value) => stringify(value));
|
|
511
|
+
const warnedMissingKeys = /* @__PURE__ */ new Set();
|
|
512
|
+
engine.registerFilter("t", function tFilter(key, ...args) {
|
|
513
|
+
const lookupKey = stringify(key);
|
|
514
|
+
let template = options.locale[lookupKey] ?? injectedKeyFallback(options.locale, lookupKey) ?? fieldLabelFallback(lookupKey);
|
|
515
|
+
if (template === void 0) {
|
|
516
|
+
template = lookupKey;
|
|
517
|
+
if (lookupKey !== "" && !warnedMissingKeys.has(lookupKey)) {
|
|
518
|
+
warnedMissingKeys.add(lookupKey);
|
|
519
|
+
console.warn(`[zitadel-login] missing text key "${lookupKey}" — rendering the raw key`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return interpolate(template, args.map(stringify));
|
|
462
523
|
});
|
|
463
|
-
|
|
464
|
-
|
|
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"
|
|
524
|
+
/** Resolves `{text_key}.placeholder` — empty when undefined (not the raw key). */
|
|
525
|
+
engine.registerFilter("fieldPlaceholder", (textKey) => {
|
|
526
|
+
const lookupKey = `${stringify(textKey)}.placeholder`;
|
|
527
|
+
return options.locale[lookupKey] ?? "";
|
|
481
528
|
});
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* @summary Get my user information
|
|
488
|
-
*/
|
|
489
|
-
const getMyUser = async (options) => {
|
|
490
|
-
return customFetch(getGetMyUserUrl(), {
|
|
491
|
-
...options,
|
|
492
|
-
method: "GET"
|
|
529
|
+
/** Resolves `{text_key}.help` — empty when undefined. */
|
|
530
|
+
engine.registerFilter("fieldHelp", (textKey) => {
|
|
531
|
+
const lookupKey = `${stringify(textKey)}.help`;
|
|
532
|
+
return options.locale[lookupKey] ?? "";
|
|
493
533
|
});
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
cookie. The browser sends it automatically on subsequent requests.
|
|
510
|
-
|
|
511
|
-
* @summary Start a new flow
|
|
512
|
-
*/
|
|
513
|
-
const createFlow = async (createFlowBody, options) => {
|
|
514
|
-
return customFetch(getCreateFlowUrl(), {
|
|
515
|
-
...options,
|
|
516
|
-
method: "POST",
|
|
517
|
-
headers: {
|
|
518
|
-
"Content-Type": "application/json",
|
|
519
|
-
...options?.headers
|
|
520
|
-
},
|
|
521
|
-
body: JSON.stringify(createFlowBody)
|
|
534
|
+
/**
|
|
535
|
+
* Builds `<zl-select>` options from a field's closed `validation.enum`. The
|
|
536
|
+
* wire only carries the allowed values (strings), so the label defaults to the
|
|
537
|
+
* value — author the enum with display-ready text. Piped through `| json` into
|
|
538
|
+
* the element's `options` attribute.
|
|
539
|
+
*/
|
|
540
|
+
engine.registerFilter("selectOptions", (values) => {
|
|
541
|
+
if (!Array.isArray(values)) return [];
|
|
542
|
+
return values.map((value) => {
|
|
543
|
+
const v = stringify(value);
|
|
544
|
+
return {
|
|
545
|
+
value: v,
|
|
546
|
+
label: v
|
|
547
|
+
};
|
|
548
|
+
});
|
|
522
549
|
});
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
/**
|
|
528
|
-
* Returns the current capability step without advancing the state machine.
|
|
529
|
-
Useful for page reloads or re-rendering after a network error.
|
|
530
|
-
|
|
531
|
-
* @summary Get current step (re-render)
|
|
532
|
-
*/
|
|
533
|
-
const getFlowStep = async (id, options) => {
|
|
534
|
-
return customFetch(getGetFlowStepUrl(id), {
|
|
535
|
-
...options,
|
|
536
|
-
method: "GET"
|
|
550
|
+
/** Resolves `{text_key}.title` for form-level `<zl-alert heading>`. */
|
|
551
|
+
engine.registerFilter("alertHeading", (textKey) => {
|
|
552
|
+
const lookupKey = `${stringify(textKey)}.title`;
|
|
553
|
+
return options.locale[lookupKey] ?? "";
|
|
537
554
|
});
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* Submits user input for the current step. The server validates,
|
|
544
|
-
processes (e.g., verifies a credential), advances the state machine
|
|
545
|
-
through any invisible steps, and returns the next visible step.
|
|
546
|
-
|
|
547
|
-
The response sets an updated encrypted `HttpOnly` cookie (`_zflow`)
|
|
548
|
-
with the new flow state. The server is stateless — all orchestration
|
|
549
|
-
state is carried in this cookie between requests.
|
|
550
|
-
|
|
551
|
-
**Important:** The `id` in the response may differ from the `id` used in
|
|
552
|
-
the request. This happens when a flow pivots (pushes a new flow onto the
|
|
553
|
-
stack) or when a stacked flow completes (auto-pops to the parent flow).
|
|
554
|
-
Always use the `id` from the latest response for the next request.
|
|
555
|
-
|
|
556
|
-
## Flow completion
|
|
557
|
-
|
|
558
|
-
When `step.type` is `complete`, the flow is terminal. The `step.behavior`
|
|
559
|
-
field tells the frontend what to do:
|
|
560
|
-
|
|
561
|
-
| `behavior` | Action |
|
|
562
|
-
|------------- |------------------------------------------------------------|
|
|
563
|
-
| `redirect` | Navigate to `redirect_uri` (OIDC/SAML auth request done). |
|
|
564
|
-
| `show` | Render the step as a success screen (e.g., registration). |
|
|
565
|
-
|
|
566
|
-
A `complete` step is only returned when the **entire flow stack** is done.
|
|
567
|
-
If a stacked flow (e.g., recovery pivoted from login) finishes, the server
|
|
568
|
-
auto-pops to the parent flow and returns the parent's next step — the
|
|
569
|
-
frontend never sees a `complete` for intermediate flows.
|
|
570
|
-
|
|
571
|
-
* @summary Submit step data and advance
|
|
572
|
-
*/
|
|
573
|
-
const submitFlowStep = async (id, submitFlowStepBody, options) => {
|
|
574
|
-
return customFetch(getSubmitFlowStepUrl(id), {
|
|
575
|
-
...options,
|
|
576
|
-
method: "POST",
|
|
577
|
-
headers: {
|
|
578
|
-
"Content-Type": "application/json",
|
|
579
|
-
...options?.headers
|
|
580
|
-
},
|
|
581
|
-
body: JSON.stringify(submitFlowStepBody)
|
|
555
|
+
/** Resolves `{text_key}.body` for form-level `<zl-alert>` message slot. */
|
|
556
|
+
engine.registerFilter("alertBody", (textKey) => {
|
|
557
|
+
const lookupKey = `${stringify(textKey)}.body`;
|
|
558
|
+
return options.locale[lookupKey] ?? "";
|
|
582
559
|
});
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
560
|
+
/** Localized inline error for `fieldName`, or empty when none applies. */
|
|
561
|
+
engine.registerFilter("fieldError", (fieldName, errors) => {
|
|
562
|
+
const name = stringify(fieldName);
|
|
563
|
+
if (!Array.isArray(errors)) return "";
|
|
564
|
+
for (const item of errors) {
|
|
565
|
+
const err = item;
|
|
566
|
+
if (err.field !== name) continue;
|
|
567
|
+
return err.text_key ? options.locale[err.text_key] ?? err.text_key : err.message ?? "";
|
|
568
|
+
}
|
|
569
|
+
return "";
|
|
570
|
+
});
|
|
571
|
+
/**
|
|
572
|
+
* Automation-hook token for a field name: strips the
|
|
573
|
+
* `x-auth-methods#` credential prefix so testids stay method-named
|
|
574
|
+
* (`zitadel-field-password`), while the `name` attribute keeps the
|
|
575
|
+
* raw wire key. See hookName for the contract.
|
|
576
|
+
*/
|
|
577
|
+
engine.registerFilter("testid", (fieldName) => hookName(stringify(fieldName)));
|
|
578
|
+
/**
|
|
579
|
+
* True when the error should render as `<zl-alert>`, not on a field. A
|
|
580
|
+
* field-scoped error (`field` set by {@link localiseFlowErrorKeys} when the
|
|
581
|
+
* step renders that field) routes inline instead; everything else — engine
|
|
582
|
+
* errors, and orphaned field errors already downgraded to a fieldless
|
|
583
|
+
* message — falls to the banner.
|
|
584
|
+
*/
|
|
585
|
+
engine.registerFilter("formLevelError", (err) => {
|
|
586
|
+
return !err?.field;
|
|
587
|
+
});
|
|
588
|
+
engine.registerTag(MANDATORY_GATES_TAG, {
|
|
589
|
+
parse() {},
|
|
590
|
+
render() {
|
|
591
|
+
return mandatoryGatesMarkerComment;
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
return engine;
|
|
595
|
+
}
|
|
587
596
|
/**
|
|
588
|
-
*
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
Accepts a project_id and challenge_nonce (from POST /bootstrap/challenge). For step-up re-auth,
|
|
594
|
-
also include session_id to add factors to an existing session.
|
|
595
|
-
|
|
596
|
-
* @summary Create a new authentication attempt
|
|
597
|
+
* Fallbacks for text keys the flow engine derives from tenant-chosen step
|
|
598
|
+
* names (`<step>.action.back` for the injected back action — see
|
|
599
|
+
* `internal/domain/flow_state_machine.go` `buildStep`). Step names are open,
|
|
600
|
+
* so no dictionary can enumerate them; a missing step-specific key falls back
|
|
601
|
+
* to its generic entry instead of leaking the raw key into the UI.
|
|
597
602
|
*/
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
},
|
|
606
|
-
body: JSON.stringify(createAuthAttemptBody)
|
|
607
|
-
});
|
|
608
|
-
};
|
|
609
|
-
const getGetAuthAttemptUrl = (attemptId) => {
|
|
610
|
-
return `${getProxyPath()}/auth_attempts/${attemptId}`;
|
|
611
|
-
};
|
|
603
|
+
const INJECTED_KEY_FALLBACKS = [{
|
|
604
|
+
suffix: ".action.back",
|
|
605
|
+
fallback: "action.back"
|
|
606
|
+
}];
|
|
607
|
+
function injectedKeyFallback(locale, key) {
|
|
608
|
+
for (const { suffix, fallback } of INJECTED_KEY_FALLBACKS) if (key.endsWith(suffix)) return locale[fallback];
|
|
609
|
+
}
|
|
612
610
|
/**
|
|
613
|
-
*
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
* @summary Get authentication attempt state
|
|
611
|
+
* Fallback for field-label keys (`<step>.field.<name>` — see
|
|
612
|
+
* `FlowField.TextKey` in `internal/domain/flow_field_resolver.go`). Both
|
|
613
|
+
* halves are tenant-chosen (step names and schema property names), so no
|
|
614
|
+
* catalog can enumerate the keys; a miss renders a humanised property name
|
|
615
|
+
* ("dateOfBirth" → "Date of birth") instead of leaking the raw key into the
|
|
616
|
+
* form. Sub-keys like `.placeholder`/`.help` are excluded — they resolve
|
|
617
|
+
* through their own filters, which stay empty on a miss.
|
|
622
618
|
*/
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
return `${getProxyPath()}/auth_attempts/${attemptId}/challenges`;
|
|
631
|
-
};
|
|
619
|
+
function fieldLabelFallback(key) {
|
|
620
|
+
const index = key.lastIndexOf(".field.");
|
|
621
|
+
if (index === -1) return void 0;
|
|
622
|
+
const field = key.slice(index + 7);
|
|
623
|
+
if (field === "" || field.includes(".")) return void 0;
|
|
624
|
+
return capitaliseFirst(humaniseFieldName(field));
|
|
625
|
+
}
|
|
632
626
|
/**
|
|
633
|
-
*
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
*
|
|
627
|
+
* Rule-suffix fallbacks for the server's field-validation keys
|
|
628
|
+
* (`error.<field>_<rule>` — see `FlowFieldValidationError.TextKey` in
|
|
629
|
+
* `internal/domain/flow_field_resolver.go`). Field names come from the
|
|
630
|
+
* tenant's user schema, so no catalog can enumerate the specific keys;
|
|
631
|
+
* a miss resolves to the rule's generic entry, interpolated with the
|
|
632
|
+
* field's label (`{0}`). The server spells the format rule `_invalid`
|
|
633
|
+
* (the catalog's existing convention, e.g. `error.email_invalid`), so
|
|
634
|
+
* that suffix takes the format wording; `_unknown_field` (a submitted
|
|
635
|
+
* name that is not a step field) takes the catch-all.
|
|
641
636
|
*/
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
*/
|
|
670
|
-
const verifyChallengeProof = async (attemptId, challengeId, verifyChallengeProofBody, options) => {
|
|
671
|
-
return customFetch(getVerifyChallengeProofUrl(attemptId, challengeId), {
|
|
672
|
-
...options,
|
|
673
|
-
method: "POST",
|
|
674
|
-
headers: {
|
|
675
|
-
"Content-Type": "application/json",
|
|
676
|
-
...options?.headers
|
|
677
|
-
},
|
|
678
|
-
body: JSON.stringify(verifyChallengeProofBody)
|
|
679
|
-
});
|
|
680
|
-
};
|
|
681
|
-
const getCreateHandoffUrl = (attemptId) => {
|
|
682
|
-
return `${getProxyPath()}/auth_attempts/${attemptId}/handoff`;
|
|
683
|
-
};
|
|
684
|
-
/**
|
|
685
|
-
* Completes the authentication attempt and mints a `handoff_token`.
|
|
686
|
-
|
|
687
|
-
Call this after all required factors have been verified and the attempt is in `completed` state.
|
|
688
|
-
The handoff token is short-lived (≤60 seconds) and must be exchanged at
|
|
689
|
-
POST /sessions/exchange to receive the final session and session_token.
|
|
690
|
-
|
|
691
|
-
The handoff token is:
|
|
692
|
-
- Single-use (atomic exchange, no retry)
|
|
693
|
-
- Audience-bound (requires matching project key for exchange)
|
|
694
|
-
- Idempotency-safe within a 5-minute window (see conventions)
|
|
695
|
-
|
|
696
|
-
* @summary Complete authentication and create handoff token
|
|
697
|
-
*/
|
|
698
|
-
const createHandoff = async (attemptId, options) => {
|
|
699
|
-
return customFetch(getCreateHandoffUrl(attemptId), {
|
|
700
|
-
...options,
|
|
701
|
-
method: "POST"
|
|
702
|
-
});
|
|
703
|
-
};
|
|
704
|
-
const getCreateProjectUrl = () => {
|
|
705
|
-
return `${getProxyPath()}/projects`;
|
|
706
|
-
};
|
|
707
|
-
/**
|
|
708
|
-
* @summary Create project
|
|
709
|
-
*/
|
|
710
|
-
const createProject = async (createProjectBody, options) => {
|
|
711
|
-
return customFetch(getCreateProjectUrl(), {
|
|
712
|
-
...options,
|
|
713
|
-
method: "POST",
|
|
714
|
-
headers: {
|
|
715
|
-
"Content-Type": "application/json",
|
|
716
|
-
...options?.headers
|
|
717
|
-
},
|
|
718
|
-
body: JSON.stringify(createProjectBody)
|
|
719
|
-
});
|
|
720
|
-
};
|
|
721
|
-
const getQueryProjectsUrl = () => {
|
|
722
|
-
return `${getProxyPath()}/projects/query`;
|
|
723
|
-
};
|
|
637
|
+
const FLOW_ERROR_RULE_FALLBACKS = [
|
|
638
|
+
{
|
|
639
|
+
suffix: "_required",
|
|
640
|
+
generic: "error.field_required"
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
suffix: "_min_length",
|
|
644
|
+
generic: "error.field_min_length"
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
suffix: "_max_length",
|
|
648
|
+
generic: "error.field_max_length"
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
suffix: "_format",
|
|
652
|
+
generic: "error.field_format"
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
suffix: "_invalid",
|
|
656
|
+
generic: "error.field_format"
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
suffix: "_unknown_field",
|
|
660
|
+
generic: "error.field_invalid"
|
|
661
|
+
}
|
|
662
|
+
];
|
|
663
|
+
const FLOW_ERROR_CATCH_ALL_KEY = "error.field_invalid";
|
|
724
664
|
/**
|
|
725
|
-
*
|
|
665
|
+
* Localises a `step.error` payload of `error.*` catalog keys —
|
|
666
|
+
* field-validation violations and general engine failures (e.g.
|
|
667
|
+
* `error.invalid_credentials`, `error.passkey_invalid`) alike.
|
|
668
|
+
* Returns `null` unless EVERY `"; "`-joined segment is an `error.*` key
|
|
669
|
+
* — the caller keeps other payloads (outcome tokens such as
|
|
670
|
+
* `user_not_found`) verbatim.
|
|
671
|
+
*
|
|
672
|
+
* Keys the locale knows pass through as `text_key` entries: the template
|
|
673
|
+
* localises them via `| t`, and `fieldErrorKeys` routes the known ones
|
|
674
|
+
* inline to their field. Unknown keys with a recognised rule suffix are
|
|
675
|
+
* pre-localised here from their generic {@link FLOW_ERROR_RULE_FALLBACKS}
|
|
676
|
+
* entry; unknown keys without one pass through as `text_key` (matching
|
|
677
|
+
* `| t`'s behaviour for non-validation keys such as
|
|
678
|
+
* `error.sign_in_server`, which localises via `.title`/`.body`).
|
|
726
679
|
*/
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
680
|
+
function localiseFlowErrorKeys(raw, ctx) {
|
|
681
|
+
const segments = raw.split("; ");
|
|
682
|
+
if (!segments.every((segment) => segment.startsWith("error."))) return null;
|
|
683
|
+
return segments.map((key) => localiseFlowErrorKey(key, ctx));
|
|
684
|
+
}
|
|
685
|
+
function localiseFlowErrorKey(key, ctx) {
|
|
686
|
+
const inlineField = fieldErrorKeys[key];
|
|
687
|
+
const orphanedInline = inlineField !== void 0 && ctx.fields !== void 0 && !ctx.fields.includes(inlineField);
|
|
688
|
+
if (!orphanedInline && ctx.locale[key] !== void 0) return inlineField !== void 0 ? {
|
|
689
|
+
field: inlineField,
|
|
690
|
+
text_key: key
|
|
691
|
+
} : { text_key: key };
|
|
692
|
+
for (const { suffix, generic } of FLOW_ERROR_RULE_FALLBACKS) {
|
|
693
|
+
if (!key.endsWith(suffix)) continue;
|
|
694
|
+
const field = key.slice(6, key.length - suffix.length);
|
|
695
|
+
if (field === "") break;
|
|
696
|
+
const message = capitaliseFirst(interpolate(ctx.locale[generic] ?? ctx.locale[FLOW_ERROR_CATCH_ALL_KEY] ?? "Please check {0}.", [fieldLabel(ctx, field)]));
|
|
697
|
+
return ctx.fields !== void 0 && ctx.fields.includes(field) ? {
|
|
698
|
+
field,
|
|
699
|
+
message
|
|
700
|
+
} : { message };
|
|
701
|
+
}
|
|
702
|
+
if (orphanedInline && ctx.locale[key] !== void 0) return { message: ctx.locale[key] };
|
|
703
|
+
return { text_key: key };
|
|
704
|
+
}
|
|
705
|
+
/** `<step>.field.<name>` from the locale, else a humanised field name. */
|
|
706
|
+
function fieldLabel(ctx, field) {
|
|
707
|
+
return ctx.locale[`${ctx.stepName}.field.${field}`] ?? humaniseFieldName(field);
|
|
708
|
+
}
|
|
741
709
|
/**
|
|
742
|
-
*
|
|
743
|
-
|
|
744
|
-
* @summary Get project
|
|
710
|
+
* `x-auth-methods#password` → "password", `givenName` → "given name",
|
|
711
|
+
* `date_of_birth` → "date of birth".
|
|
745
712
|
*/
|
|
746
|
-
|
|
747
|
-
return
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
713
|
+
function humaniseFieldName(field) {
|
|
714
|
+
return (field.includes("#") ? field.split("#").pop() : field).replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_.-]+/g, " ").trim().toLowerCase();
|
|
715
|
+
}
|
|
716
|
+
/** Messages may open with a lowercase field label; sentence-case them. */
|
|
717
|
+
function capitaliseFirst(text) {
|
|
718
|
+
return text.length > 0 ? text[0]?.toUpperCase() + text.slice(1) : text;
|
|
719
|
+
}
|
|
720
|
+
function stringify(value) {
|
|
721
|
+
if (value == null) return "";
|
|
722
|
+
if (typeof value === "string") return value;
|
|
723
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
724
|
+
try {
|
|
725
|
+
return JSON.stringify(value);
|
|
726
|
+
} catch {
|
|
727
|
+
return String(value);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
755
730
|
/**
|
|
756
|
-
*
|
|
757
|
-
|
|
758
|
-
*
|
|
731
|
+
* Positional `{n}` substitution shared by the `| t` filter and the
|
|
732
|
+
* pre-localising fallback in {@link localiseFlowErrorKeys}, so both
|
|
733
|
+
* produce byte-identical output for the same template and args.
|
|
759
734
|
*/
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
headers: {
|
|
765
|
-
"Content-Type": "application/json",
|
|
766
|
-
...options?.headers
|
|
767
|
-
},
|
|
768
|
-
body: JSON.stringify(patchProjectBody)
|
|
735
|
+
function interpolate(template, args) {
|
|
736
|
+
if (args.length === 0) return template;
|
|
737
|
+
return template.replace(/\{(\d+)\}/g, (match, index) => {
|
|
738
|
+
return args[Number(index)] ?? match;
|
|
769
739
|
});
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
};
|
|
740
|
+
}
|
|
741
|
+
//#endregion
|
|
742
|
+
//#region src/orchestrator/locales/en.ts
|
|
774
743
|
/**
|
|
775
|
-
*
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
*
|
|
744
|
+
* Minimal English locale dictionary.
|
|
745
|
+
*
|
|
746
|
+
* The `| t` filter looks up `text_key` strings here. Missing keys fall through
|
|
747
|
+
* to the raw key (matches the spec in
|
|
748
|
+
* `docs/design/flowengine/flow-engine-guide.md`).
|
|
749
|
+
*
|
|
750
|
+
* Text keys are auto-generated by the server from the flow definition:
|
|
751
|
+
* - Step titles: `<step>.title`
|
|
752
|
+
* - Step descriptions: `<step>.description`
|
|
753
|
+
* - Field labels: `<step>.field.<field>`
|
|
754
|
+
* - Action labels: `<step>.action.<action>`
|
|
755
|
+
*
|
|
756
|
+
* Copy aligned to Figma screens file `xkvBjkOJ8ENuHdTGZHXezK` (May 2026).
|
|
757
|
+
* MVP only — multi-locale support is deferred.
|
|
782
758
|
*/
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
759
|
+
const en = {
|
|
760
|
+
"identifier.title": "Sign in",
|
|
761
|
+
"identifier.description": "Enter your email to continue",
|
|
762
|
+
"identifier.field.email": "Email",
|
|
763
|
+
"identifier.field.email.placeholder": "you@example.com",
|
|
764
|
+
"identifier.field.password": "Password",
|
|
765
|
+
"identifier.action.submit": "Sign in",
|
|
766
|
+
"identifier.action.continue": "Continue",
|
|
767
|
+
"identifier.action.passkey": "Sign in with a passkey",
|
|
768
|
+
"identifier.action.register.lead": "Don't have an account? ",
|
|
769
|
+
"identifier.action.register.link": "Sign up",
|
|
770
|
+
"authenticate.title": "Sign in",
|
|
771
|
+
"authenticate.description": "Enter your password",
|
|
772
|
+
"authenticate.field.password": "Password",
|
|
773
|
+
"authenticate.action.submit": "Sign in",
|
|
774
|
+
"choose-register.title": "Create your account",
|
|
775
|
+
"choose-register.description": "Choose how you'd like to sign up",
|
|
776
|
+
"choose-register.action.register-with-password": "Sign up with password",
|
|
777
|
+
"choose-register.action.register-with-passkey": "Sign up with passkey",
|
|
778
|
+
"collect-credentials.title": "Create your account",
|
|
779
|
+
"collect-credentials.description": "Set up your email and password",
|
|
780
|
+
"collect-credentials.field.email": "Email",
|
|
781
|
+
"collect-credentials.field.email.placeholder": "you@example.com",
|
|
782
|
+
"collect-credentials.field.password": "Password",
|
|
783
|
+
"collect-credentials.action.submit": "Sign up",
|
|
784
|
+
"register-password.title": "Create your password",
|
|
785
|
+
"register-password.description": "Choose a secure password for your account",
|
|
786
|
+
"register-password.field.password": "Password",
|
|
787
|
+
"register-password.action.submit": "Sign up",
|
|
788
|
+
"register-password.action.back": "Back",
|
|
789
|
+
"passkey-upsell.title": "Sign in faster next time",
|
|
790
|
+
"passkey-upsell.description": "No password needed ever again.",
|
|
791
|
+
"passkey-upsell.description.line1": "No password needed ever again.",
|
|
792
|
+
"passkey-upsell.description.line2": "Sign in with Face ID, Touch ID, or PIN.",
|
|
793
|
+
"passkey-upsell.action.passkey_register": "Set up passkey",
|
|
794
|
+
"passkey-upsell.action.skip": "Skip for now",
|
|
795
|
+
"passkey-upsell.action.setup": "Set up passkey",
|
|
796
|
+
"collect-passkey-email.title": "Create your account",
|
|
797
|
+
"collect-passkey-email.description": "Enter your email to set up a passkey",
|
|
798
|
+
"collect-passkey-email.field.email": "Email",
|
|
799
|
+
"collect-passkey-email.field.email.placeholder": "you@example.com",
|
|
800
|
+
"collect-passkey-email.action.submit": "Continue",
|
|
801
|
+
"passkey-enroll.title": "Set up your passkey",
|
|
802
|
+
"passkey-enroll.description": "Use Face ID, Touch ID, or your device PIN to create a passkey.",
|
|
803
|
+
"passkey-enroll.action.passkey_register": "Set up passkey",
|
|
804
|
+
"done.title": "You're signed in as",
|
|
805
|
+
"done.description": "",
|
|
806
|
+
"password.title": "Sign in",
|
|
807
|
+
"password.description": "Enter your password",
|
|
808
|
+
"password.field.password": "Password",
|
|
809
|
+
"password.action.signin": "Sign in",
|
|
810
|
+
"password.action.passkey": "Sign in with a passkey",
|
|
811
|
+
"password.action.back": "Back",
|
|
812
|
+
"password.action.register.lead": "Don't have an account? ",
|
|
813
|
+
"password.action.register.link": "Sign up",
|
|
814
|
+
"register.title": "Create your account",
|
|
815
|
+
"register.description": "",
|
|
816
|
+
"register.field.email": "Email",
|
|
817
|
+
"register.field.email.placeholder": "you@example.com",
|
|
818
|
+
"register.field.password": "Password",
|
|
819
|
+
"register.field.givenName": "Given name",
|
|
820
|
+
"register.field.familyName": "Family name",
|
|
821
|
+
"register.field.companyName": "Company name",
|
|
822
|
+
"register.field.dateOfBirth": "Date of birth",
|
|
823
|
+
"register.field.country": "Country",
|
|
824
|
+
"register.field.country.placeholder": "Select your country",
|
|
825
|
+
"register.field.terms": "I agree to the terms of service",
|
|
826
|
+
"register.action.password": "Continue with password",
|
|
827
|
+
"register.action.passkey": "Continue with a passkey",
|
|
828
|
+
"register.action.submit": "Sign up",
|
|
829
|
+
"register.action.back": "Back",
|
|
830
|
+
"register.action.sign_in.lead": "Already have an account? ",
|
|
831
|
+
"register.action.sign_in.link": "Sign in",
|
|
832
|
+
"complete.title": "You're signed in as",
|
|
833
|
+
"signed-in.continue": "Continue",
|
|
834
|
+
"signed-in.logout": "Sign out",
|
|
835
|
+
"passkey-login.title": "Sign in with your passkey",
|
|
836
|
+
"passkey-first.title": "Sign in",
|
|
837
|
+
"passkey-first.description": "Use your passkey to continue",
|
|
838
|
+
"passkey-first.action.passkey": "Continue with passkey",
|
|
839
|
+
"passkey-first.action.email_fallback": "Use email instead",
|
|
840
|
+
"recover.title": "Check your email",
|
|
841
|
+
"recover.description": "We sent a password reset link to your email address.",
|
|
842
|
+
"recover.action.back": "Back to sign in",
|
|
843
|
+
"submit.continue": "Continue",
|
|
844
|
+
"submit.signin": "Sign in",
|
|
845
|
+
"action.forgot_password": "Forgot password?",
|
|
846
|
+
"action.cancel": "Cancel",
|
|
847
|
+
"action.back": "Back",
|
|
848
|
+
"sso.redirect.title": "Redirecting to your provider…",
|
|
849
|
+
"passkey.pending.status": "Waiting for your passkey…",
|
|
850
|
+
"error.passkey_cancelled": "The passkey prompt was closed before completing.",
|
|
851
|
+
"error.passkey_timeout": "The passkey request timed out. Please try again.",
|
|
852
|
+
"error.passkey_not_registered": "This passkey is not registered. Please sign in with your email and password.",
|
|
853
|
+
"error.passkey_setup_failed": "Passkey registration did not complete. Please try again.",
|
|
854
|
+
"error.passkey_unsupported": "This device does not support passkeys",
|
|
855
|
+
"error.passkey_failed": "Something went wrong. Please try again.",
|
|
856
|
+
"error.passkey_invalid": "This passkey could not be verified. Please try again.",
|
|
857
|
+
"error.passkey_registration_invalid": "The new passkey could not be verified. Please try registering it again.",
|
|
858
|
+
"error.email_required": "Please enter an email address",
|
|
859
|
+
"error.email_invalid": "Please enter a valid email",
|
|
860
|
+
"error.password_required": "Please enter a password",
|
|
861
|
+
"error.password_incorrect": "Wrong email or password.",
|
|
862
|
+
"error.email_exists": "An account with this email already exists.",
|
|
863
|
+
/** Figma sign-in error `6602:180268` — inline on password field. */
|
|
864
|
+
"error.invalid_credentials": "Wrong email or password.",
|
|
865
|
+
"error.required": "This field is required.",
|
|
866
|
+
"error.field_required": "{0} is required.",
|
|
867
|
+
"error.field_format": "Please enter a valid {0}.",
|
|
868
|
+
"error.field_min_length": "{0} is too short.",
|
|
869
|
+
"error.field_max_length": "{0} is too long.",
|
|
870
|
+
"error.field_invalid": "Please check {0}.",
|
|
871
|
+
"error.sign_in_server.title": "We couldn't complete your sign in.",
|
|
872
|
+
"error.sign_in_server.body": "Please try again in a few minutes"
|
|
873
|
+
};
|
|
874
|
+
//#endregion
|
|
875
|
+
//#region src/orchestrator/locales/de.ts
|
|
876
|
+
const de = {
|
|
877
|
+
"identifier.title": "Anmelden",
|
|
878
|
+
"identifier.description": "Gib deine E-Mail-Adresse ein, um fortzufahren",
|
|
879
|
+
"identifier.field.email": "E-Mail",
|
|
880
|
+
"identifier.field.email.placeholder": "du@beispiel.de",
|
|
881
|
+
"identifier.field.password": "Passwort",
|
|
882
|
+
"identifier.action.submit": "Anmelden",
|
|
883
|
+
"identifier.action.continue": "Weiter",
|
|
884
|
+
"identifier.action.passkey": "Mit Passkey anmelden",
|
|
885
|
+
"identifier.action.register.lead": "Noch kein Konto? ",
|
|
886
|
+
"identifier.action.register.link": "Registrieren",
|
|
887
|
+
"authenticate.title": "Anmelden",
|
|
888
|
+
"authenticate.description": "Gib dein Passwort ein",
|
|
889
|
+
"authenticate.field.password": "Passwort",
|
|
890
|
+
"authenticate.action.submit": "Anmelden",
|
|
891
|
+
"choose-register.title": "Konto erstellen",
|
|
892
|
+
"choose-register.description": "Wähle, wie du dich registrieren möchtest",
|
|
893
|
+
"choose-register.action.register-with-password": "Mit Passwort registrieren",
|
|
894
|
+
"choose-register.action.register-with-passkey": "Mit Passkey registrieren",
|
|
895
|
+
"collect-credentials.title": "Konto erstellen",
|
|
896
|
+
"collect-credentials.description": "Richte E-Mail und Passwort ein",
|
|
897
|
+
"collect-credentials.field.email": "E-Mail",
|
|
898
|
+
"collect-credentials.field.email.placeholder": "du@beispiel.de",
|
|
899
|
+
"collect-credentials.field.password": "Passwort",
|
|
900
|
+
"collect-credentials.action.submit": "Registrieren",
|
|
901
|
+
"register-password.title": "Passwort erstellen",
|
|
902
|
+
"register-password.description": "Wähle ein sicheres Passwort für dein Konto",
|
|
903
|
+
"register-password.field.password": "Passwort",
|
|
904
|
+
"register-password.action.submit": "Registrieren",
|
|
905
|
+
"register-password.action.back": "Zurück",
|
|
906
|
+
"passkey-upsell.title": "Nächstes Mal schneller anmelden",
|
|
907
|
+
"passkey-upsell.description": "Kein Passwort mehr nötig.",
|
|
908
|
+
"passkey-upsell.description.line1": "Kein Passwort mehr nötig.",
|
|
909
|
+
"passkey-upsell.description.line2": "Melde dich mit Face ID, Touch ID oder PIN an.",
|
|
910
|
+
"passkey-upsell.action.passkey_register": "Passkey einrichten",
|
|
911
|
+
"passkey-upsell.action.skip": "Vorerst überspringen",
|
|
912
|
+
"passkey-upsell.action.setup": "Passkey einrichten",
|
|
913
|
+
"collect-passkey-email.title": "Konto erstellen",
|
|
914
|
+
"collect-passkey-email.description": "Gib deine E-Mail-Adresse ein, um einen Passkey einzurichten",
|
|
915
|
+
"collect-passkey-email.field.email": "E-Mail",
|
|
916
|
+
"collect-passkey-email.field.email.placeholder": "du@beispiel.de",
|
|
917
|
+
"collect-passkey-email.action.submit": "Weiter",
|
|
918
|
+
"passkey-enroll.title": "Passkey einrichten",
|
|
919
|
+
"passkey-enroll.description": "Verwende Face ID, Touch ID oder die Geräte-PIN, um einen Passkey zu erstellen.",
|
|
920
|
+
"passkey-enroll.action.passkey_register": "Passkey einrichten",
|
|
921
|
+
"done.title": "Du bist angemeldet als",
|
|
922
|
+
"done.description": "",
|
|
923
|
+
"password.title": "Anmelden",
|
|
924
|
+
"password.description": "Gib dein Passwort ein",
|
|
925
|
+
"password.field.password": "Passwort",
|
|
926
|
+
"password.action.signin": "Anmelden",
|
|
927
|
+
"password.action.passkey": "Mit Passkey anmelden",
|
|
928
|
+
"password.action.back": "Zurück",
|
|
929
|
+
"password.action.register.lead": "Noch kein Konto? ",
|
|
930
|
+
"password.action.register.link": "Registrieren",
|
|
931
|
+
"register.title": "Konto erstellen",
|
|
932
|
+
"register.description": "",
|
|
933
|
+
"register.field.email": "E-Mail",
|
|
934
|
+
"register.field.email.placeholder": "du@beispiel.de",
|
|
935
|
+
"register.field.password": "Passwort",
|
|
936
|
+
"register.field.givenName": "Vorname",
|
|
937
|
+
"register.field.familyName": "Nachname",
|
|
938
|
+
"register.field.companyName": "Firmenname",
|
|
939
|
+
"register.field.dateOfBirth": "Geburtsdatum",
|
|
940
|
+
"register.field.country": "Land",
|
|
941
|
+
"register.field.country.placeholder": "Land auswählen",
|
|
942
|
+
"register.field.terms": "Ich stimme den Nutzungsbedingungen zu",
|
|
943
|
+
"register.action.password": "Mit Passwort fortfahren",
|
|
944
|
+
"register.action.passkey": "Weiter mit Passkey",
|
|
945
|
+
"register.action.submit": "Registrieren",
|
|
946
|
+
"register.action.back": "Zurück",
|
|
947
|
+
"register.action.sign_in.lead": "Bereits ein Konto? ",
|
|
948
|
+
"register.action.sign_in.link": "Anmelden",
|
|
949
|
+
"complete.title": "Du bist angemeldet als",
|
|
950
|
+
"signed-in.continue": "Weiter",
|
|
951
|
+
"signed-in.logout": "Abmelden",
|
|
952
|
+
"passkey-login.title": "Mit Passkey anmelden",
|
|
953
|
+
"passkey-first.title": "Anmelden",
|
|
954
|
+
"passkey-first.description": "Verwende deinen Passkey, um fortzufahren",
|
|
955
|
+
"passkey-first.action.passkey": "Weiter mit Passkey",
|
|
956
|
+
"passkey-first.action.email_fallback": "Stattdessen E-Mail verwenden",
|
|
957
|
+
"recover.title": "E-Mail prüfen",
|
|
958
|
+
"recover.description": "Wir haben einen Link zum Zurücksetzen des Passworts an deine E-Mail-Adresse gesendet.",
|
|
959
|
+
"recover.action.back": "Zurück zur Anmeldung",
|
|
960
|
+
"submit.continue": "Weiter",
|
|
961
|
+
"submit.signin": "Anmelden",
|
|
962
|
+
"action.forgot_password": "Passwort vergessen?",
|
|
963
|
+
"action.cancel": "Abbrechen",
|
|
964
|
+
"action.back": "Zurück",
|
|
965
|
+
"sso.redirect.title": "Weiterleitung zum Anbieter…",
|
|
966
|
+
"passkey.pending.status": "Warten auf deinen Passkey…",
|
|
967
|
+
"error.passkey_cancelled": "Die Passkey-Abfrage wurde vorzeitig geschlossen.",
|
|
968
|
+
"error.passkey_timeout": "Die Passkey-Anfrage hat zu lange gedauert. Bitte versuche es erneut.",
|
|
969
|
+
"error.passkey_not_registered": "Dieser Passkey ist nicht registriert. Bitte melde dich mit E-Mail und Passwort an.",
|
|
970
|
+
"error.passkey_setup_failed": "Passkey-Registrierung wurde nicht abgeschlossen. Bitte versuche es erneut.",
|
|
971
|
+
"error.passkey_unsupported": "Dieses Gerät unterstützt keine Passkeys",
|
|
972
|
+
"error.passkey_failed": "Etwas ist schiefgelaufen. Bitte versuche es erneut.",
|
|
973
|
+
"error.passkey_invalid": "Dieser Passkey konnte nicht bestätigt werden. Bitte versuche es erneut.",
|
|
974
|
+
"error.passkey_registration_invalid": "Der neue Passkey konnte nicht bestätigt werden. Bitte registriere ihn erneut.",
|
|
975
|
+
"error.email_required": "Bitte gib eine E-Mail-Adresse ein",
|
|
976
|
+
"error.email_invalid": "Bitte gib eine gültige E-Mail-Adresse ein",
|
|
977
|
+
"error.password_required": "Bitte gib ein Passwort ein",
|
|
978
|
+
"error.password_incorrect": "Falsche E-Mail oder falsches Passwort.",
|
|
979
|
+
"error.email_exists": "Ein Konto mit dieser E-Mail-Adresse existiert bereits.",
|
|
980
|
+
"error.invalid_credentials": "Falsche E-Mail oder falsches Passwort.",
|
|
981
|
+
"error.required": "Dieses Feld ist erforderlich.",
|
|
982
|
+
"error.field_required": "{0} ist erforderlich.",
|
|
983
|
+
"error.field_format": "Bitte gib einen gültigen Wert für {0} ein.",
|
|
984
|
+
"error.field_min_length": "{0} ist zu kurz.",
|
|
985
|
+
"error.field_max_length": "{0} ist zu lang.",
|
|
986
|
+
"error.field_invalid": "Bitte überprüfe {0}.",
|
|
987
|
+
"error.sign_in_server.title": "Anmeldung konnte nicht abgeschlossen werden.",
|
|
988
|
+
"error.sign_in_server.body": "Bitte versuche es in einigen Minuten erneut"
|
|
989
|
+
};
|
|
990
|
+
//#endregion
|
|
991
|
+
//#region src/orchestrator/locales/it.ts
|
|
992
|
+
const it = {
|
|
993
|
+
"identifier.title": "Accedi",
|
|
994
|
+
"identifier.description": "Inserisci la tua e-mail per continuare",
|
|
995
|
+
"identifier.field.email": "E-mail",
|
|
996
|
+
"identifier.field.email.placeholder": "tu@esempio.com",
|
|
997
|
+
"identifier.field.password": "Password",
|
|
998
|
+
"identifier.action.submit": "Accedi",
|
|
999
|
+
"identifier.action.continue": "Continua",
|
|
1000
|
+
"identifier.action.passkey": "Accedi con passkey",
|
|
1001
|
+
"identifier.action.register.lead": "Non hai un account? ",
|
|
1002
|
+
"identifier.action.register.link": "Registrati",
|
|
1003
|
+
"authenticate.title": "Accedi",
|
|
1004
|
+
"authenticate.description": "Inserisci la tua password",
|
|
1005
|
+
"authenticate.field.password": "Password",
|
|
1006
|
+
"authenticate.action.submit": "Accedi",
|
|
1007
|
+
"choose-register.title": "Crea il tuo account",
|
|
1008
|
+
"choose-register.description": "Scegli come registrarti",
|
|
1009
|
+
"choose-register.action.register-with-password": "Registrati con password",
|
|
1010
|
+
"choose-register.action.register-with-passkey": "Registrati con passkey",
|
|
1011
|
+
"collect-credentials.title": "Crea il tuo account",
|
|
1012
|
+
"collect-credentials.description": "Configura e-mail e password",
|
|
1013
|
+
"collect-credentials.field.email": "E-mail",
|
|
1014
|
+
"collect-credentials.field.email.placeholder": "tu@esempio.com",
|
|
1015
|
+
"collect-credentials.field.password": "Password",
|
|
1016
|
+
"collect-credentials.action.submit": "Registrati",
|
|
1017
|
+
"register-password.title": "Crea la tua password",
|
|
1018
|
+
"register-password.description": "Scegli una password sicura per il tuo account",
|
|
1019
|
+
"register-password.field.password": "Password",
|
|
1020
|
+
"register-password.action.submit": "Registrati",
|
|
1021
|
+
"register-password.action.back": "Indietro",
|
|
1022
|
+
"passkey-upsell.title": "Accedi più velocemente la prossima volta",
|
|
1023
|
+
"passkey-upsell.description": "Nessuna password necessaria.",
|
|
1024
|
+
"passkey-upsell.description.line1": "Nessuna password necessaria.",
|
|
1025
|
+
"passkey-upsell.description.line2": "Accedi con Face ID, Touch ID o PIN.",
|
|
1026
|
+
"passkey-upsell.action.passkey_register": "Configura passkey",
|
|
1027
|
+
"passkey-upsell.action.skip": "Salta per ora",
|
|
1028
|
+
"passkey-upsell.action.setup": "Configura passkey",
|
|
1029
|
+
"collect-passkey-email.title": "Crea il tuo account",
|
|
1030
|
+
"collect-passkey-email.description": "Inserisci la tua e-mail per configurare una passkey",
|
|
1031
|
+
"collect-passkey-email.field.email": "E-mail",
|
|
1032
|
+
"collect-passkey-email.field.email.placeholder": "tu@esempio.com",
|
|
1033
|
+
"collect-passkey-email.action.submit": "Continua",
|
|
1034
|
+
"passkey-enroll.title": "Configura la tua passkey",
|
|
1035
|
+
"passkey-enroll.description": "Usa Face ID, Touch ID o il PIN del dispositivo per creare una passkey.",
|
|
1036
|
+
"passkey-enroll.action.passkey_register": "Configura passkey",
|
|
1037
|
+
"done.title": "Sei connesso come",
|
|
1038
|
+
"done.description": "",
|
|
1039
|
+
"password.title": "Accedi",
|
|
1040
|
+
"password.description": "Inserisci la tua password",
|
|
1041
|
+
"password.field.password": "Password",
|
|
1042
|
+
"password.action.signin": "Accedi",
|
|
1043
|
+
"password.action.passkey": "Accedi con passkey",
|
|
1044
|
+
"password.action.back": "Indietro",
|
|
1045
|
+
"password.action.register.lead": "Non hai un account? ",
|
|
1046
|
+
"password.action.register.link": "Registrati",
|
|
1047
|
+
"register.title": "Crea il tuo account",
|
|
1048
|
+
"register.description": "",
|
|
1049
|
+
"register.field.email": "E-mail",
|
|
1050
|
+
"register.field.email.placeholder": "tu@esempio.com",
|
|
1051
|
+
"register.field.password": "Password",
|
|
1052
|
+
"register.field.givenName": "Nome",
|
|
1053
|
+
"register.field.familyName": "Cognome",
|
|
1054
|
+
"register.field.companyName": "Nome azienda",
|
|
1055
|
+
"register.field.dateOfBirth": "Data di nascita",
|
|
1056
|
+
"register.field.country": "Paese",
|
|
1057
|
+
"register.field.country.placeholder": "Seleziona il tuo paese",
|
|
1058
|
+
"register.field.terms": "Accetto i termini di servizio",
|
|
1059
|
+
"register.action.password": "Continua con password",
|
|
1060
|
+
"register.action.passkey": "Continua con passkey",
|
|
1061
|
+
"register.action.submit": "Registrati",
|
|
1062
|
+
"register.action.back": "Indietro",
|
|
1063
|
+
"register.action.sign_in.lead": "Hai già un account? ",
|
|
1064
|
+
"register.action.sign_in.link": "Accedi",
|
|
1065
|
+
"complete.title": "Sei connesso come",
|
|
1066
|
+
"signed-in.continue": "Continua",
|
|
1067
|
+
"signed-in.logout": "Esci",
|
|
1068
|
+
"passkey-login.title": "Accedi con la tua passkey",
|
|
1069
|
+
"passkey-first.title": "Accedi",
|
|
1070
|
+
"passkey-first.description": "Usa la tua passkey per continuare",
|
|
1071
|
+
"passkey-first.action.passkey": "Continua con passkey",
|
|
1072
|
+
"passkey-first.action.email_fallback": "Usa invece l'email",
|
|
1073
|
+
"recover.title": "Controlla la tua e-mail",
|
|
1074
|
+
"recover.description": "Abbiamo inviato un link per reimpostare la password al tuo indirizzo e-mail.",
|
|
1075
|
+
"recover.action.back": "Torna all'accesso",
|
|
1076
|
+
"submit.continue": "Continua",
|
|
1077
|
+
"submit.signin": "Accedi",
|
|
1078
|
+
"action.forgot_password": "Password dimenticata?",
|
|
1079
|
+
"action.cancel": "Annulla",
|
|
1080
|
+
"action.back": "Indietro",
|
|
1081
|
+
"sso.redirect.title": "Reindirizzamento al provider…",
|
|
1082
|
+
"passkey.pending.status": "In attesa della tua passkey…",
|
|
1083
|
+
"error.passkey_cancelled": "La richiesta della passkey è stata chiusa prima del completamento.",
|
|
1084
|
+
"error.passkey_timeout": "La richiesta della passkey è scaduta. Riprova.",
|
|
1085
|
+
"error.passkey_not_registered": "Questa passkey non è registrata. Accedi con e-mail e password.",
|
|
1086
|
+
"error.passkey_setup_failed": "La registrazione della passkey non è stata completata. Riprova.",
|
|
1087
|
+
"error.passkey_unsupported": "Questo dispositivo non supporta le passkey",
|
|
1088
|
+
"error.passkey_failed": "Qualcosa è andato storto. Riprova.",
|
|
1089
|
+
"error.passkey_invalid": "Non è stato possibile verificare questa passkey. Riprova.",
|
|
1090
|
+
"error.passkey_registration_invalid": "Non è stato possibile verificare la nuova passkey. Riprova a registrarla.",
|
|
1091
|
+
"error.email_required": "Inserisci un indirizzo e-mail",
|
|
1092
|
+
"error.email_invalid": "Inserisci un indirizzo e-mail valido",
|
|
1093
|
+
"error.password_required": "Inserisci una password",
|
|
1094
|
+
"error.password_incorrect": "E-mail o password errata.",
|
|
1095
|
+
"error.email_exists": "Esiste già un account con questo indirizzo e-mail.",
|
|
1096
|
+
"error.invalid_credentials": "E-mail o password errata.",
|
|
1097
|
+
"error.required": "Questo campo è obbligatorio.",
|
|
1098
|
+
"error.field_required": "{0} è obbligatorio.",
|
|
1099
|
+
"error.field_format": "Inserisci un valore valido per {0}.",
|
|
1100
|
+
"error.field_min_length": "{0} è troppo corto.",
|
|
1101
|
+
"error.field_max_length": "{0} è troppo lungo.",
|
|
1102
|
+
"error.field_invalid": "Controlla {0}.",
|
|
1103
|
+
"error.sign_in_server.title": "Non è stato possibile completare l'accesso.",
|
|
1104
|
+
"error.sign_in_server.body": "Riprova tra qualche minuto"
|
|
1105
|
+
};
|
|
1106
|
+
//#endregion
|
|
1107
|
+
//#region src/orchestrator/locales/business.ts
|
|
1108
|
+
const businessLocales = {
|
|
1109
|
+
en: {
|
|
1110
|
+
"identifier.field.email": "Work email",
|
|
1111
|
+
"identifier.field.email.placeholder": "you@company.com",
|
|
1112
|
+
"collect-credentials.field.email": "Work email",
|
|
1113
|
+
"collect-credentials.field.email.placeholder": "you@company.com",
|
|
1114
|
+
"collect-passkey-email.field.email": "Work email",
|
|
1115
|
+
"collect-passkey-email.field.email.placeholder": "you@company.com",
|
|
1116
|
+
"register.field.email": "Work email",
|
|
1117
|
+
"register.field.email.placeholder": "you@company.com"
|
|
1118
|
+
},
|
|
1119
|
+
de: {
|
|
1120
|
+
"identifier.field.email": "Geschäftliche E-Mail",
|
|
1121
|
+
"identifier.field.email.placeholder": "du@unternehmen.com",
|
|
1122
|
+
"collect-credentials.field.email": "Geschäftliche E-Mail",
|
|
1123
|
+
"collect-credentials.field.email.placeholder": "du@unternehmen.com",
|
|
1124
|
+
"collect-passkey-email.field.email": "Geschäftliche E-Mail",
|
|
1125
|
+
"collect-passkey-email.field.email.placeholder": "du@unternehmen.com",
|
|
1126
|
+
"register.field.email": "Geschäftliche E-Mail",
|
|
1127
|
+
"register.field.email.placeholder": "du@unternehmen.com"
|
|
1128
|
+
},
|
|
1129
|
+
it: {
|
|
1130
|
+
"identifier.field.email": "E-mail aziendale",
|
|
1131
|
+
"identifier.field.email.placeholder": "tu@azienda.com",
|
|
1132
|
+
"collect-credentials.field.email": "E-mail aziendale",
|
|
1133
|
+
"collect-credentials.field.email.placeholder": "tu@azienda.com",
|
|
1134
|
+
"collect-passkey-email.field.email": "E-mail aziendale",
|
|
1135
|
+
"collect-passkey-email.field.email.placeholder": "tu@azienda.com",
|
|
1136
|
+
"register.field.email": "E-mail aziendale",
|
|
1137
|
+
"register.field.email.placeholder": "tu@azienda.com"
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
//#endregion
|
|
1141
|
+
//#region src/orchestrator/locales/index.ts
|
|
1142
|
+
const builtinLocales = {
|
|
1143
|
+
en,
|
|
1144
|
+
de,
|
|
1145
|
+
it
|
|
1146
|
+
};
|
|
1147
|
+
//#endregion
|
|
1148
|
+
//#region ../api/dist/runtime/base-url.mjs
|
|
1149
|
+
let proxyPath = "";
|
|
1150
|
+
function getProxyPath() {
|
|
1151
|
+
return proxyPath;
|
|
1152
|
+
}
|
|
1153
|
+
function setProxyPath(path) {
|
|
1154
|
+
proxyPath = path;
|
|
1155
|
+
}
|
|
1156
|
+
//#endregion
|
|
1157
|
+
//#region ../api/dist/chunk-CfYAbeIz.mjs
|
|
1158
|
+
var __defProp = Object.defineProperty;
|
|
1159
|
+
var __exportAll = (all, no_symbols) => {
|
|
1160
|
+
let target = {};
|
|
1161
|
+
for (var name in all) __defProp(target, name, {
|
|
1162
|
+
get: all[name],
|
|
1163
|
+
enumerable: true
|
|
1164
|
+
});
|
|
1165
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
1166
|
+
return target;
|
|
1167
|
+
};
|
|
1168
|
+
//#endregion
|
|
1169
|
+
//#region ../api/dist/generated/endpoints/zitadelNextGen.mjs
|
|
1170
|
+
/**
|
|
1171
|
+
* Generated by orval v8.10.0 🍺
|
|
1172
|
+
* Do not edit manually.
|
|
1173
|
+
* Zitadel NextGen
|
|
1174
|
+
* This is the next generation of the Zitadel identity platform.
|
|
1175
|
+
* OpenAPI spec version: 0.0.1
|
|
1176
|
+
*/
|
|
1177
|
+
var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
|
|
1178
|
+
completeClaim: () => completeClaim,
|
|
1179
|
+
createAuthAttempt: () => createAuthAttempt,
|
|
1180
|
+
createBranding: () => createBranding,
|
|
1181
|
+
createFlow: () => createFlow,
|
|
1182
|
+
createFlowDefinition: () => createFlowDefinition,
|
|
1183
|
+
createHandoff: () => createHandoff,
|
|
1184
|
+
createProject: () => createProject,
|
|
1185
|
+
createSchema: () => createSchema,
|
|
1186
|
+
createSession: () => createSession,
|
|
1187
|
+
createTeam: () => createTeam,
|
|
1188
|
+
createUser: () => createUser,
|
|
1189
|
+
deleteFlowDefinition: () => deleteFlowDefinition,
|
|
1190
|
+
deleteTeam: () => deleteTeam,
|
|
1191
|
+
deleteUserByID: () => deleteUserByID,
|
|
1192
|
+
exchangeHandoff: () => exchangeHandoff,
|
|
1193
|
+
getAuthAttempt: () => getAuthAttempt,
|
|
1194
|
+
getBrandingById: () => getBrandingById,
|
|
1195
|
+
getClaimStatus: () => getClaimStatus,
|
|
1196
|
+
getCompleteClaimUrl: () => getCompleteClaimUrl,
|
|
1197
|
+
getCreateAuthAttemptUrl: () => getCreateAuthAttemptUrl,
|
|
1198
|
+
getCreateBrandingUrl: () => getCreateBrandingUrl,
|
|
1199
|
+
getCreateFlowDefinitionUrl: () => getCreateFlowDefinitionUrl,
|
|
1200
|
+
getCreateFlowUrl: () => getCreateFlowUrl,
|
|
1201
|
+
getCreateHandoffUrl: () => getCreateHandoffUrl,
|
|
1202
|
+
getCreateProjectUrl: () => getCreateProjectUrl,
|
|
1203
|
+
getCreateSchemaUrl: () => getCreateSchemaUrl,
|
|
1204
|
+
getCreateSessionUrl: () => getCreateSessionUrl,
|
|
1205
|
+
getCreateTeamUrl: () => getCreateTeamUrl,
|
|
1206
|
+
getCreateUserUrl: () => getCreateUserUrl,
|
|
1207
|
+
getDeleteFlowDefinitionUrl: () => getDeleteFlowDefinitionUrl,
|
|
1208
|
+
getDeleteTeamUrl: () => getDeleteTeamUrl,
|
|
1209
|
+
getDeleteUserByIDUrl: () => getDeleteUserByIDUrl,
|
|
1210
|
+
getEvent: () => getEvent,
|
|
1211
|
+
getExchangeHandoffUrl: () => getExchangeHandoffUrl,
|
|
1212
|
+
getFlowDefinition: () => getFlowDefinition,
|
|
1213
|
+
getFlowStep: () => getFlowStep,
|
|
1214
|
+
getGetAuthAttemptUrl: () => getGetAuthAttemptUrl,
|
|
1215
|
+
getGetBrandingByIdUrl: () => getGetBrandingByIdUrl,
|
|
1216
|
+
getGetClaimStatusUrl: () => getGetClaimStatusUrl,
|
|
1217
|
+
getGetEventUrl: () => getGetEventUrl,
|
|
1218
|
+
getGetFlowDefinitionUrl: () => getGetFlowDefinitionUrl,
|
|
1219
|
+
getGetFlowStepUrl: () => getGetFlowStepUrl,
|
|
1220
|
+
getGetHealthUrl: () => getGetHealthUrl,
|
|
1221
|
+
getGetLiveUrl: () => getGetLiveUrl,
|
|
1222
|
+
getGetMySessionUrl: () => getGetMySessionUrl,
|
|
1223
|
+
getGetMyUserUrl: () => getGetMyUserUrl,
|
|
1224
|
+
getGetProjectUrl: () => getGetProjectUrl,
|
|
1225
|
+
getGetReadyUrl: () => getGetReadyUrl,
|
|
1226
|
+
getGetSchemaByIdUrl: () => getGetSchemaByIdUrl,
|
|
1227
|
+
getGetSessionUrl: () => getGetSessionUrl,
|
|
1228
|
+
getGetTeamUrl: () => getGetTeamUrl,
|
|
1229
|
+
getGetUserByIDUrl: () => getGetUserByIDUrl,
|
|
1230
|
+
getHealth: () => getHealth,
|
|
1231
|
+
getInitClaimUrl: () => getInitClaimUrl,
|
|
1232
|
+
getIssueChallengeUrl: () => getIssueChallengeUrl,
|
|
1233
|
+
getListBrandingUrl: () => getListBrandingUrl,
|
|
1234
|
+
getListEventsUrl: () => getListEventsUrl,
|
|
1235
|
+
getListFlowDefinitionsUrl: () => getListFlowDefinitionsUrl,
|
|
1236
|
+
getListSchemasUrl: () => getListSchemasUrl,
|
|
1237
|
+
getListUserPasskeysUrl: () => getListUserPasskeysUrl,
|
|
1238
|
+
getListUserTeamsUrl: () => getListUserTeamsUrl,
|
|
1239
|
+
getListUsersUrl: () => getListUsersUrl,
|
|
1240
|
+
getLive: () => getLive,
|
|
1241
|
+
getMySession: () => getMySession,
|
|
1242
|
+
getMyUser: () => getMyUser,
|
|
1243
|
+
getPatchProjectUrl: () => getPatchProjectUrl,
|
|
1244
|
+
getProject: () => getProject,
|
|
1245
|
+
getQueryProjectsUrl: () => getQueryProjectsUrl,
|
|
1246
|
+
getQuerySessionsUrl: () => getQuerySessionsUrl,
|
|
1247
|
+
getQueryTeamsUrl: () => getQueryTeamsUrl,
|
|
1248
|
+
getReady: () => getReady,
|
|
1249
|
+
getRevokeMySessionUrl: () => getRevokeMySessionUrl,
|
|
1250
|
+
getRevokeSessionUrl: () => getRevokeSessionUrl,
|
|
1251
|
+
getSchemaById: () => getSchemaById,
|
|
1252
|
+
getSession: () => getSession,
|
|
1253
|
+
getSetUserPasswordUrl: () => getSetUserPasswordUrl,
|
|
1254
|
+
getSubmitFlowStepUrl: () => getSubmitFlowStepUrl,
|
|
1255
|
+
getTeam: () => getTeam,
|
|
1256
|
+
getUpdateFlowDefinitionUrl: () => getUpdateFlowDefinitionUrl,
|
|
1257
|
+
getUpdateTeamUrl: () => getUpdateTeamUrl,
|
|
1258
|
+
getUserByID: () => getUserByID,
|
|
1259
|
+
getVerifyChallengeProofUrl: () => getVerifyChallengeProofUrl,
|
|
1260
|
+
initClaim: () => initClaim,
|
|
1261
|
+
issueChallenge: () => issueChallenge,
|
|
1262
|
+
listBranding: () => listBranding,
|
|
1263
|
+
listEvents: () => listEvents,
|
|
1264
|
+
listFlowDefinitions: () => listFlowDefinitions,
|
|
1265
|
+
listSchemas: () => listSchemas,
|
|
1266
|
+
listUserPasskeys: () => listUserPasskeys,
|
|
1267
|
+
listUserTeams: () => listUserTeams,
|
|
1268
|
+
listUsers: () => listUsers,
|
|
1269
|
+
patchProject: () => patchProject,
|
|
1270
|
+
queryProjects: () => queryProjects,
|
|
1271
|
+
querySessions: () => querySessions,
|
|
1272
|
+
queryTeams: () => queryTeams,
|
|
1273
|
+
revokeMySession: () => revokeMySession,
|
|
1274
|
+
revokeSession: () => revokeSession,
|
|
1275
|
+
setUserPassword: () => setUserPassword,
|
|
1276
|
+
submitFlowStep: () => submitFlowStep,
|
|
1277
|
+
updateFlowDefinition: () => updateFlowDefinition,
|
|
1278
|
+
updateTeam: () => updateTeam,
|
|
1279
|
+
verifyChallengeProof: () => verifyChallengeProof
|
|
1280
|
+
});
|
|
1281
|
+
const getGetHealthUrl = () => {
|
|
1282
|
+
return `${getProxyPath()}/healthz`;
|
|
796
1283
|
};
|
|
797
1284
|
/**
|
|
798
|
-
*
|
|
799
|
-
|
|
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
|
|
1285
|
+
* Check whether the server is healthy
|
|
1286
|
+
* @summary Check server health
|
|
804
1287
|
*/
|
|
805
|
-
const
|
|
806
|
-
return customFetch(
|
|
1288
|
+
const getHealth = async (options) => {
|
|
1289
|
+
return customFetch(getGetHealthUrl(), {
|
|
807
1290
|
...options,
|
|
808
1291
|
method: "GET"
|
|
809
1292
|
});
|
|
810
1293
|
};
|
|
811
|
-
const
|
|
812
|
-
return `${getProxyPath()}/
|
|
1294
|
+
const getGetLiveUrl = () => {
|
|
1295
|
+
return `${getProxyPath()}/livez`;
|
|
813
1296
|
};
|
|
814
1297
|
/**
|
|
815
|
-
*
|
|
816
|
-
|
|
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
|
|
1298
|
+
* Check whether the server is started
|
|
1299
|
+
* @summary Check server liveness
|
|
821
1300
|
*/
|
|
822
|
-
const
|
|
823
|
-
return customFetch(
|
|
1301
|
+
const getLive = async (options) => {
|
|
1302
|
+
return customFetch(getGetLiveUrl(), {
|
|
824
1303
|
...options,
|
|
825
|
-
method: "
|
|
826
|
-
headers: {
|
|
827
|
-
"Content-Type": "application/json",
|
|
828
|
-
...options?.headers
|
|
829
|
-
},
|
|
830
|
-
body: JSON.stringify(completeClaimBody)
|
|
1304
|
+
method: "GET"
|
|
831
1305
|
});
|
|
832
1306
|
};
|
|
833
|
-
const
|
|
834
|
-
return `${getProxyPath()}/
|
|
1307
|
+
const getGetReadyUrl = () => {
|
|
1308
|
+
return `${getProxyPath()}/readyz`;
|
|
835
1309
|
};
|
|
836
1310
|
/**
|
|
837
|
-
*
|
|
838
|
-
|
|
839
|
-
This is optional — an `auth_attempt` will create a session implicitly if none is provided.
|
|
840
|
-
Use this explicitly when you want to:
|
|
841
|
-
- Pre-allocate a `session_id` before the user is known, so device/telemetry signals
|
|
842
|
-
can be correlated with the eventual authenticated session from the start.
|
|
843
|
-
- Track anonymous state (bot detection, device fingerprint) that survives until authentication.
|
|
844
|
-
|
|
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.
|
|
855
|
-
|
|
856
|
-
Anonymous sessions expire aggressively (10-minute TTL). The TTL resets to the configured
|
|
857
|
-
full session TTL when the first authentication factor is written via a completing `auth_attempt`.
|
|
858
|
-
|
|
859
|
-
* @summary Create an anonymous session shell
|
|
1311
|
+
* Check whether the server is ready to accept requests
|
|
1312
|
+
* @summary Check server readiness
|
|
860
1313
|
*/
|
|
861
|
-
const
|
|
862
|
-
return customFetch(
|
|
1314
|
+
const getReady = async (options) => {
|
|
1315
|
+
return customFetch(getGetReadyUrl(), {
|
|
863
1316
|
...options,
|
|
864
|
-
method: "
|
|
865
|
-
headers: {
|
|
866
|
-
"Content-Type": "application/json",
|
|
867
|
-
...options?.headers
|
|
868
|
-
},
|
|
869
|
-
body: JSON.stringify(createSessionBody)
|
|
1317
|
+
method: "GET"
|
|
870
1318
|
});
|
|
871
1319
|
};
|
|
872
|
-
const
|
|
1320
|
+
const getCreateUserUrl = (params) => {
|
|
873
1321
|
const normalizedParams = new URLSearchParams();
|
|
874
1322
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
875
1323
|
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
876
1324
|
});
|
|
877
1325
|
const stringifiedParams = normalizedParams.toString();
|
|
878
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/
|
|
1326
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users?${stringifiedParams}` : `${getProxyPath()}/users`;
|
|
879
1327
|
};
|
|
880
1328
|
/**
|
|
881
|
-
*
|
|
882
|
-
Sessions of every lifecycle state are returned; each carries its `state`.
|
|
883
|
-
Requires `session.read` permission.
|
|
884
|
-
|
|
885
|
-
* @summary Query sessions
|
|
1329
|
+
* @summary Create user
|
|
886
1330
|
*/
|
|
887
|
-
const
|
|
888
|
-
return customFetch(
|
|
1331
|
+
const createUser = async (createUserBody, params, options) => {
|
|
1332
|
+
return customFetch(getCreateUserUrl(params), {
|
|
889
1333
|
...options,
|
|
890
1334
|
method: "POST",
|
|
891
1335
|
headers: {
|
|
892
1336
|
"Content-Type": "application/json",
|
|
893
1337
|
...options?.headers
|
|
894
1338
|
},
|
|
895
|
-
body: JSON.stringify(
|
|
1339
|
+
body: JSON.stringify(createUserBody)
|
|
896
1340
|
});
|
|
897
1341
|
};
|
|
898
|
-
const
|
|
1342
|
+
const getListUsersUrl = (params) => {
|
|
899
1343
|
const normalizedParams = new URLSearchParams();
|
|
900
1344
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
901
1345
|
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
902
1346
|
});
|
|
903
1347
|
const stringifiedParams = normalizedParams.toString();
|
|
904
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/
|
|
1348
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users?${stringifiedParams}` : `${getProxyPath()}/users`;
|
|
905
1349
|
};
|
|
906
1350
|
/**
|
|
907
|
-
*
|
|
908
|
-
and returns the resulting session and a `session_token`.
|
|
909
|
-
|
|
910
|
-
The server resolves the originating `auth_attempt` from the token and then:
|
|
911
|
-
|
|
912
|
-
| Originating auth_attempt | Outcome |
|
|
913
|
-
|---|---|
|
|
914
|
-
| No `session_id` | A new authenticated session is **created**. |
|
|
915
|
-
| `session_id` points to an anonymous shell | Existing session is **upgraded** — user and factors written in, TTL reset to full session TTL. |
|
|
916
|
-
| `session_id` points to an active session (step-up) | Existing session is **upgraded** — new factors merged, `assurance_levels[]` expanded. |
|
|
917
|
-
|
|
918
|
-
The response shape is identical in all three cases.
|
|
919
|
-
|
|
920
|
-
The `session_token` supersedes any previously issued `session_token` for the same session.
|
|
921
|
-
Clients must replace their stored token at this point.
|
|
922
|
-
|
|
923
|
-
Requires a project service key (OAuth2 client credentials).
|
|
924
|
-
|
|
925
|
-
* @summary Exchange handoff token for a session
|
|
1351
|
+
* @summary List users
|
|
926
1352
|
*/
|
|
927
|
-
const
|
|
928
|
-
return customFetch(
|
|
1353
|
+
const listUsers = async (params, options) => {
|
|
1354
|
+
return customFetch(getListUsersUrl(params), {
|
|
929
1355
|
...options,
|
|
930
|
-
method: "
|
|
931
|
-
headers: {
|
|
932
|
-
"Content-Type": "application/json",
|
|
933
|
-
...options?.headers
|
|
934
|
-
},
|
|
935
|
-
body: JSON.stringify(exchangeHandoffBody)
|
|
1356
|
+
method: "GET"
|
|
936
1357
|
});
|
|
937
1358
|
};
|
|
938
|
-
const
|
|
1359
|
+
const getGetUserByIDUrl = (userId, params) => {
|
|
939
1360
|
const normalizedParams = new URLSearchParams();
|
|
940
1361
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
941
1362
|
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
942
1363
|
});
|
|
943
1364
|
const stringifiedParams = normalizedParams.toString();
|
|
944
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/
|
|
1365
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}?${stringifiedParams}` : `${getProxyPath()}/users/${userId}`;
|
|
945
1366
|
};
|
|
946
1367
|
/**
|
|
947
|
-
*
|
|
948
|
-
satisfied assurance levels.
|
|
949
|
-
|
|
950
|
-
`assurance_levels[]` may shrink over time as factor freshness windows expire,
|
|
951
|
-
without the session itself expiring. Use step-up authentication (a new `auth_attempt`
|
|
952
|
-
against the same `session_id`) to restore a dropped assurance level.
|
|
953
|
-
|
|
954
|
-
* @summary Get session state
|
|
1368
|
+
* @summary Get user by ID
|
|
955
1369
|
*/
|
|
956
|
-
const
|
|
957
|
-
return customFetch(
|
|
1370
|
+
const getUserByID = async (userId, params, options) => {
|
|
1371
|
+
return customFetch(getGetUserByIDUrl(userId, params), {
|
|
958
1372
|
...options,
|
|
959
1373
|
method: "GET"
|
|
960
1374
|
});
|
|
961
1375
|
};
|
|
962
|
-
const
|
|
963
|
-
|
|
964
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
965
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
966
|
-
});
|
|
967
|
-
const stringifiedParams = normalizedParams.toString();
|
|
968
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions/${sessionId}?${stringifiedParams}` : `${getProxyPath()}/sessions/${sessionId}`;
|
|
1376
|
+
const getDeleteUserByIDUrl = (userId) => {
|
|
1377
|
+
return `${getProxyPath()}/users/${userId}`;
|
|
969
1378
|
};
|
|
970
1379
|
/**
|
|
971
|
-
*
|
|
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).
|
|
976
|
-
|
|
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.
|
|
980
|
-
|
|
981
|
-
* @summary Revoke session
|
|
1380
|
+
* @summary Delete user by ID
|
|
982
1381
|
*/
|
|
983
|
-
const
|
|
984
|
-
return customFetch(
|
|
1382
|
+
const deleteUserByID = async (userId, options) => {
|
|
1383
|
+
return customFetch(getDeleteUserByIDUrl(userId), {
|
|
985
1384
|
...options,
|
|
986
1385
|
method: "DELETE"
|
|
987
1386
|
});
|
|
988
1387
|
};
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
* Returns the current state of the current session including its factors and all currently
|
|
994
|
-
satisfied assurance levels.
|
|
995
|
-
|
|
996
|
-
`assurance_levels[]` may shrink over time as factor freshness windows expire,
|
|
997
|
-
without the session itself expiring. Use step-up authentication (a new `auth_attempt`
|
|
998
|
-
against the same `session_id`) to restore a dropped assurance level.
|
|
999
|
-
|
|
1000
|
-
* @summary Get my session state
|
|
1001
|
-
*/
|
|
1002
|
-
const getMySession = async (options) => {
|
|
1003
|
-
return customFetch(getGetMySessionUrl(), {
|
|
1004
|
-
...options,
|
|
1005
|
-
method: "GET"
|
|
1388
|
+
const getListUserPasskeysUrl = (userId, params) => {
|
|
1389
|
+
const normalizedParams = new URLSearchParams();
|
|
1390
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1391
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1006
1392
|
});
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
return `${getProxyPath()}/sessions/me`;
|
|
1393
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1394
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}/passkeys?${stringifiedParams}` : `${getProxyPath()}/users/${userId}/passkeys`;
|
|
1010
1395
|
};
|
|
1011
1396
|
/**
|
|
1012
|
-
*
|
|
1013
|
-
|
|
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.
|
|
1018
|
-
|
|
1019
|
-
* @summary Revoke my session
|
|
1397
|
+
* @summary List user passkeys
|
|
1020
1398
|
*/
|
|
1021
|
-
const
|
|
1022
|
-
return customFetch(
|
|
1399
|
+
const listUserPasskeys = async (userId, params, options) => {
|
|
1400
|
+
return customFetch(getListUserPasskeysUrl(userId, params), {
|
|
1023
1401
|
...options,
|
|
1024
|
-
method: "
|
|
1402
|
+
method: "GET"
|
|
1025
1403
|
});
|
|
1026
1404
|
};
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1030
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1031
|
-
});
|
|
1032
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1033
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/schemas?${stringifiedParams}` : `${getProxyPath()}/schemas`;
|
|
1405
|
+
const getSetUserPasswordUrl = (userId) => {
|
|
1406
|
+
return `${getProxyPath()}/users/${userId}/password`;
|
|
1034
1407
|
};
|
|
1035
1408
|
/**
|
|
1036
|
-
*
|
|
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.
|
|
1042
|
-
|
|
1043
|
-
The schema can either be a concrete schema, e.g. a user schema, or a
|
|
1044
|
-
schema-url which will be resolved by the server.
|
|
1045
|
-
|
|
1046
|
-
* @summary Create new schema
|
|
1409
|
+
* @summary Set user password
|
|
1047
1410
|
*/
|
|
1048
|
-
const
|
|
1049
|
-
return customFetch(
|
|
1411
|
+
const setUserPassword = async (userId, setUserPasswordBody, options) => {
|
|
1412
|
+
return customFetch(getSetUserPasswordUrl(userId), {
|
|
1050
1413
|
...options,
|
|
1051
|
-
method: "
|
|
1414
|
+
method: "PUT",
|
|
1052
1415
|
headers: {
|
|
1053
1416
|
"Content-Type": "application/json",
|
|
1054
1417
|
...options?.headers
|
|
1055
1418
|
},
|
|
1056
|
-
body: JSON.stringify(
|
|
1419
|
+
body: JSON.stringify(setUserPasswordBody)
|
|
1057
1420
|
});
|
|
1058
1421
|
};
|
|
1059
|
-
const
|
|
1422
|
+
const getListUserTeamsUrl = (userId, params) => {
|
|
1060
1423
|
const normalizedParams = new URLSearchParams();
|
|
1061
1424
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1062
1425
|
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1063
1426
|
});
|
|
1064
1427
|
const stringifiedParams = normalizedParams.toString();
|
|
1065
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/
|
|
1428
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}/teams?${stringifiedParams}` : `${getProxyPath()}/users/${userId}/teams`;
|
|
1066
1429
|
};
|
|
1067
1430
|
/**
|
|
1068
|
-
*
|
|
1069
|
-
|
|
1431
|
+
* The user's team roster, ordered by team name. Each entry carries the
|
|
1432
|
+
team's name, so a client renders a page without resolving ids one by one.
|
|
1433
|
+
|
|
1434
|
+
This is the N:N roster and it is not lifecycle ownership: the single team
|
|
1435
|
+
that owns the user's lifecycle is reported as `metadata.lifecycle_owner_team_id`
|
|
1436
|
+
on the user read endpoints. Memberships the user was removed from are not
|
|
1437
|
+
returned.
|
|
1070
1438
|
|
|
1071
|
-
* @summary List
|
|
1439
|
+
* @summary List the teams a user belongs to
|
|
1072
1440
|
*/
|
|
1073
|
-
const
|
|
1074
|
-
return customFetch(
|
|
1441
|
+
const listUserTeams = async (userId, params, options) => {
|
|
1442
|
+
return customFetch(getListUserTeamsUrl(userId, params), {
|
|
1075
1443
|
...options,
|
|
1076
1444
|
method: "GET"
|
|
1077
1445
|
});
|
|
1078
1446
|
};
|
|
1079
|
-
const
|
|
1080
|
-
|
|
1081
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1082
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1083
|
-
});
|
|
1084
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1085
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/schemas/${id}?${stringifiedParams}` : `${getProxyPath()}/schemas/${id}`;
|
|
1447
|
+
const getGetMyUserUrl = () => {
|
|
1448
|
+
return `${getProxyPath()}/users/me`;
|
|
1086
1449
|
};
|
|
1087
1450
|
/**
|
|
1088
|
-
* Get
|
|
1089
|
-
* @summary Get schema by ID
|
|
1451
|
+
* @summary Get my user information
|
|
1090
1452
|
*/
|
|
1091
|
-
const
|
|
1092
|
-
return customFetch(
|
|
1453
|
+
const getMyUser = async (options) => {
|
|
1454
|
+
return customFetch(getGetMyUserUrl(), {
|
|
1093
1455
|
...options,
|
|
1094
1456
|
method: "GET"
|
|
1095
1457
|
});
|
|
1096
1458
|
};
|
|
1097
|
-
const
|
|
1098
|
-
return `${getProxyPath()}/
|
|
1459
|
+
const getCreateFlowUrl = () => {
|
|
1460
|
+
return `${getProxyPath()}/flow`;
|
|
1099
1461
|
};
|
|
1100
1462
|
/**
|
|
1101
|
-
*
|
|
1102
|
-
|
|
1103
|
-
|
|
1463
|
+
* Resolves a flow definition based on purpose + audience context and returns
|
|
1464
|
+
the first capability step. Creates a new session implicitly unless
|
|
1465
|
+
`session_id` is provided (for step-up / reauth on an existing session).
|
|
1104
1466
|
|
|
1105
|
-
|
|
1467
|
+
The response contains an `id` field — the flow handle. Use it as the path
|
|
1468
|
+
parameter for all subsequent `/flow/{id}/submit` calls.
|
|
1106
1469
|
|
|
1107
|
-
|
|
1470
|
+
The response also sets an encrypted `HttpOnly` cookie (`_zflow`) containing
|
|
1471
|
+
the flow's orchestration state (current step, collected data, history).
|
|
1472
|
+
The server is stateless between requests — all flow state lives in this
|
|
1473
|
+
cookie. The browser sends it automatically on subsequent requests.
|
|
1474
|
+
|
|
1475
|
+
* @summary Start a new flow
|
|
1108
1476
|
*/
|
|
1109
|
-
const
|
|
1110
|
-
return customFetch(
|
|
1477
|
+
const createFlow = async (createFlowBody, options) => {
|
|
1478
|
+
return customFetch(getCreateFlowUrl(), {
|
|
1111
1479
|
...options,
|
|
1112
1480
|
method: "POST",
|
|
1113
1481
|
headers: {
|
|
1114
1482
|
"Content-Type": "application/json",
|
|
1115
1483
|
...options?.headers
|
|
1116
1484
|
},
|
|
1117
|
-
body: JSON.stringify(
|
|
1485
|
+
body: JSON.stringify(createFlowBody)
|
|
1118
1486
|
});
|
|
1119
1487
|
};
|
|
1120
|
-
const
|
|
1121
|
-
|
|
1122
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1123
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1124
|
-
});
|
|
1125
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1126
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions?${stringifiedParams}` : `${getProxyPath()}/flow_definitions`;
|
|
1488
|
+
const getGetFlowStepUrl = (id) => {
|
|
1489
|
+
return `${getProxyPath()}/flow/${id}`;
|
|
1127
1490
|
};
|
|
1128
1491
|
/**
|
|
1129
|
-
*
|
|
1130
|
-
|
|
1492
|
+
* Returns the current capability step without advancing the state machine.
|
|
1493
|
+
Useful for page reloads or re-rendering after a network error.
|
|
1131
1494
|
|
|
1132
|
-
* @summary
|
|
1495
|
+
* @summary Get current step (re-render)
|
|
1133
1496
|
*/
|
|
1134
|
-
const
|
|
1135
|
-
return customFetch(
|
|
1497
|
+
const getFlowStep = async (id, options) => {
|
|
1498
|
+
return customFetch(getGetFlowStepUrl(id), {
|
|
1136
1499
|
...options,
|
|
1137
1500
|
method: "GET"
|
|
1138
1501
|
});
|
|
1139
1502
|
};
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1142
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1143
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1144
|
-
});
|
|
1145
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1146
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions/${id}?${stringifiedParams}` : `${getProxyPath()}/flow_definitions/${id}`;
|
|
1503
|
+
const getSubmitFlowStepUrl = (id) => {
|
|
1504
|
+
return `${getProxyPath()}/flow/${id}/submit`;
|
|
1147
1505
|
};
|
|
1148
1506
|
/**
|
|
1149
|
-
*
|
|
1150
|
-
|
|
1507
|
+
* Submits user input for the current step. The server validates,
|
|
1508
|
+
processes (e.g., verifies a credential), advances the state machine
|
|
1509
|
+
through any invisible steps, and returns the next visible step.
|
|
1510
|
+
|
|
1511
|
+
The response sets an updated encrypted `HttpOnly` cookie (`_zflow`)
|
|
1512
|
+
with the new flow state. The server is stateless — all orchestration
|
|
1513
|
+
state is carried in this cookie between requests.
|
|
1514
|
+
|
|
1515
|
+
**Important:** The `id` in the response may differ from the `id` used in
|
|
1516
|
+
the request. This happens when a flow pivots (pushes a new flow onto the
|
|
1517
|
+
stack) or when a stacked flow completes (auto-pops to the parent flow).
|
|
1518
|
+
Always use the `id` from the latest response for the next request.
|
|
1519
|
+
|
|
1520
|
+
## Flow completion
|
|
1521
|
+
|
|
1522
|
+
When `step.type` is `complete`, the flow is terminal. The `step.behavior`
|
|
1523
|
+
field tells the frontend what to do:
|
|
1524
|
+
|
|
1525
|
+
| `behavior` | Action |
|
|
1526
|
+
|------------- |------------------------------------------------------------|
|
|
1527
|
+
| `redirect` | Navigate to `redirect_uri` (OIDC/SAML auth request done). |
|
|
1528
|
+
| `show` | Render the step as a success screen (e.g., registration). |
|
|
1529
|
+
|
|
1530
|
+
A `complete` step is only returned when the **entire flow stack** is done.
|
|
1531
|
+
If a stacked flow (e.g., recovery pivoted from login) finishes, the server
|
|
1532
|
+
auto-pops to the parent flow and returns the parent's next step — the
|
|
1533
|
+
frontend never sees a `complete` for intermediate flows.
|
|
1534
|
+
|
|
1535
|
+
* @summary Submit step data and advance
|
|
1151
1536
|
*/
|
|
1152
|
-
const
|
|
1153
|
-
return customFetch(
|
|
1537
|
+
const submitFlowStep = async (id, submitFlowStepBody, options) => {
|
|
1538
|
+
return customFetch(getSubmitFlowStepUrl(id), {
|
|
1154
1539
|
...options,
|
|
1155
|
-
method: "
|
|
1540
|
+
method: "POST",
|
|
1541
|
+
headers: {
|
|
1542
|
+
"Content-Type": "application/json",
|
|
1543
|
+
...options?.headers
|
|
1544
|
+
},
|
|
1545
|
+
body: JSON.stringify(submitFlowStepBody)
|
|
1156
1546
|
});
|
|
1157
1547
|
};
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1161
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1162
|
-
});
|
|
1163
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1164
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions/${id}?${stringifiedParams}` : `${getProxyPath()}/flow_definitions/${id}`;
|
|
1548
|
+
const getCreateAuthAttemptUrl = () => {
|
|
1549
|
+
return `${getProxyPath()}/auth_attempts`;
|
|
1165
1550
|
};
|
|
1166
1551
|
/**
|
|
1167
|
-
*
|
|
1168
|
-
If `flow_definition.status` is omitted, the current status is preserved
|
|
1552
|
+
* Starts a new authentication attempt. This is the entry point for the auth_attempts state machine.
|
|
1169
1553
|
|
|
1170
|
-
|
|
1554
|
+
An attempt is an ephemeral (15-minute TTL) state machine that drives a single authentication round.
|
|
1555
|
+
It accepts factor challenges, verifies proofs, and completes into a session or handoff token.
|
|
1556
|
+
|
|
1557
|
+
Accepts a project_id and challenge_nonce (from POST /bootstrap/challenge). For step-up re-auth,
|
|
1558
|
+
also include session_id to add factors to an existing session.
|
|
1559
|
+
|
|
1560
|
+
* @summary Create a new authentication attempt
|
|
1171
1561
|
*/
|
|
1172
|
-
const
|
|
1173
|
-
return customFetch(
|
|
1562
|
+
const createAuthAttempt = async (createAuthAttemptBody, options) => {
|
|
1563
|
+
return customFetch(getCreateAuthAttemptUrl(), {
|
|
1174
1564
|
...options,
|
|
1175
|
-
method: "
|
|
1565
|
+
method: "POST",
|
|
1176
1566
|
headers: {
|
|
1177
1567
|
"Content-Type": "application/json",
|
|
1178
1568
|
...options?.headers
|
|
1179
1569
|
},
|
|
1180
|
-
body: JSON.stringify(
|
|
1570
|
+
body: JSON.stringify(createAuthAttemptBody)
|
|
1181
1571
|
});
|
|
1182
1572
|
};
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1186
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1187
|
-
});
|
|
1188
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1189
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions/${id}?${stringifiedParams}` : `${getProxyPath()}/flow_definitions/${id}`;
|
|
1573
|
+
const getGetAuthAttemptUrl = (attemptId) => {
|
|
1574
|
+
return `${getProxyPath()}/auth_attempts/${attemptId}`;
|
|
1190
1575
|
};
|
|
1191
1576
|
/**
|
|
1192
|
-
*
|
|
1193
|
-
If the flow definition is currently being used by a flow, the deletion will fail.
|
|
1194
|
-
If the flow definition is the last active flow definition for a given purpose, the deletion will fail to prevent disruption of new flows being started for that purpose.
|
|
1577
|
+
* Polls the current state of an authentication attempt.
|
|
1195
1578
|
|
|
1196
|
-
|
|
1579
|
+
Returns the attempt's state, available factors for the next challenge,
|
|
1580
|
+
challenges issued so far, and any errors preventing progress.
|
|
1581
|
+
|
|
1582
|
+
Use this for polling during long-running factor verifications (e.g., waiting for
|
|
1583
|
+
a federated IdP callback or a device flow).
|
|
1584
|
+
|
|
1585
|
+
* @summary Get authentication attempt state
|
|
1197
1586
|
*/
|
|
1198
|
-
const
|
|
1199
|
-
return customFetch(
|
|
1587
|
+
const getAuthAttempt = async (attemptId, options) => {
|
|
1588
|
+
return customFetch(getGetAuthAttemptUrl(attemptId), {
|
|
1200
1589
|
...options,
|
|
1201
|
-
method: "
|
|
1590
|
+
method: "GET"
|
|
1202
1591
|
});
|
|
1203
1592
|
};
|
|
1204
|
-
const
|
|
1205
|
-
|
|
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`;
|
|
1593
|
+
const getIssueChallengeUrl = (attemptId) => {
|
|
1594
|
+
return `${getProxyPath()}/auth_attempts/${attemptId}/challenges`;
|
|
1211
1595
|
};
|
|
1212
1596
|
/**
|
|
1213
|
-
*
|
|
1597
|
+
* Issues a single-factor verification challenge within an auth attempt.
|
|
1598
|
+
|
|
1599
|
+
This advances the authentication state machine by requesting a specific factor method
|
|
1600
|
+
(password, passkey, TOTP, OTP via SMS, etc.). The server responds with challenge details
|
|
1601
|
+
including method, metadata, and any UI hints. The client then verifies the proof
|
|
1602
|
+
by calling POST /auth_attempts/{attempt_id}/challenges/{challenge_id}/verify.
|
|
1603
|
+
|
|
1604
|
+
* @summary Issue a factor challenge
|
|
1214
1605
|
*/
|
|
1215
|
-
const
|
|
1216
|
-
return customFetch(
|
|
1606
|
+
const issueChallenge = async (attemptId, issueChallengeBody, options) => {
|
|
1607
|
+
return customFetch(getIssueChallengeUrl(attemptId), {
|
|
1217
1608
|
...options,
|
|
1218
1609
|
method: "POST",
|
|
1219
1610
|
headers: {
|
|
1220
1611
|
"Content-Type": "application/json",
|
|
1221
1612
|
...options?.headers
|
|
1222
1613
|
},
|
|
1223
|
-
body: JSON.stringify(
|
|
1614
|
+
body: JSON.stringify(issueChallengeBody)
|
|
1224
1615
|
});
|
|
1225
1616
|
};
|
|
1226
|
-
const
|
|
1227
|
-
|
|
1228
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1229
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1230
|
-
});
|
|
1231
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1232
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/query?${stringifiedParams}` : `${getProxyPath()}/teams/query`;
|
|
1617
|
+
const getVerifyChallengeProofUrl = (attemptId, challengeId) => {
|
|
1618
|
+
return `${getProxyPath()}/auth_attempts/${attemptId}/challenges/${challengeId}/verify`;
|
|
1233
1619
|
};
|
|
1234
1620
|
/**
|
|
1235
|
-
*
|
|
1236
|
-
Teams of every lifecycle status are returned; each carries its `status`.
|
|
1621
|
+
* Submits a proof (credential, code, assertion) to verify a factor challenge.
|
|
1237
1622
|
|
|
1238
|
-
|
|
1623
|
+
The proof format depends on the challenge method. For example:
|
|
1624
|
+
- `password` method: { password: "…" }
|
|
1625
|
+
- `totp` method: { totp: { code: "123456" } }
|
|
1626
|
+
- `passkey` method: { passkey: { assertion: "…" } }
|
|
1627
|
+
- `recovery_code` method: { recovery_code: "…" }
|
|
1628
|
+
|
|
1629
|
+
On successful verification, the factor is written to the auth attempt.
|
|
1630
|
+
The attempt moves to the next pending challenge or completes if all required factors are verified.
|
|
1631
|
+
|
|
1632
|
+
* @summary Verify a factor proof
|
|
1239
1633
|
*/
|
|
1240
|
-
const
|
|
1241
|
-
return customFetch(
|
|
1634
|
+
const verifyChallengeProof = async (attemptId, challengeId, verifyChallengeProofBody, options) => {
|
|
1635
|
+
return customFetch(getVerifyChallengeProofUrl(attemptId, challengeId), {
|
|
1242
1636
|
...options,
|
|
1243
1637
|
method: "POST",
|
|
1244
1638
|
headers: {
|
|
1245
1639
|
"Content-Type": "application/json",
|
|
1246
1640
|
...options?.headers
|
|
1247
1641
|
},
|
|
1248
|
-
body: JSON.stringify(
|
|
1249
|
-
});
|
|
1250
|
-
};
|
|
1251
|
-
const getGetTeamUrl = (teamId, params) => {
|
|
1252
|
-
const normalizedParams = new URLSearchParams();
|
|
1253
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1254
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1255
|
-
});
|
|
1256
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1257
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/${teamId}?${stringifiedParams}` : `${getProxyPath()}/teams/${teamId}`;
|
|
1258
|
-
};
|
|
1259
|
-
/**
|
|
1260
|
-
* Returns a Team by its id.
|
|
1261
|
-
|
|
1262
|
-
* @summary Get team
|
|
1263
|
-
*/
|
|
1264
|
-
const getTeam = async (teamId, params, options) => {
|
|
1265
|
-
return customFetch(getGetTeamUrl(teamId, params), {
|
|
1266
|
-
...options,
|
|
1267
|
-
method: "GET"
|
|
1642
|
+
body: JSON.stringify(verifyChallengeProofBody)
|
|
1268
1643
|
});
|
|
1269
1644
|
};
|
|
1270
|
-
const
|
|
1271
|
-
|
|
1272
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1273
|
-
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1274
|
-
});
|
|
1275
|
-
const stringifiedParams = normalizedParams.toString();
|
|
1276
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/${teamId}?${stringifiedParams}` : `${getProxyPath()}/teams/${teamId}`;
|
|
1645
|
+
const getCreateHandoffUrl = (attemptId) => {
|
|
1646
|
+
return `${getProxyPath()}/auth_attempts/${attemptId}/handoff`;
|
|
1277
1647
|
};
|
|
1278
1648
|
/**
|
|
1279
|
-
*
|
|
1649
|
+
* Completes the authentication attempt and mints a `handoff_token`.
|
|
1280
1650
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1651
|
+
Call this after all required factors have been verified and the attempt is in `completed` state.
|
|
1652
|
+
The handoff token is short-lived (≤60 seconds) and must be exchanged at
|
|
1653
|
+
POST /sessions/exchange to receive the final session and session_token.
|
|
1284
1654
|
|
|
1285
|
-
The
|
|
1286
|
-
|
|
1655
|
+
The handoff token is:
|
|
1656
|
+
- Single-use (atomic exchange, no retry)
|
|
1657
|
+
- Audience-bound (requires matching project key for exchange)
|
|
1658
|
+
- Idempotency-safe within a 5-minute window (see conventions)
|
|
1287
1659
|
|
|
1288
|
-
* @summary
|
|
1660
|
+
* @summary Complete authentication and create handoff token
|
|
1289
1661
|
*/
|
|
1290
|
-
const
|
|
1291
|
-
return customFetch(
|
|
1662
|
+
const createHandoff = async (attemptId, options) => {
|
|
1663
|
+
return customFetch(getCreateHandoffUrl(attemptId), {
|
|
1292
1664
|
...options,
|
|
1293
|
-
method: "
|
|
1665
|
+
method: "POST"
|
|
1294
1666
|
});
|
|
1295
1667
|
};
|
|
1296
|
-
const
|
|
1297
|
-
|
|
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}`;
|
|
1668
|
+
const getCreateProjectUrl = () => {
|
|
1669
|
+
return `${getProxyPath()}/projects`;
|
|
1303
1670
|
};
|
|
1304
1671
|
/**
|
|
1305
|
-
*
|
|
1306
|
-
|
|
1307
|
-
* @summary Update team
|
|
1672
|
+
* @summary Create project
|
|
1308
1673
|
*/
|
|
1309
|
-
const
|
|
1310
|
-
return customFetch(
|
|
1674
|
+
const createProject = async (createProjectBody, options) => {
|
|
1675
|
+
return customFetch(getCreateProjectUrl(), {
|
|
1311
1676
|
...options,
|
|
1312
|
-
method: "
|
|
1677
|
+
method: "POST",
|
|
1313
1678
|
headers: {
|
|
1314
1679
|
"Content-Type": "application/json",
|
|
1315
1680
|
...options?.headers
|
|
1316
1681
|
},
|
|
1317
|
-
body: JSON.stringify(
|
|
1682
|
+
body: JSON.stringify(createProjectBody)
|
|
1318
1683
|
});
|
|
1319
1684
|
};
|
|
1320
|
-
const
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1685
|
+
const getQueryProjectsUrl = () => {
|
|
1686
|
+
return `${getProxyPath()}/projects/query`;
|
|
1687
|
+
};
|
|
1688
|
+
/**
|
|
1689
|
+
* @summary Query projects
|
|
1690
|
+
*/
|
|
1691
|
+
const queryProjects = async (queryProjectsBody, options) => {
|
|
1692
|
+
return customFetch(getQueryProjectsUrl(), {
|
|
1693
|
+
...options,
|
|
1694
|
+
method: "POST",
|
|
1695
|
+
headers: {
|
|
1696
|
+
"Content-Type": "application/json",
|
|
1697
|
+
...options?.headers
|
|
1698
|
+
},
|
|
1699
|
+
body: JSON.stringify(queryProjectsBody)
|
|
1324
1700
|
});
|
|
1325
|
-
|
|
1326
|
-
|
|
1701
|
+
};
|
|
1702
|
+
const getGetProjectUrl = (projectId) => {
|
|
1703
|
+
return `${getProxyPath()}/projects/${projectId}`;
|
|
1327
1704
|
};
|
|
1328
1705
|
/**
|
|
1329
|
-
*
|
|
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).
|
|
1706
|
+
* Returns the current state of a project.
|
|
1333
1707
|
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1708
|
+
* @summary Get project
|
|
1709
|
+
*/
|
|
1710
|
+
const getProject = async (projectId, options) => {
|
|
1711
|
+
return customFetch(getGetProjectUrl(projectId), {
|
|
1712
|
+
...options,
|
|
1713
|
+
method: "GET"
|
|
1714
|
+
});
|
|
1715
|
+
};
|
|
1716
|
+
const getPatchProjectUrl = (projectId) => {
|
|
1717
|
+
return `${getProxyPath()}/projects/${projectId}`;
|
|
1718
|
+
};
|
|
1719
|
+
/**
|
|
1720
|
+
* Updates the state of a project.
|
|
1338
1721
|
|
|
1339
|
-
* @summary
|
|
1722
|
+
* @summary Update project
|
|
1340
1723
|
*/
|
|
1341
|
-
const
|
|
1342
|
-
return customFetch(
|
|
1724
|
+
const patchProject = async (projectId, patchProjectBody, options) => {
|
|
1725
|
+
return customFetch(getPatchProjectUrl(projectId), {
|
|
1343
1726
|
...options,
|
|
1344
|
-
method: "
|
|
1727
|
+
method: "PATCH",
|
|
1345
1728
|
headers: {
|
|
1346
1729
|
"Content-Type": "application/json",
|
|
1347
1730
|
...options?.headers
|
|
1348
1731
|
},
|
|
1349
|
-
body: JSON.stringify(
|
|
1732
|
+
body: JSON.stringify(patchProjectBody)
|
|
1350
1733
|
});
|
|
1351
1734
|
};
|
|
1352
|
-
const
|
|
1353
|
-
|
|
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`;
|
|
1735
|
+
const getInitClaimUrl = (projectId) => {
|
|
1736
|
+
return `${getProxyPath()}/projects/${projectId}/claim/init`;
|
|
1359
1737
|
};
|
|
1360
1738
|
/**
|
|
1361
|
-
*
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1739
|
+
* Starts a claim challenge for an unclaimed project. Authenticated by the
|
|
1740
|
+
project secret, this mints a single-use, short-lived challenge and returns
|
|
1741
|
+
the `claim_url` the developer opens in a browser to complete the claim,
|
|
1742
|
+
together with the `challenge_id` the CLI polls with. The exact expiry is
|
|
1743
|
+
carried by `expires_at` on the response.
|
|
1366
1744
|
|
|
1367
|
-
* @summary
|
|
1745
|
+
* @summary Initialize a project claim
|
|
1368
1746
|
*/
|
|
1369
|
-
const
|
|
1370
|
-
return customFetch(
|
|
1747
|
+
const initClaim = async (projectId, options) => {
|
|
1748
|
+
return customFetch(getInitClaimUrl(projectId), {
|
|
1371
1749
|
...options,
|
|
1372
|
-
method: "
|
|
1750
|
+
method: "POST"
|
|
1373
1751
|
});
|
|
1374
1752
|
};
|
|
1375
|
-
const
|
|
1753
|
+
const getGetClaimStatusUrl = (projectId, params) => {
|
|
1376
1754
|
const normalizedParams = new URLSearchParams();
|
|
1377
1755
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1378
1756
|
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1379
1757
|
});
|
|
1380
1758
|
const stringifiedParams = normalizedParams.toString();
|
|
1381
|
-
return stringifiedParams.length > 0 ? `${getProxyPath()}/
|
|
1759
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/projects/${projectId}/claim/status?${stringifiedParams}` : `${getProxyPath()}/projects/${projectId}/claim/status`;
|
|
1382
1760
|
};
|
|
1383
1761
|
/**
|
|
1384
|
-
*
|
|
1385
|
-
|
|
1762
|
+
* Polled by the CLI while a browser completes the claim. Authorized by the
|
|
1763
|
+
project secret that initiated the challenge. Returns `pending`, or
|
|
1764
|
+
`completed` with the owning team, the claim timestamp, and the dashboard
|
|
1765
|
+
URL once the browser leg has finished.
|
|
1766
|
+
|
|
1767
|
+
* @summary Get claim status
|
|
1386
1768
|
*/
|
|
1387
|
-
const
|
|
1388
|
-
return customFetch(
|
|
1769
|
+
const getClaimStatus = async (projectId, params, options) => {
|
|
1770
|
+
return customFetch(getGetClaimStatusUrl(projectId, params), {
|
|
1389
1771
|
...options,
|
|
1390
1772
|
method: "GET"
|
|
1391
1773
|
});
|
|
1392
1774
|
};
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
* Factory for the typed Zitadel client every consumer reaches for.
|
|
1397
|
-
*
|
|
1398
|
-
* Wraps the orval-generated endpoints in a {@link Proxy} so each
|
|
1399
|
-
* generated function sees the right base URL and (optionally) the
|
|
1400
|
-
* right bearer token at call time — without the caller having to
|
|
1401
|
-
* thread either through every operation, and without exposing the
|
|
1402
|
-
* module-globals (`setProxyPath` / `setApiAuthToken`) as part of the
|
|
1403
|
-
* public API.
|
|
1404
|
-
*
|
|
1405
|
-
* Per-instance isolation: multiple clients can coexist in one process
|
|
1406
|
-
* (different servers, different tokens). Their Proxy `get` traps set
|
|
1407
|
-
* the globals synchronously before invoking the underlying generated
|
|
1408
|
-
* function, which reads them at the top of `customFetch` before any
|
|
1409
|
-
* `await`. Two clients running interleaved within a single JS turn
|
|
1410
|
-
* are safe; two clients running in parallel across awaits could
|
|
1411
|
-
* clobber each other — not a pattern any current consumer uses.
|
|
1412
|
-
*/
|
|
1413
|
-
/**
|
|
1414
|
-
* Build a typed Zitadel client pre-bound to a base URL and (optionally)
|
|
1415
|
-
* a bearer token. Every method on the returned object mirrors a
|
|
1416
|
-
* generated orval function:
|
|
1417
|
-
*
|
|
1418
|
-
* const client = createZitadelClient({ baseUrl, token });
|
|
1419
|
-
* await client.createProject({ preview_origins: [] });
|
|
1420
|
-
* await client.createSchema(body, { project_id });
|
|
1421
|
-
*/
|
|
1422
|
-
function createZitadelClient(opts) {
|
|
1423
|
-
let baseUrl = opts.baseUrl;
|
|
1424
|
-
while (baseUrl.endsWith("/")) baseUrl = baseUrl.slice(0, -1);
|
|
1425
|
-
return new Proxy(zitadelNextGen_exports, { get(target, prop, receiver) {
|
|
1426
|
-
const value = Reflect.get(target, prop, receiver);
|
|
1427
|
-
if (typeof value !== "function") return value;
|
|
1428
|
-
return (...args) => {
|
|
1429
|
-
setProxyPath(baseUrl);
|
|
1430
|
-
setApiAuthToken(opts.token);
|
|
1431
|
-
return value(...args);
|
|
1432
|
-
};
|
|
1433
|
-
} });
|
|
1434
|
-
}
|
|
1435
|
-
//#endregion
|
|
1436
|
-
//#region ../api/dist/runtime/config.mjs
|
|
1437
|
-
/**
|
|
1438
|
-
* Slot for the configured project, stored on `globalThis` under a
|
|
1439
|
-
* {@link Symbol.for} key. Every copy of this module evaluated in the
|
|
1440
|
-
* same JS realm resolves the symbol through the global symbol registry
|
|
1441
|
-
* to the same identity, so they all read and write a single slot —
|
|
1442
|
-
* needed because the standalone components bundle inlines its own copy
|
|
1443
|
-
* of this file, and dual-package hazards / monorepo duplicates can load
|
|
1444
|
-
* a second copy alongside the app's. With a module-local `let` each
|
|
1445
|
-
* instance kept its own singleton and `configureZitadel()` calls in one
|
|
1446
|
-
* were invisible to `getZitadelConfig()` calls in another. Sharing is
|
|
1447
|
-
* realm-scoped — separate realms (iframes, Node `vm` contexts, worker
|
|
1448
|
-
* threads) have their own registries and are not unified by this.
|
|
1449
|
-
*/
|
|
1450
|
-
const PROJECT_SLOT = Symbol.for("@zitadel/api/config:currentProject");
|
|
1451
|
-
function readSlot() {
|
|
1452
|
-
return globalThis[PROJECT_SLOT] ?? null;
|
|
1453
|
-
}
|
|
1454
|
-
/**
|
|
1455
|
-
* Per-project API client cache. Ensures `getApi(project)` returns the
|
|
1456
|
-
* same instance for the same project handle — no re-wrapping on every call.
|
|
1457
|
-
* Module-local on purpose: keyed by the shared frozen `ZitadelProject`
|
|
1458
|
-
* object identity, so cross-instance reads still resolve through the
|
|
1459
|
-
* same key and at worst memoize once per module instance.
|
|
1460
|
-
*/
|
|
1461
|
-
const apiCache = /* @__PURE__ */ new WeakMap();
|
|
1462
|
-
/**
|
|
1463
|
-
* Returns a typed API client for the given project, with the base URL
|
|
1464
|
-
* pre-bound. Cached per project handle — safe to call multiple times.
|
|
1465
|
-
*
|
|
1466
|
-
* ```ts
|
|
1467
|
-
* const project = configureZitadel({ ... });
|
|
1468
|
-
* export const api = getApi(project);
|
|
1469
|
-
* ```
|
|
1470
|
-
*/
|
|
1471
|
-
function getApi(project) {
|
|
1472
|
-
let api = apiCache.get(project);
|
|
1473
|
-
if (!api) {
|
|
1474
|
-
api = createZitadelClient({
|
|
1475
|
-
baseUrl: project.proxyPath,
|
|
1476
|
-
token: project.publishableKey
|
|
1477
|
-
});
|
|
1478
|
-
apiCache.set(project, api);
|
|
1479
|
-
}
|
|
1480
|
-
return api;
|
|
1481
|
-
}
|
|
1482
|
-
/**
|
|
1483
|
-
* Returns the current project handle, or `null` if {@link configureZitadel}
|
|
1484
|
-
* has not been called yet.
|
|
1485
|
-
*/
|
|
1486
|
-
function getZitadelConfig() {
|
|
1487
|
-
return readSlot();
|
|
1488
|
-
}
|
|
1489
|
-
//#endregion
|
|
1490
|
-
//#region src/orchestrator/resolve-api.ts
|
|
1491
|
-
/** Matches the `configureZitadel()` default so attribute and JS config agree. */
|
|
1492
|
-
const DEFAULT_PROXY_PATH = "/__nextgen";
|
|
1493
|
-
/**
|
|
1494
|
-
* Cache of project handles synthesized from attributes, keyed by their value
|
|
1495
|
-
* tuple. `getApi()` caches its client in a `WeakMap` keyed by the project's
|
|
1496
|
-
* identity, so the same attribute values must yield the *same* frozen object
|
|
1497
|
-
* across the several `resolveApi()` calls a single flow makes — otherwise
|
|
1498
|
-
* every call misses the cache and re-wraps a fresh client. A real page uses a
|
|
1499
|
-
* handful of distinct configs at most; the FIFO bound below is just a guard so
|
|
1500
|
-
* a pathological page that churns through unique configs can't leak memory.
|
|
1501
|
-
*/
|
|
1502
|
-
const MAX_SYNTHETIC_PROJECTS = 32;
|
|
1503
|
-
const syntheticProjects = /* @__PURE__ */ new Map();
|
|
1504
|
-
/**
|
|
1505
|
-
* Builds a `ZitadelProject` from declarative attributes, or `undefined` when
|
|
1506
|
-
* no `project-id` was given (nothing to configure). Mirrors
|
|
1507
|
-
* `configureZitadel()`'s proxy-path defaulting so the two config paths are
|
|
1508
|
-
* interchangeable.
|
|
1509
|
-
*/
|
|
1510
|
-
function projectFromAttrs({ projectId, proxyPath, url }) {
|
|
1511
|
-
if (!projectId) return void 0;
|
|
1512
|
-
const resolvedProxyPath = proxyPath || DEFAULT_PROXY_PATH;
|
|
1513
|
-
const resolvedUrl = url || void 0;
|
|
1514
|
-
const key = JSON.stringify([
|
|
1515
|
-
projectId,
|
|
1516
|
-
resolvedProxyPath,
|
|
1517
|
-
resolvedUrl ?? ""
|
|
1518
|
-
]);
|
|
1519
|
-
let project = syntheticProjects.get(key);
|
|
1520
|
-
if (!project) {
|
|
1521
|
-
project = Object.freeze({
|
|
1522
|
-
projectId,
|
|
1523
|
-
proxyPath: resolvedProxyPath,
|
|
1524
|
-
url: resolvedUrl
|
|
1525
|
-
});
|
|
1526
|
-
if (syntheticProjects.size >= MAX_SYNTHETIC_PROJECTS) {
|
|
1527
|
-
const oldest = syntheticProjects.keys().next().value;
|
|
1528
|
-
if (oldest !== void 0) syntheticProjects.delete(oldest);
|
|
1529
|
-
}
|
|
1530
|
-
syntheticProjects.set(key, project);
|
|
1531
|
-
}
|
|
1532
|
-
return project;
|
|
1533
|
-
}
|
|
1775
|
+
const getCompleteClaimUrl = (projectId) => {
|
|
1776
|
+
return `${getProxyPath()}/projects/${projectId}/claim/complete`;
|
|
1777
|
+
};
|
|
1534
1778
|
/**
|
|
1535
|
-
*
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
*
|
|
1541
|
-
*
|
|
1542
|
-
* Both JS paths (property and global) win over the declarative attributes, so a
|
|
1543
|
-
* deliberate `configureZitadel()` is never silently overridden by fallback or
|
|
1544
|
-
* stale HTML attributes; the attributes are the no-JS fallback.
|
|
1545
|
-
*
|
|
1546
|
-
* Both `<zitadel-login>` and `<zitadel-logout>` need the same resolution and
|
|
1547
|
-
* the same "no config" failure, so it lives here rather than being copied
|
|
1548
|
-
* into each element. Callers run this inside their try/catch so a missing
|
|
1549
|
-
* configuration surfaces through the element's normal error path rather than
|
|
1550
|
-
* as an unhandled rejection.
|
|
1551
|
-
*
|
|
1552
|
-
* @param project Optional per-element override handle.
|
|
1553
|
-
* @param attrs Declarative config read from the element's attributes.
|
|
1554
|
-
* @param element Tag name used in the thrown error (e.g. `<zitadel-login>`).
|
|
1779
|
+
* Called by the browser after the developer authenticates on the claim page.
|
|
1780
|
+
Authenticated by the `__nextgen_session` cookie, it attaches the project to
|
|
1781
|
+
the developer's personal team using the `challenge_id` from the claim URL as
|
|
1782
|
+
its single-use, browser-safe authorization.
|
|
1783
|
+
|
|
1784
|
+
* @summary Complete a project claim
|
|
1555
1785
|
*/
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1786
|
+
const completeClaim = async (projectId, completeClaimBody, options) => {
|
|
1787
|
+
return customFetch(getCompleteClaimUrl(projectId), {
|
|
1788
|
+
...options,
|
|
1789
|
+
method: "POST",
|
|
1790
|
+
headers: {
|
|
1791
|
+
"Content-Type": "application/json",
|
|
1792
|
+
...options?.headers
|
|
1793
|
+
},
|
|
1794
|
+
body: JSON.stringify(completeClaimBody)
|
|
1795
|
+
});
|
|
1796
|
+
};
|
|
1797
|
+
const getCreateSessionUrl = () => {
|
|
1798
|
+
return `${getProxyPath()}/sessions`;
|
|
1569
1799
|
};
|
|
1570
|
-
//#endregion
|
|
1571
|
-
//#region src/orchestrator/branding-validator.ts
|
|
1572
1800
|
/**
|
|
1573
|
-
*
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1801
|
+
* Creates an anonymous session shell with no user and no factors (`state: building`).
|
|
1802
|
+
|
|
1803
|
+
This is optional — an `auth_attempt` will create a session implicitly if none is provided.
|
|
1804
|
+
Use this explicitly when you want to:
|
|
1805
|
+
- Pre-allocate a `session_id` before the user is known, so device/telemetry signals
|
|
1806
|
+
can be correlated with the eventual authenticated session from the start.
|
|
1807
|
+
- Track anonymous state (bot detection, device fingerprint) that survives until authentication.
|
|
1808
|
+
|
|
1809
|
+
Creating a session is an app-plane operation on the project credential
|
|
1810
|
+
(`session.write`). The returned `session_token` is a session credential for the
|
|
1811
|
+
end-user client, not a management scope: it is delivered as the
|
|
1812
|
+
`__nextgen_session` cookie and authorises the self-service operations
|
|
1813
|
+
`GET /sessions/me` and `DELETE /sessions/me` (`nextgenSession` scheme). The
|
|
1814
|
+
by-id operations `GET /sessions/{session_id}` and `DELETE /sessions/{session_id}`
|
|
1815
|
+
are operator endpoints and require `session.read` / `session.delete` instead.
|
|
1816
|
+
|
|
1817
|
+
The `session_token` is superseded when a handoff exchange completes — clients must
|
|
1818
|
+
replace it at that point.
|
|
1819
|
+
|
|
1820
|
+
Anonymous sessions expire aggressively (10-minute TTL). The TTL resets to the configured
|
|
1821
|
+
full session TTL when the first authentication factor is written via a completing `auth_attempt`.
|
|
1822
|
+
|
|
1823
|
+
* @summary Create an anonymous session shell
|
|
1586
1824
|
*/
|
|
1587
|
-
const
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
};
|
|
1607
|
-
return {
|
|
1608
|
-
branding: out,
|
|
1609
|
-
issues
|
|
1610
|
-
};
|
|
1611
|
-
}
|
|
1612
|
-
function sanitiseUrl(value, field, issues) {
|
|
1613
|
-
if (value == null || value === "") return;
|
|
1614
|
-
try {
|
|
1615
|
-
const parsed = new URL(value);
|
|
1616
|
-
if (parsed.protocol !== "https:") {
|
|
1617
|
-
issues.push(`${field} must use https (got "${parsed.protocol}") — dropped.`);
|
|
1618
|
-
return;
|
|
1619
|
-
}
|
|
1620
|
-
return parsed.toString();
|
|
1621
|
-
} catch {
|
|
1622
|
-
issues.push(`${field} is not a valid URL — dropped.`);
|
|
1623
|
-
return;
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
//#endregion
|
|
1627
|
-
//#region src/orchestrator/exportparts.ts
|
|
1825
|
+
const createSession = async (createSessionBody, options) => {
|
|
1826
|
+
return customFetch(getCreateSessionUrl(), {
|
|
1827
|
+
...options,
|
|
1828
|
+
method: "POST",
|
|
1829
|
+
headers: {
|
|
1830
|
+
"Content-Type": "application/json",
|
|
1831
|
+
...options?.headers
|
|
1832
|
+
},
|
|
1833
|
+
body: JSON.stringify(createSessionBody)
|
|
1834
|
+
});
|
|
1835
|
+
};
|
|
1836
|
+
const getQuerySessionsUrl = (params) => {
|
|
1837
|
+
const normalizedParams = new URLSearchParams();
|
|
1838
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1839
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1840
|
+
});
|
|
1841
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1842
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions/query?${stringifiedParams}` : `${getProxyPath()}/sessions/query`;
|
|
1843
|
+
};
|
|
1628
1844
|
/**
|
|
1629
|
-
*
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
*
|
|
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.
|
|
1639
|
-
*
|
|
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.
|
|
1845
|
+
* Returns the sessions of a project, paginated with a cursor.
|
|
1846
|
+
Sessions of every lifecycle state are returned; each carries its `state`.
|
|
1847
|
+
Requires `session.read` permission.
|
|
1848
|
+
|
|
1849
|
+
* @summary Query sessions
|
|
1645
1850
|
*/
|
|
1646
|
-
const
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1851
|
+
const querySessions = async (querySessionsBody, params, options) => {
|
|
1852
|
+
return customFetch(getQuerySessionsUrl(params), {
|
|
1853
|
+
...options,
|
|
1854
|
+
method: "POST",
|
|
1855
|
+
headers: {
|
|
1856
|
+
"Content-Type": "application/json",
|
|
1857
|
+
...options?.headers
|
|
1858
|
+
},
|
|
1859
|
+
body: JSON.stringify(querySessionsBody)
|
|
1860
|
+
});
|
|
1861
|
+
};
|
|
1862
|
+
const getExchangeHandoffUrl = (params) => {
|
|
1863
|
+
const normalizedParams = new URLSearchParams();
|
|
1864
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1865
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1866
|
+
});
|
|
1867
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1868
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions/exchange?${stringifiedParams}` : `${getProxyPath()}/sessions/exchange`;
|
|
1869
|
+
};
|
|
1651
1870
|
/**
|
|
1652
|
-
*
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1871
|
+
* Consumes a one-time `handoff_token` minted by `POST /auth_attempts/{id}/handoff`
|
|
1872
|
+
and returns the resulting session and a `session_token`.
|
|
1873
|
+
|
|
1874
|
+
The server resolves the originating `auth_attempt` from the token and then:
|
|
1875
|
+
|
|
1876
|
+
| Originating auth_attempt | Outcome |
|
|
1877
|
+
|---|---|
|
|
1878
|
+
| No `session_id` | A new authenticated session is **created**. |
|
|
1879
|
+
| `session_id` points to an anonymous shell | Existing session is **upgraded** — user and factors written in, TTL reset to full session TTL. |
|
|
1880
|
+
| `session_id` points to an active session (step-up) | Existing session is **upgraded** — new factors merged, `assurance_levels[]` expanded. |
|
|
1881
|
+
|
|
1882
|
+
The response shape is identical in all three cases.
|
|
1883
|
+
|
|
1884
|
+
The `session_token` supersedes any previously issued `session_token` for the same session.
|
|
1885
|
+
Clients must replace their stored token at this point.
|
|
1886
|
+
|
|
1887
|
+
Requires a project service key (OAuth2 client credentials).
|
|
1888
|
+
|
|
1889
|
+
* @summary Exchange handoff token for a session
|
|
1656
1890
|
*/
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1891
|
+
const exchangeHandoff = async (exchangeHandoffBody, params, options) => {
|
|
1892
|
+
return customFetch(getExchangeHandoffUrl(params), {
|
|
1893
|
+
...options,
|
|
1894
|
+
method: "POST",
|
|
1895
|
+
headers: {
|
|
1896
|
+
"Content-Type": "application/json",
|
|
1897
|
+
...options?.headers
|
|
1898
|
+
},
|
|
1899
|
+
body: JSON.stringify(exchangeHandoffBody)
|
|
1900
|
+
});
|
|
1901
|
+
};
|
|
1902
|
+
const getGetSessionUrl = (sessionId) => {
|
|
1903
|
+
return `${getProxyPath()}/sessions/${sessionId}`;
|
|
1904
|
+
};
|
|
1662
1905
|
/**
|
|
1663
|
-
*
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1906
|
+
* Returns the current state of a session including its factors and all currently
|
|
1907
|
+
satisfied assurance levels.
|
|
1908
|
+
|
|
1909
|
+
`assurance_levels[]` may shrink over time as factor freshness windows expire,
|
|
1910
|
+
without the session itself expiring. Use step-up authentication (a new `auth_attempt`
|
|
1911
|
+
against the same `session_id`) to restore a dropped assurance level.
|
|
1912
|
+
|
|
1913
|
+
* @summary Get session state
|
|
1914
|
+
*/
|
|
1915
|
+
const getSession = async (sessionId, options) => {
|
|
1916
|
+
return customFetch(getGetSessionUrl(sessionId), {
|
|
1917
|
+
...options,
|
|
1918
|
+
method: "GET"
|
|
1919
|
+
});
|
|
1920
|
+
};
|
|
1921
|
+
const getRevokeSessionUrl = (sessionId) => {
|
|
1922
|
+
return `${getProxyPath()}/sessions/${sessionId}`;
|
|
1923
|
+
};
|
|
1924
|
+
/**
|
|
1925
|
+
* Permanently deletes the session, terminating it immediately.
|
|
1926
|
+
|
|
1927
|
+
This is the operator revoke path and requires the `session.delete` scope on a
|
|
1928
|
+
project-bound credential. End-user logout with the `__nextgen_session` cookie is
|
|
1929
|
+
`DELETE /sessions/me` (`nextgenSession` scheme).
|
|
1930
|
+
|
|
1931
|
+
Idempotent: deleting a session that does not exist (or was already deleted)
|
|
1932
|
+
still returns 204. After deletion, any tokens derived from the session are
|
|
1933
|
+
invalidated.
|
|
1934
|
+
|
|
1935
|
+
* @summary Revoke session
|
|
1669
1936
|
*/
|
|
1670
|
-
|
|
1671
|
-
return
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
function patchMandatoryGates(html, step, locale) {
|
|
1680
|
-
const template = document.createElement("template");
|
|
1681
|
-
template.innerHTML = html;
|
|
1682
|
-
const fragment = template.content;
|
|
1683
|
-
const additions = collectMissingAtoms(fragment, step, locale);
|
|
1684
|
-
const marker = findMarkerComment(fragment);
|
|
1685
|
-
if (marker?.parentNode) {
|
|
1686
|
-
for (const node of additions) marker.parentNode.insertBefore(node, marker);
|
|
1687
|
-
marker.remove();
|
|
1688
|
-
} else for (const node of additions) fragment.appendChild(node);
|
|
1689
|
-
return template.innerHTML;
|
|
1690
|
-
}
|
|
1691
|
-
function collectMissingAtoms(fragment, step, locale) {
|
|
1692
|
-
const additions = [];
|
|
1693
|
-
if (step.fields) for (const field of step.fields) {
|
|
1694
|
-
if (!field.required) continue;
|
|
1695
|
-
if (hasFieldFor(fragment, field.name)) continue;
|
|
1696
|
-
additions.push(buildField(field.name, field.text_key, field.type, locale, field.required));
|
|
1697
|
-
}
|
|
1698
|
-
if (step.actions && !hasPrimaryButton(fragment)) {
|
|
1699
|
-
const primary = step.actions.find((action) => action.primary);
|
|
1700
|
-
if (primary) additions.push(buildSubmit(primary.name, primary.text_key, locale));
|
|
1701
|
-
}
|
|
1702
|
-
return additions;
|
|
1703
|
-
}
|
|
1704
|
-
function hasPrimaryButton(fragment) {
|
|
1705
|
-
return Boolean(fragment.querySelector("zl-button[hierarchy=\"primary\"]"));
|
|
1706
|
-
}
|
|
1707
|
-
function hasFieldFor(fragment, name) {
|
|
1708
|
-
for (const field of fragment.querySelectorAll(FIELD_ATOM_TAGS)) if (field.getAttribute("name") === name) return true;
|
|
1709
|
-
return false;
|
|
1710
|
-
}
|
|
1711
|
-
function findMarkerComment(fragment) {
|
|
1712
|
-
const walker = (fragment.ownerDocument ?? document).createTreeWalker(fragment, NodeFilter.SHOW_COMMENT);
|
|
1713
|
-
let node = walker.nextNode();
|
|
1714
|
-
while (node) {
|
|
1715
|
-
if (node.nodeValue?.trim() === "ZL_MANDATORY_GATES") return node;
|
|
1716
|
-
node = walker.nextNode();
|
|
1717
|
-
}
|
|
1718
|
-
return null;
|
|
1719
|
-
}
|
|
1720
|
-
function buildField(name, textKey, type, locale, required) {
|
|
1721
|
-
const el = document.createElement("zl-field");
|
|
1722
|
-
el.setAttribute("name", name);
|
|
1723
|
-
el.setAttribute("label", lookup(locale, textKey ?? name));
|
|
1724
|
-
el.setAttribute("type", type);
|
|
1725
|
-
if (required) el.setAttribute("required", "");
|
|
1726
|
-
return el;
|
|
1727
|
-
}
|
|
1728
|
-
function buildSubmit(name, textKey, locale) {
|
|
1729
|
-
const el = document.createElement("zl-button");
|
|
1730
|
-
el.setAttribute("hierarchy", "primary");
|
|
1731
|
-
el.setAttribute("size", "medium");
|
|
1732
|
-
el.setAttribute("type", "submit");
|
|
1733
|
-
el.setAttribute("block", "");
|
|
1734
|
-
el.setAttribute("action", name);
|
|
1735
|
-
el.setAttribute("label", lookup(locale, textKey ?? "submit.continue"));
|
|
1736
|
-
return el;
|
|
1737
|
-
}
|
|
1738
|
-
function lookup(locale, key) {
|
|
1739
|
-
return locale[key] ?? key;
|
|
1740
|
-
}
|
|
1741
|
-
//#endregion
|
|
1742
|
-
//#region src/orchestrator/liquid.ts
|
|
1937
|
+
const revokeSession = async (sessionId, options) => {
|
|
1938
|
+
return customFetch(getRevokeSessionUrl(sessionId), {
|
|
1939
|
+
...options,
|
|
1940
|
+
method: "DELETE"
|
|
1941
|
+
});
|
|
1942
|
+
};
|
|
1943
|
+
const getGetMySessionUrl = () => {
|
|
1944
|
+
return `${getProxyPath()}/sessions/me`;
|
|
1945
|
+
};
|
|
1743
1946
|
/**
|
|
1744
|
-
*
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
*
|
|
1752
|
-
*
|
|
1753
|
-
* - `{{ }}` output is HTML-escaped (LiquidJS default since v10).
|
|
1754
|
-
* - The `| raw` filter is overridden to a no-op string passthrough that still
|
|
1755
|
-
* escapes (Layer 1 of the security pipeline).
|
|
1756
|
-
* - Partials are loaded from an in-memory map; no filesystem access.
|
|
1947
|
+
* Returns the current state of the current session including its factors and all currently
|
|
1948
|
+
satisfied assurance levels.
|
|
1949
|
+
|
|
1950
|
+
`assurance_levels[]` may shrink over time as factor freshness windows expire,
|
|
1951
|
+
without the session itself expiring. Use step-up authentication (a new `auth_attempt`
|
|
1952
|
+
against the same `session_id`) to restore a dropped assurance level.
|
|
1953
|
+
|
|
1954
|
+
* @summary Get my session state
|
|
1757
1955
|
*/
|
|
1758
|
-
const
|
|
1956
|
+
const getMySession = async (options) => {
|
|
1957
|
+
return customFetch(getGetMySessionUrl(), {
|
|
1958
|
+
...options,
|
|
1959
|
+
method: "GET"
|
|
1960
|
+
});
|
|
1961
|
+
};
|
|
1962
|
+
const getRevokeMySessionUrl = () => {
|
|
1963
|
+
return `${getProxyPath()}/sessions/me`;
|
|
1964
|
+
};
|
|
1759
1965
|
/**
|
|
1760
|
-
*
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
*
|
|
1768
|
-
* fieldless banner message.
|
|
1966
|
+
* Logs out by permanently deleting the session.
|
|
1967
|
+
|
|
1968
|
+
The `__nextgen_session` cookie issued at creation (or superseded by a handoff
|
|
1969
|
+
exchange) is required. Idempotent: if the session is already gone this still
|
|
1970
|
+
returns 204. Any tokens derived from the session are invalidated, and the
|
|
1971
|
+
cookie itself is cleared in the response.
|
|
1972
|
+
|
|
1973
|
+
* @summary Revoke my session
|
|
1769
1974
|
*/
|
|
1770
|
-
const
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
"error.password_incorrect": "password",
|
|
1776
|
-
"error.invalid_credentials": "password"
|
|
1975
|
+
const revokeMySession = async (options) => {
|
|
1976
|
+
return customFetch(getRevokeMySessionUrl(), {
|
|
1977
|
+
...options,
|
|
1978
|
+
method: "DELETE"
|
|
1979
|
+
});
|
|
1777
1980
|
};
|
|
1778
|
-
|
|
1779
|
-
const
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
...options.templates ?? {}
|
|
1783
|
-
},
|
|
1784
|
-
cache: true,
|
|
1785
|
-
jsTruthy: true,
|
|
1786
|
-
relativeReference: false,
|
|
1787
|
-
strictFilters: false,
|
|
1788
|
-
strictVariables: false,
|
|
1789
|
-
outputEscape: "escape"
|
|
1981
|
+
const getCreateSchemaUrl = (params) => {
|
|
1982
|
+
const normalizedParams = new URLSearchParams();
|
|
1983
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
1984
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1790
1985
|
});
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1986
|
+
const stringifiedParams = normalizedParams.toString();
|
|
1987
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/schemas?${stringifiedParams}` : `${getProxyPath()}/schemas`;
|
|
1988
|
+
};
|
|
1989
|
+
/**
|
|
1990
|
+
* Create a new schema. The optional `$id` field is the JSON Schema document
|
|
1991
|
+
URI used to identify the schema in future requests (GitOps-stable identity,
|
|
1992
|
+
not a free-form resource primary key). When `$id` is omitted, the server
|
|
1993
|
+
generates a `sch_*` URL. When provided, `$id` must be unique within the
|
|
1994
|
+
project and should ideally be a valid URI pointing at where the schema can
|
|
1995
|
+
be accessed.
|
|
1996
|
+
|
|
1997
|
+
The schema can either be a concrete schema, e.g. a user schema, or a
|
|
1998
|
+
schema-url which will be resolved by the server.
|
|
1999
|
+
|
|
2000
|
+
* @summary Create new schema
|
|
2001
|
+
*/
|
|
2002
|
+
const createSchema = async (createSchemaBody, params, options) => {
|
|
2003
|
+
return customFetch(getCreateSchemaUrl(params), {
|
|
2004
|
+
...options,
|
|
2005
|
+
method: "POST",
|
|
2006
|
+
headers: {
|
|
2007
|
+
"Content-Type": "application/json",
|
|
2008
|
+
...options?.headers
|
|
2009
|
+
},
|
|
2010
|
+
body: JSON.stringify(createSchemaBody)
|
|
1804
2011
|
});
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
2012
|
+
};
|
|
2013
|
+
const getListSchemasUrl = (params) => {
|
|
2014
|
+
const normalizedParams = new URLSearchParams();
|
|
2015
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
2016
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1809
2017
|
});
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
2018
|
+
const stringifiedParams = normalizedParams.toString();
|
|
2019
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/schemas?${stringifiedParams}` : `${getProxyPath()}/schemas`;
|
|
2020
|
+
};
|
|
2021
|
+
/**
|
|
2022
|
+
* Retrieve a list of all schemas available in the system. This endpoint
|
|
2023
|
+
supports pagination and filtering based on schema attributes.
|
|
2024
|
+
|
|
2025
|
+
* @summary List all schemas
|
|
2026
|
+
*/
|
|
2027
|
+
const listSchemas = async (params, options) => {
|
|
2028
|
+
return customFetch(getListSchemasUrl(params), {
|
|
2029
|
+
...options,
|
|
2030
|
+
method: "GET"
|
|
1814
2031
|
});
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
*/
|
|
1821
|
-
engine.registerFilter("selectOptions", (values) => {
|
|
1822
|
-
if (!Array.isArray(values)) return [];
|
|
1823
|
-
return values.map((value) => {
|
|
1824
|
-
const v = stringify(value);
|
|
1825
|
-
return {
|
|
1826
|
-
value: v,
|
|
1827
|
-
label: v
|
|
1828
|
-
};
|
|
1829
|
-
});
|
|
2032
|
+
};
|
|
2033
|
+
const getGetSchemaByIdUrl = (id, params) => {
|
|
2034
|
+
const normalizedParams = new URLSearchParams();
|
|
2035
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
2036
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1830
2037
|
});
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
2038
|
+
const stringifiedParams = normalizedParams.toString();
|
|
2039
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/schemas/${id}?${stringifiedParams}` : `${getProxyPath()}/schemas/${id}`;
|
|
2040
|
+
};
|
|
2041
|
+
/**
|
|
2042
|
+
* Get a schema by its ID. This will return the default revision of the schema.
|
|
2043
|
+
* @summary Get schema by ID
|
|
2044
|
+
*/
|
|
2045
|
+
const getSchemaById = async (id, params, options) => {
|
|
2046
|
+
return customFetch(getGetSchemaByIdUrl(id, params), {
|
|
2047
|
+
...options,
|
|
2048
|
+
method: "GET"
|
|
1835
2049
|
});
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2050
|
+
};
|
|
2051
|
+
const getCreateFlowDefinitionUrl = () => {
|
|
2052
|
+
return `${getProxyPath()}/flow_definitions`;
|
|
2053
|
+
};
|
|
2054
|
+
/**
|
|
2055
|
+
* Creates a new flow definition.
|
|
2056
|
+
Flow definitions are templates that define the sequence of steps (capabilities)
|
|
2057
|
+
for a particular user journey (e.g., registration, login, password reset).
|
|
2058
|
+
|
|
2059
|
+
Flow definitions are created based on the flow definition schema, which includes the flow's purpose, audience, and the steps involved.
|
|
2060
|
+
|
|
2061
|
+
* @summary Create a new flow definition
|
|
2062
|
+
*/
|
|
2063
|
+
const createFlowDefinition = async (createFlowDefinitionBody, options) => {
|
|
2064
|
+
return customFetch(getCreateFlowDefinitionUrl(), {
|
|
2065
|
+
...options,
|
|
2066
|
+
method: "POST",
|
|
2067
|
+
headers: {
|
|
2068
|
+
"Content-Type": "application/json",
|
|
2069
|
+
...options?.headers
|
|
2070
|
+
},
|
|
2071
|
+
body: JSON.stringify(createFlowDefinitionBody)
|
|
1840
2072
|
});
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
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 ?? "";
|
|
1849
|
-
}
|
|
1850
|
-
return "";
|
|
2073
|
+
};
|
|
2074
|
+
const getListFlowDefinitionsUrl = (params) => {
|
|
2075
|
+
const normalizedParams = new URLSearchParams();
|
|
2076
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
2077
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
1851
2078
|
});
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
*/
|
|
1866
|
-
engine.registerFilter("formLevelError", (err) => {
|
|
1867
|
-
return !err?.field;
|
|
2079
|
+
const stringifiedParams = normalizedParams.toString();
|
|
2080
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions?${stringifiedParams}` : `${getProxyPath()}/flow_definitions`;
|
|
2081
|
+
};
|
|
2082
|
+
/**
|
|
2083
|
+
* Retrieves a list of all flow definitions.
|
|
2084
|
+
This endpoint can be used to view existing flow definitions and their configurations.
|
|
2085
|
+
|
|
2086
|
+
* @summary List flow definitions
|
|
2087
|
+
*/
|
|
2088
|
+
const listFlowDefinitions = async (params, options) => {
|
|
2089
|
+
return customFetch(getListFlowDefinitionsUrl(params), {
|
|
2090
|
+
...options,
|
|
2091
|
+
method: "GET"
|
|
1868
2092
|
});
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
2093
|
+
};
|
|
2094
|
+
const getGetFlowDefinitionUrl = (id) => {
|
|
2095
|
+
return `${getProxyPath()}/flow_definitions/${id}`;
|
|
2096
|
+
};
|
|
2097
|
+
/**
|
|
2098
|
+
* Get a flow definition by id
|
|
2099
|
+
* @summary Get a flow definition by id
|
|
2100
|
+
*/
|
|
2101
|
+
const getFlowDefinition = async (id, options) => {
|
|
2102
|
+
return customFetch(getGetFlowDefinitionUrl(id), {
|
|
2103
|
+
...options,
|
|
2104
|
+
method: "GET"
|
|
1874
2105
|
});
|
|
1875
|
-
|
|
1876
|
-
|
|
2106
|
+
};
|
|
2107
|
+
const getUpdateFlowDefinitionUrl = (id) => {
|
|
2108
|
+
return `${getProxyPath()}/flow_definitions/${id}`;
|
|
2109
|
+
};
|
|
1877
2110
|
/**
|
|
1878
|
-
*
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
*
|
|
1882
|
-
* to its generic entry instead of leaking the raw key into the UI.
|
|
2111
|
+
* Update a flow definition by id. This endpoint replaces the existing flow definition.
|
|
2112
|
+
If `flow_definition.status` is omitted, the current status is preserved
|
|
2113
|
+
|
|
2114
|
+
* @summary Update a flow definition by id
|
|
1883
2115
|
*/
|
|
1884
|
-
const
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
2116
|
+
const updateFlowDefinition = async (id, updateFlowDefinitionBody, options) => {
|
|
2117
|
+
return customFetch(getUpdateFlowDefinitionUrl(id), {
|
|
2118
|
+
...options,
|
|
2119
|
+
method: "PUT",
|
|
2120
|
+
headers: {
|
|
2121
|
+
"Content-Type": "application/json",
|
|
2122
|
+
...options?.headers
|
|
2123
|
+
},
|
|
2124
|
+
body: JSON.stringify(updateFlowDefinitionBody)
|
|
2125
|
+
});
|
|
2126
|
+
};
|
|
2127
|
+
const getDeleteFlowDefinitionUrl = (id) => {
|
|
2128
|
+
return `${getProxyPath()}/flow_definitions/${id}`;
|
|
2129
|
+
};
|
|
1891
2130
|
/**
|
|
1892
|
-
*
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
*
|
|
1897
|
-
* form. Sub-keys like `.placeholder`/`.help` are excluded — they resolve
|
|
1898
|
-
* through their own filters, which stay empty on a miss.
|
|
2131
|
+
* Delete a flow definition by id.
|
|
2132
|
+
If the flow definition is currently being used by a flow, the deletion will fail.
|
|
2133
|
+
If the flow definition is the last active flow definition for a given purpose, the deletion will fail to prevent disruption of new flows being started for that purpose.
|
|
2134
|
+
|
|
2135
|
+
* @summary Delete a flow definition by id
|
|
1899
2136
|
*/
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
2137
|
+
const deleteFlowDefinition = async (id, options) => {
|
|
2138
|
+
return customFetch(getDeleteFlowDefinitionUrl(id), {
|
|
2139
|
+
...options,
|
|
2140
|
+
method: "DELETE"
|
|
2141
|
+
});
|
|
2142
|
+
};
|
|
2143
|
+
const getCreateTeamUrl = (params) => {
|
|
2144
|
+
const normalizedParams = new URLSearchParams();
|
|
2145
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
2146
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
2147
|
+
});
|
|
2148
|
+
const stringifiedParams = normalizedParams.toString();
|
|
2149
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams?${stringifiedParams}` : `${getProxyPath()}/teams`;
|
|
2150
|
+
};
|
|
1907
2151
|
/**
|
|
1908
|
-
*
|
|
1909
|
-
* (`error.<field>_<rule>` — see `FlowFieldValidationError.TextKey` in
|
|
1910
|
-
* `internal/domain/flow_field_resolver.go`). Field names come from the
|
|
1911
|
-
* tenant's user schema, so no catalog can enumerate the specific keys;
|
|
1912
|
-
* a miss resolves to the rule's generic entry, interpolated with the
|
|
1913
|
-
* field's label (`{0}`). The server spells the format rule `_invalid`
|
|
1914
|
-
* (the catalog's existing convention, e.g. `error.email_invalid`), so
|
|
1915
|
-
* that suffix takes the format wording; `_unknown_field` (a submitted
|
|
1916
|
-
* name that is not a step field) takes the catch-all.
|
|
2152
|
+
* @summary Create team
|
|
1917
2153
|
*/
|
|
1918
|
-
const
|
|
1919
|
-
{
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
{
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
{
|
|
1936
|
-
|
|
1937
|
-
generic: "error.field_format"
|
|
1938
|
-
},
|
|
1939
|
-
{
|
|
1940
|
-
suffix: "_unknown_field",
|
|
1941
|
-
generic: "error.field_invalid"
|
|
1942
|
-
}
|
|
1943
|
-
];
|
|
1944
|
-
const FLOW_ERROR_CATCH_ALL_KEY = "error.field_invalid";
|
|
2154
|
+
const createTeam = async (createTeamBody, params, options) => {
|
|
2155
|
+
return customFetch(getCreateTeamUrl(params), {
|
|
2156
|
+
...options,
|
|
2157
|
+
method: "POST",
|
|
2158
|
+
headers: {
|
|
2159
|
+
"Content-Type": "application/json",
|
|
2160
|
+
...options?.headers
|
|
2161
|
+
},
|
|
2162
|
+
body: JSON.stringify(createTeamBody)
|
|
2163
|
+
});
|
|
2164
|
+
};
|
|
2165
|
+
const getQueryTeamsUrl = (params) => {
|
|
2166
|
+
const normalizedParams = new URLSearchParams();
|
|
2167
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
2168
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
2169
|
+
});
|
|
2170
|
+
const stringifiedParams = normalizedParams.toString();
|
|
2171
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/query?${stringifiedParams}` : `${getProxyPath()}/teams/query`;
|
|
2172
|
+
};
|
|
1945
2173
|
/**
|
|
1946
|
-
*
|
|
1947
|
-
|
|
1948
|
-
*
|
|
1949
|
-
* Returns `null` unless EVERY `"; "`-joined segment is an `error.*` key
|
|
1950
|
-
* — the caller keeps other payloads (outcome tokens such as
|
|
1951
|
-
* `user_not_found`) verbatim.
|
|
1952
|
-
*
|
|
1953
|
-
* Keys the locale knows pass through as `text_key` entries: the template
|
|
1954
|
-
* localises them via `| t`, and `fieldErrorKeys` routes the known ones
|
|
1955
|
-
* inline to their field. Unknown keys with a recognised rule suffix are
|
|
1956
|
-
* pre-localised here from their generic {@link FLOW_ERROR_RULE_FALLBACKS}
|
|
1957
|
-
* entry; unknown keys without one pass through as `text_key` (matching
|
|
1958
|
-
* `| t`'s behaviour for non-validation keys such as
|
|
1959
|
-
* `error.sign_in_server`, which localises via `.title`/`.body`).
|
|
2174
|
+
* Returns the teams of a project, paginated with a cursor.
|
|
2175
|
+
|
|
2176
|
+
* @summary Query teams
|
|
1960
2177
|
*/
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
const field = key.slice(6, key.length - suffix.length);
|
|
1976
|
-
if (field === "") break;
|
|
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 };
|
|
1982
|
-
}
|
|
1983
|
-
if (orphanedInline && ctx.locale[key] !== void 0) return { message: ctx.locale[key] };
|
|
1984
|
-
return { text_key: key };
|
|
1985
|
-
}
|
|
1986
|
-
/** `<step>.field.<name>` from the locale, else a humanised field name. */
|
|
1987
|
-
function fieldLabel(ctx, field) {
|
|
1988
|
-
return ctx.locale[`${ctx.stepName}.field.${field}`] ?? humaniseFieldName(field);
|
|
1989
|
-
}
|
|
2178
|
+
const queryTeams = async (queryTeamsBody, params, options) => {
|
|
2179
|
+
return customFetch(getQueryTeamsUrl(params), {
|
|
2180
|
+
...options,
|
|
2181
|
+
method: "POST",
|
|
2182
|
+
headers: {
|
|
2183
|
+
"Content-Type": "application/json",
|
|
2184
|
+
...options?.headers
|
|
2185
|
+
},
|
|
2186
|
+
body: JSON.stringify(queryTeamsBody)
|
|
2187
|
+
});
|
|
2188
|
+
};
|
|
2189
|
+
const getGetTeamUrl = (teamId) => {
|
|
2190
|
+
return `${getProxyPath()}/teams/${teamId}`;
|
|
2191
|
+
};
|
|
1990
2192
|
/**
|
|
1991
|
-
*
|
|
1992
|
-
|
|
2193
|
+
* Returns a Team by its id.
|
|
2194
|
+
|
|
2195
|
+
* @summary Get team
|
|
1993
2196
|
*/
|
|
1994
|
-
|
|
1995
|
-
return (
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
if (typeof value === "string") return value;
|
|
2004
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
2005
|
-
try {
|
|
2006
|
-
return JSON.stringify(value);
|
|
2007
|
-
} catch {
|
|
2008
|
-
return String(value);
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2197
|
+
const getTeam = async (teamId, options) => {
|
|
2198
|
+
return customFetch(getGetTeamUrl(teamId), {
|
|
2199
|
+
...options,
|
|
2200
|
+
method: "GET"
|
|
2201
|
+
});
|
|
2202
|
+
};
|
|
2203
|
+
const getDeleteTeamUrl = (teamId) => {
|
|
2204
|
+
return `${getProxyPath()}/teams/${teamId}`;
|
|
2205
|
+
};
|
|
2011
2206
|
/**
|
|
2012
|
-
*
|
|
2013
|
-
|
|
2014
|
-
|
|
2207
|
+
* Deactivates the team.
|
|
2208
|
+
|
|
2209
|
+
The team is tombstoned rather than erased: it stays readable through
|
|
2210
|
+
getTeam with status `deactivated`. Its memberships are removed and the
|
|
2211
|
+
users whose lifecycle it owns are deactivated with it.
|
|
2212
|
+
|
|
2213
|
+
The request is idempotent. Deleting a team that is already deactivated
|
|
2214
|
+
or doesn't exist succeeds without changing anything.
|
|
2215
|
+
|
|
2216
|
+
* @summary Delete team
|
|
2015
2217
|
*/
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2218
|
+
const deleteTeam = async (teamId, options) => {
|
|
2219
|
+
return customFetch(getDeleteTeamUrl(teamId), {
|
|
2220
|
+
...options,
|
|
2221
|
+
method: "DELETE"
|
|
2020
2222
|
});
|
|
2021
|
-
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2223
|
+
};
|
|
2224
|
+
const getUpdateTeamUrl = (teamId) => {
|
|
2225
|
+
return `${getProxyPath()}/teams/${teamId}`;
|
|
2226
|
+
};
|
|
2024
2227
|
/**
|
|
2025
|
-
*
|
|
2026
|
-
|
|
2027
|
-
*
|
|
2028
|
-
* This lives in its own module — separate from `liquid.ts` — so the public
|
|
2029
|
-
* barrel can re-export `TEMPLATE_NAMES` without dragging LiquidJS' `Liquid`
|
|
2030
|
-
* type (and its Node ambient-type requirements) into the published
|
|
2031
|
-
* declaration surface. See `liquid.ts` / the orchestrator barrel.
|
|
2228
|
+
* Update team. Only active teams can be updated.
|
|
2229
|
+
|
|
2230
|
+
* @summary Update team
|
|
2032
2231
|
*/
|
|
2033
|
-
const
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2232
|
+
const updateTeam = async (teamId, updateTeamBody, options) => {
|
|
2233
|
+
return customFetch(getUpdateTeamUrl(teamId), {
|
|
2234
|
+
...options,
|
|
2235
|
+
method: "PATCH",
|
|
2236
|
+
headers: {
|
|
2237
|
+
"Content-Type": "application/json",
|
|
2238
|
+
...options?.headers
|
|
2239
|
+
},
|
|
2240
|
+
body: JSON.stringify(updateTeamBody)
|
|
2241
|
+
});
|
|
2242
|
+
};
|
|
2243
|
+
const getCreateBrandingUrl = (params) => {
|
|
2244
|
+
const normalizedParams = new URLSearchParams();
|
|
2245
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
2246
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
2247
|
+
});
|
|
2248
|
+
const stringifiedParams = normalizedParams.toString();
|
|
2249
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/branding?${stringifiedParams}` : `${getProxyPath()}/branding`;
|
|
2038
2250
|
};
|
|
2039
|
-
//#endregion
|
|
2040
|
-
//#region src/orchestrator/locales/en.ts
|
|
2041
2251
|
/**
|
|
2042
|
-
*
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
*
|
|
2053
|
-
*
|
|
2054
|
-
* Copy aligned to Figma screens file `xkvBjkOJ8ENuHdTGZHXezK` (May 2026).
|
|
2055
|
-
* MVP only — multi-locale support is deferred.
|
|
2252
|
+
* Publishes a new immutable branding revision for the project. Branding
|
|
2253
|
+
revisions cannot be updated or deleted; every edit publishes a new
|
|
2254
|
+
revision, and flow responses resolve the latest revision per project
|
|
2255
|
+
(see ADR 040).
|
|
2256
|
+
|
|
2257
|
+
The `liquid_template` is validated lexically on save (size, encoding,
|
|
2258
|
+
banned patterns such as `<script>` tags, inline event handlers, and the
|
|
2259
|
+
`| raw` filter). Authoritative LiquidJS validation runs at authoring
|
|
2260
|
+
time via `zitadel plan` / `zitadel apply`.
|
|
2261
|
+
|
|
2262
|
+
* @summary Publish a new branding revision
|
|
2056
2263
|
*/
|
|
2057
|
-
const
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
"passkey-upsell.description.line2": "Sign in with Face ID, Touch ID, or PIN.",
|
|
2091
|
-
"passkey-upsell.action.passkey_register": "Set up passkey",
|
|
2092
|
-
"passkey-upsell.action.skip": "Skip for now",
|
|
2093
|
-
"passkey-upsell.action.setup": "Set up passkey",
|
|
2094
|
-
"collect-passkey-email.title": "Create your account",
|
|
2095
|
-
"collect-passkey-email.description": "Enter your email to set up a passkey",
|
|
2096
|
-
"collect-passkey-email.field.email": "Email",
|
|
2097
|
-
"collect-passkey-email.field.email.placeholder": "you@example.com",
|
|
2098
|
-
"collect-passkey-email.action.submit": "Continue",
|
|
2099
|
-
"passkey-enroll.title": "Set up your passkey",
|
|
2100
|
-
"passkey-enroll.description": "Use Face ID, Touch ID, or your device PIN to create a passkey.",
|
|
2101
|
-
"passkey-enroll.action.passkey_register": "Set up passkey",
|
|
2102
|
-
"done.title": "You're signed in as",
|
|
2103
|
-
"done.description": "",
|
|
2104
|
-
"password.title": "Sign in",
|
|
2105
|
-
"password.description": "Enter your password",
|
|
2106
|
-
"password.field.password": "Password",
|
|
2107
|
-
"password.action.signin": "Sign in",
|
|
2108
|
-
"password.action.passkey": "Sign in with a passkey",
|
|
2109
|
-
"password.action.back": "Back",
|
|
2110
|
-
"password.action.register.lead": "Don't have an account? ",
|
|
2111
|
-
"password.action.register.link": "Sign up",
|
|
2112
|
-
"register.title": "Create your account",
|
|
2113
|
-
"register.description": "",
|
|
2114
|
-
"register.field.email": "Email",
|
|
2115
|
-
"register.field.email.placeholder": "you@example.com",
|
|
2116
|
-
"register.field.password": "Password",
|
|
2117
|
-
"register.field.givenName": "Given name",
|
|
2118
|
-
"register.field.familyName": "Family name",
|
|
2119
|
-
"register.field.companyName": "Company name",
|
|
2120
|
-
"register.field.dateOfBirth": "Date of birth",
|
|
2121
|
-
"register.field.country": "Country",
|
|
2122
|
-
"register.field.country.placeholder": "Select your country",
|
|
2123
|
-
"register.field.terms": "I agree to the terms of service",
|
|
2124
|
-
"register.action.password": "Continue with password",
|
|
2125
|
-
"register.action.passkey": "Continue with a passkey",
|
|
2126
|
-
"register.action.submit": "Sign up",
|
|
2127
|
-
"register.action.back": "Back",
|
|
2128
|
-
"register.action.sign_in.lead": "Already have an account? ",
|
|
2129
|
-
"register.action.sign_in.link": "Sign in",
|
|
2130
|
-
"complete.title": "You're signed in as",
|
|
2131
|
-
"signed-in.continue": "Continue",
|
|
2132
|
-
"signed-in.logout": "Sign out",
|
|
2133
|
-
"passkey-login.title": "Sign in with your passkey",
|
|
2134
|
-
"passkey-first.title": "Sign in",
|
|
2135
|
-
"passkey-first.description": "Use your passkey to continue",
|
|
2136
|
-
"passkey-first.action.passkey": "Continue with passkey",
|
|
2137
|
-
"passkey-first.action.email_fallback": "Use email instead",
|
|
2138
|
-
"recover.title": "Check your email",
|
|
2139
|
-
"recover.description": "We sent a password reset link to your email address.",
|
|
2140
|
-
"recover.action.back": "Back to sign in",
|
|
2141
|
-
"submit.continue": "Continue",
|
|
2142
|
-
"submit.signin": "Sign in",
|
|
2143
|
-
"action.forgot_password": "Forgot password?",
|
|
2144
|
-
"action.cancel": "Cancel",
|
|
2145
|
-
"action.back": "Back",
|
|
2146
|
-
"sso.redirect.title": "Redirecting to your provider…",
|
|
2147
|
-
"passkey.pending.status": "Waiting for your passkey…",
|
|
2148
|
-
"error.passkey_cancelled": "The passkey prompt was closed before completing.",
|
|
2149
|
-
"error.passkey_timeout": "The passkey request timed out. Please try again.",
|
|
2150
|
-
"error.passkey_not_registered": "This passkey is not registered. Please sign in with your email and password.",
|
|
2151
|
-
"error.passkey_setup_failed": "Passkey registration did not complete. Please try again.",
|
|
2152
|
-
"error.passkey_unsupported": "This device does not support passkeys",
|
|
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.",
|
|
2156
|
-
"error.email_required": "Please enter an email address",
|
|
2157
|
-
"error.email_invalid": "Please enter a valid email",
|
|
2158
|
-
"error.password_required": "Please enter a password",
|
|
2159
|
-
"error.password_incorrect": "Wrong email or password.",
|
|
2160
|
-
"error.email_exists": "An account with this email already exists.",
|
|
2161
|
-
/** Figma sign-in error `6602:180268` — inline on password field. */
|
|
2162
|
-
"error.invalid_credentials": "Wrong email or password.",
|
|
2163
|
-
"error.required": "This field is required.",
|
|
2164
|
-
"error.field_required": "{0} is required.",
|
|
2165
|
-
"error.field_format": "Please enter a valid {0}.",
|
|
2166
|
-
"error.field_min_length": "{0} is too short.",
|
|
2167
|
-
"error.field_max_length": "{0} is too long.",
|
|
2168
|
-
"error.field_invalid": "Please check {0}.",
|
|
2169
|
-
"error.sign_in_server.title": "We couldn't complete your sign in.",
|
|
2170
|
-
"error.sign_in_server.body": "Please try again in a few minutes"
|
|
2264
|
+
const createBranding = async (createBrandingBody, params, options) => {
|
|
2265
|
+
return customFetch(getCreateBrandingUrl(params), {
|
|
2266
|
+
...options,
|
|
2267
|
+
method: "POST",
|
|
2268
|
+
headers: {
|
|
2269
|
+
"Content-Type": "application/json",
|
|
2270
|
+
...options?.headers
|
|
2271
|
+
},
|
|
2272
|
+
body: JSON.stringify(createBrandingBody)
|
|
2273
|
+
});
|
|
2274
|
+
};
|
|
2275
|
+
const getListBrandingUrl = (params) => {
|
|
2276
|
+
const normalizedParams = new URLSearchParams();
|
|
2277
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
2278
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
2279
|
+
});
|
|
2280
|
+
const stringifiedParams = normalizedParams.toString();
|
|
2281
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/branding?${stringifiedParams}` : `${getProxyPath()}/branding`;
|
|
2282
|
+
};
|
|
2283
|
+
/**
|
|
2284
|
+
* Lists branding revisions for the project, newest first, capped at the
|
|
2285
|
+
100 most recent. The first entry is the revision flow responses
|
|
2286
|
+
currently resolve. Deliberately unpaginated in v1 — list endpoints
|
|
2287
|
+
gain a real query mechanism together (ADR 031); advertising pagination
|
|
2288
|
+
parameters the server ignores would be worse than none.
|
|
2289
|
+
|
|
2290
|
+
* @summary List branding revisions
|
|
2291
|
+
*/
|
|
2292
|
+
const listBranding = async (params, options) => {
|
|
2293
|
+
return customFetch(getListBrandingUrl(params), {
|
|
2294
|
+
...options,
|
|
2295
|
+
method: "GET"
|
|
2296
|
+
});
|
|
2171
2297
|
};
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
const de = {
|
|
2175
|
-
"identifier.title": "Anmelden",
|
|
2176
|
-
"identifier.description": "Gib deine E-Mail-Adresse ein, um fortzufahren",
|
|
2177
|
-
"identifier.field.email": "E-Mail",
|
|
2178
|
-
"identifier.field.email.placeholder": "du@beispiel.de",
|
|
2179
|
-
"identifier.field.password": "Passwort",
|
|
2180
|
-
"identifier.action.submit": "Anmelden",
|
|
2181
|
-
"identifier.action.continue": "Anmelden",
|
|
2182
|
-
"identifier.action.passkey": "Mit Passkey anmelden",
|
|
2183
|
-
"identifier.action.register.lead": "Noch kein Konto? ",
|
|
2184
|
-
"identifier.action.register.link": "Registrieren",
|
|
2185
|
-
"authenticate.title": "Anmelden",
|
|
2186
|
-
"authenticate.description": "Gib dein Passwort ein",
|
|
2187
|
-
"authenticate.field.password": "Passwort",
|
|
2188
|
-
"authenticate.action.submit": "Anmelden",
|
|
2189
|
-
"choose-register.title": "Konto erstellen",
|
|
2190
|
-
"choose-register.description": "Wähle, wie du dich registrieren möchtest",
|
|
2191
|
-
"choose-register.action.register-with-password": "Mit Passwort registrieren",
|
|
2192
|
-
"choose-register.action.register-with-passkey": "Mit Passkey registrieren",
|
|
2193
|
-
"collect-credentials.title": "Konto erstellen",
|
|
2194
|
-
"collect-credentials.description": "Richte E-Mail und Passwort ein",
|
|
2195
|
-
"collect-credentials.field.email": "E-Mail",
|
|
2196
|
-
"collect-credentials.field.email.placeholder": "du@beispiel.de",
|
|
2197
|
-
"collect-credentials.field.password": "Passwort",
|
|
2198
|
-
"collect-credentials.action.submit": "Registrieren",
|
|
2199
|
-
"register-password.title": "Passwort erstellen",
|
|
2200
|
-
"register-password.description": "Wähle ein sicheres Passwort für dein Konto",
|
|
2201
|
-
"register-password.field.password": "Passwort",
|
|
2202
|
-
"register-password.action.submit": "Registrieren",
|
|
2203
|
-
"register-password.action.back": "Zurück",
|
|
2204
|
-
"passkey-upsell.title": "Nächstes Mal schneller anmelden",
|
|
2205
|
-
"passkey-upsell.description": "Kein Passwort mehr nötig.",
|
|
2206
|
-
"passkey-upsell.description.line1": "Kein Passwort mehr nötig.",
|
|
2207
|
-
"passkey-upsell.description.line2": "Melde dich mit Face ID, Touch ID oder PIN an.",
|
|
2208
|
-
"passkey-upsell.action.passkey_register": "Passkey einrichten",
|
|
2209
|
-
"passkey-upsell.action.skip": "Vorerst überspringen",
|
|
2210
|
-
"passkey-upsell.action.setup": "Passkey einrichten",
|
|
2211
|
-
"collect-passkey-email.title": "Konto erstellen",
|
|
2212
|
-
"collect-passkey-email.description": "Gib deine E-Mail-Adresse ein, um einen Passkey einzurichten",
|
|
2213
|
-
"collect-passkey-email.field.email": "E-Mail",
|
|
2214
|
-
"collect-passkey-email.field.email.placeholder": "du@beispiel.de",
|
|
2215
|
-
"collect-passkey-email.action.submit": "Weiter",
|
|
2216
|
-
"passkey-enroll.title": "Passkey einrichten",
|
|
2217
|
-
"passkey-enroll.description": "Verwende Face ID, Touch ID oder die Geräte-PIN, um einen Passkey zu erstellen.",
|
|
2218
|
-
"passkey-enroll.action.passkey_register": "Passkey einrichten",
|
|
2219
|
-
"done.title": "Du bist angemeldet als",
|
|
2220
|
-
"done.description": "",
|
|
2221
|
-
"password.title": "Anmelden",
|
|
2222
|
-
"password.description": "Gib dein Passwort ein",
|
|
2223
|
-
"password.field.password": "Passwort",
|
|
2224
|
-
"password.action.signin": "Anmelden",
|
|
2225
|
-
"password.action.passkey": "Mit Passkey anmelden",
|
|
2226
|
-
"password.action.back": "Zurück",
|
|
2227
|
-
"password.action.register.lead": "Noch kein Konto? ",
|
|
2228
|
-
"password.action.register.link": "Registrieren",
|
|
2229
|
-
"register.title": "Konto erstellen",
|
|
2230
|
-
"register.description": "",
|
|
2231
|
-
"register.field.email": "E-Mail",
|
|
2232
|
-
"register.field.email.placeholder": "du@beispiel.de",
|
|
2233
|
-
"register.field.password": "Passwort",
|
|
2234
|
-
"register.field.givenName": "Vorname",
|
|
2235
|
-
"register.field.familyName": "Nachname",
|
|
2236
|
-
"register.field.companyName": "Firmenname",
|
|
2237
|
-
"register.field.dateOfBirth": "Geburtsdatum",
|
|
2238
|
-
"register.field.country": "Land",
|
|
2239
|
-
"register.field.country.placeholder": "Land auswählen",
|
|
2240
|
-
"register.field.terms": "Ich stimme den Nutzungsbedingungen zu",
|
|
2241
|
-
"register.action.password": "Mit Passwort fortfahren",
|
|
2242
|
-
"register.action.passkey": "Weiter mit Passkey",
|
|
2243
|
-
"register.action.submit": "Registrieren",
|
|
2244
|
-
"register.action.back": "Zurück",
|
|
2245
|
-
"register.action.sign_in.lead": "Bereits ein Konto? ",
|
|
2246
|
-
"register.action.sign_in.link": "Anmelden",
|
|
2247
|
-
"complete.title": "Du bist angemeldet als",
|
|
2248
|
-
"signed-in.continue": "Weiter",
|
|
2249
|
-
"signed-in.logout": "Abmelden",
|
|
2250
|
-
"passkey-login.title": "Mit Passkey anmelden",
|
|
2251
|
-
"passkey-first.title": "Anmelden",
|
|
2252
|
-
"passkey-first.description": "Verwende deinen Passkey, um fortzufahren",
|
|
2253
|
-
"passkey-first.action.passkey": "Weiter mit Passkey",
|
|
2254
|
-
"passkey-first.action.email_fallback": "Stattdessen E-Mail verwenden",
|
|
2255
|
-
"recover.title": "E-Mail prüfen",
|
|
2256
|
-
"recover.description": "Wir haben einen Link zum Zurücksetzen des Passworts an deine E-Mail-Adresse gesendet.",
|
|
2257
|
-
"recover.action.back": "Zurück zur Anmeldung",
|
|
2258
|
-
"submit.continue": "Weiter",
|
|
2259
|
-
"submit.signin": "Anmelden",
|
|
2260
|
-
"action.forgot_password": "Passwort vergessen?",
|
|
2261
|
-
"action.cancel": "Abbrechen",
|
|
2262
|
-
"action.back": "Zurück",
|
|
2263
|
-
"sso.redirect.title": "Weiterleitung zum Anbieter…",
|
|
2264
|
-
"passkey.pending.status": "Warten auf deinen Passkey…",
|
|
2265
|
-
"error.passkey_cancelled": "Die Passkey-Abfrage wurde vorzeitig geschlossen.",
|
|
2266
|
-
"error.passkey_timeout": "Die Passkey-Anfrage hat zu lange gedauert. Bitte versuche es erneut.",
|
|
2267
|
-
"error.passkey_not_registered": "Dieser Passkey ist nicht registriert. Bitte melde dich mit E-Mail und Passwort an.",
|
|
2268
|
-
"error.passkey_setup_failed": "Passkey-Registrierung wurde nicht abgeschlossen. Bitte versuche es erneut.",
|
|
2269
|
-
"error.passkey_unsupported": "Dieses Gerät unterstützt keine Passkeys",
|
|
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.",
|
|
2273
|
-
"error.email_required": "Bitte gib eine E-Mail-Adresse ein",
|
|
2274
|
-
"error.email_invalid": "Bitte gib eine gültige E-Mail-Adresse ein",
|
|
2275
|
-
"error.password_required": "Bitte gib ein Passwort ein",
|
|
2276
|
-
"error.password_incorrect": "Falsche E-Mail oder falsches Passwort.",
|
|
2277
|
-
"error.email_exists": "Ein Konto mit dieser E-Mail-Adresse existiert bereits.",
|
|
2278
|
-
"error.invalid_credentials": "Falsche E-Mail oder falsches Passwort.",
|
|
2279
|
-
"error.required": "Dieses Feld ist erforderlich.",
|
|
2280
|
-
"error.field_required": "{0} ist erforderlich.",
|
|
2281
|
-
"error.field_format": "Bitte gib einen gültigen Wert für {0} ein.",
|
|
2282
|
-
"error.field_min_length": "{0} ist zu kurz.",
|
|
2283
|
-
"error.field_max_length": "{0} ist zu lang.",
|
|
2284
|
-
"error.field_invalid": "Bitte überprüfe {0}.",
|
|
2285
|
-
"error.sign_in_server.title": "Anmeldung konnte nicht abgeschlossen werden.",
|
|
2286
|
-
"error.sign_in_server.body": "Bitte versuche es in einigen Minuten erneut"
|
|
2298
|
+
const getGetBrandingByIdUrl = (id) => {
|
|
2299
|
+
return `${getProxyPath()}/branding/${id}`;
|
|
2287
2300
|
};
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
"passkey-enroll.title": "Configura la tua passkey",
|
|
2333
|
-
"passkey-enroll.description": "Usa Face ID, Touch ID o il PIN del dispositivo per creare una passkey.",
|
|
2334
|
-
"passkey-enroll.action.passkey_register": "Configura passkey",
|
|
2335
|
-
"done.title": "Sei connesso come",
|
|
2336
|
-
"done.description": "",
|
|
2337
|
-
"password.title": "Accedi",
|
|
2338
|
-
"password.description": "Inserisci la tua password",
|
|
2339
|
-
"password.field.password": "Password",
|
|
2340
|
-
"password.action.signin": "Accedi",
|
|
2341
|
-
"password.action.passkey": "Accedi con passkey",
|
|
2342
|
-
"password.action.back": "Indietro",
|
|
2343
|
-
"password.action.register.lead": "Non hai un account? ",
|
|
2344
|
-
"password.action.register.link": "Registrati",
|
|
2345
|
-
"register.title": "Crea il tuo account",
|
|
2346
|
-
"register.description": "",
|
|
2347
|
-
"register.field.email": "E-mail",
|
|
2348
|
-
"register.field.email.placeholder": "tu@esempio.com",
|
|
2349
|
-
"register.field.password": "Password",
|
|
2350
|
-
"register.field.givenName": "Nome",
|
|
2351
|
-
"register.field.familyName": "Cognome",
|
|
2352
|
-
"register.field.companyName": "Nome azienda",
|
|
2353
|
-
"register.field.dateOfBirth": "Data di nascita",
|
|
2354
|
-
"register.field.country": "Paese",
|
|
2355
|
-
"register.field.country.placeholder": "Seleziona il tuo paese",
|
|
2356
|
-
"register.field.terms": "Accetto i termini di servizio",
|
|
2357
|
-
"register.action.password": "Continua con password",
|
|
2358
|
-
"register.action.passkey": "Continua con passkey",
|
|
2359
|
-
"register.action.submit": "Registrati",
|
|
2360
|
-
"register.action.back": "Indietro",
|
|
2361
|
-
"register.action.sign_in.lead": "Hai già un account? ",
|
|
2362
|
-
"register.action.sign_in.link": "Accedi",
|
|
2363
|
-
"complete.title": "Sei connesso come",
|
|
2364
|
-
"signed-in.continue": "Continua",
|
|
2365
|
-
"signed-in.logout": "Esci",
|
|
2366
|
-
"passkey-login.title": "Accedi con la tua passkey",
|
|
2367
|
-
"passkey-first.title": "Accedi",
|
|
2368
|
-
"passkey-first.description": "Usa la tua passkey per continuare",
|
|
2369
|
-
"passkey-first.action.passkey": "Continua con passkey",
|
|
2370
|
-
"passkey-first.action.email_fallback": "Usa invece l'email",
|
|
2371
|
-
"recover.title": "Controlla la tua e-mail",
|
|
2372
|
-
"recover.description": "Abbiamo inviato un link per reimpostare la password al tuo indirizzo e-mail.",
|
|
2373
|
-
"recover.action.back": "Torna all'accesso",
|
|
2374
|
-
"submit.continue": "Continua",
|
|
2375
|
-
"submit.signin": "Accedi",
|
|
2376
|
-
"action.forgot_password": "Password dimenticata?",
|
|
2377
|
-
"action.cancel": "Annulla",
|
|
2378
|
-
"action.back": "Indietro",
|
|
2379
|
-
"sso.redirect.title": "Reindirizzamento al provider…",
|
|
2380
|
-
"passkey.pending.status": "In attesa della tua passkey…",
|
|
2381
|
-
"error.passkey_cancelled": "La richiesta della passkey è stata chiusa prima del completamento.",
|
|
2382
|
-
"error.passkey_timeout": "La richiesta della passkey è scaduta. Riprova.",
|
|
2383
|
-
"error.passkey_not_registered": "Questa passkey non è registrata. Accedi con e-mail e password.",
|
|
2384
|
-
"error.passkey_setup_failed": "La registrazione della passkey non è stata completata. Riprova.",
|
|
2385
|
-
"error.passkey_unsupported": "Questo dispositivo non supporta le passkey",
|
|
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.",
|
|
2389
|
-
"error.email_required": "Inserisci un indirizzo e-mail",
|
|
2390
|
-
"error.email_invalid": "Inserisci un indirizzo e-mail valido",
|
|
2391
|
-
"error.password_required": "Inserisci una password",
|
|
2392
|
-
"error.password_incorrect": "E-mail o password errata.",
|
|
2393
|
-
"error.email_exists": "Esiste già un account con questo indirizzo e-mail.",
|
|
2394
|
-
"error.invalid_credentials": "E-mail o password errata.",
|
|
2395
|
-
"error.required": "Questo campo è obbligatorio.",
|
|
2396
|
-
"error.field_required": "{0} è obbligatorio.",
|
|
2397
|
-
"error.field_format": "Inserisci un valore valido per {0}.",
|
|
2398
|
-
"error.field_min_length": "{0} è troppo corto.",
|
|
2399
|
-
"error.field_max_length": "{0} è troppo lungo.",
|
|
2400
|
-
"error.field_invalid": "Controlla {0}.",
|
|
2401
|
-
"error.sign_in_server.title": "Non è stato possibile completare l'accesso.",
|
|
2402
|
-
"error.sign_in_server.body": "Riprova tra qualche minuto"
|
|
2301
|
+
/**
|
|
2302
|
+
* Retrieves a single branding revision, including its stored configuration.
|
|
2303
|
+
* @summary Get a branding revision by ID
|
|
2304
|
+
*/
|
|
2305
|
+
const getBrandingById = async (id, options) => {
|
|
2306
|
+
return customFetch(getGetBrandingByIdUrl(id), {
|
|
2307
|
+
...options,
|
|
2308
|
+
method: "GET"
|
|
2309
|
+
});
|
|
2310
|
+
};
|
|
2311
|
+
const getListEventsUrl = (params) => {
|
|
2312
|
+
const normalizedParams = new URLSearchParams();
|
|
2313
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
2314
|
+
if (Array.isArray(value) && ["category", "event_type"].includes(key)) {
|
|
2315
|
+
value.forEach((v) => {
|
|
2316
|
+
normalizedParams.append(key, v === null ? "null" : v.toString());
|
|
2317
|
+
});
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
2321
|
+
});
|
|
2322
|
+
const stringifiedParams = normalizedParams.toString();
|
|
2323
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/events?${stringifiedParams}` : `${getProxyPath()}/events`;
|
|
2324
|
+
};
|
|
2325
|
+
/**
|
|
2326
|
+
* Returns project-scoped audit events, newest-first by keyset on
|
|
2327
|
+
`(created_at, id)` (ADR 027 / ADR 049). Pass `order=asc` for oldest-first.
|
|
2328
|
+
Requires `events.read`.
|
|
2329
|
+
|
|
2330
|
+
Pre-claim projects return an empty list (events are stored but not
|
|
2331
|
+
visible until claim succeeds). Team-scoped credentials see only events
|
|
2332
|
+
whose emit-time `team_id` matches the credential team (enforced when
|
|
2333
|
+
team-scoped tokens exist).
|
|
2334
|
+
|
|
2335
|
+
Clients discriminate each item via `event_type` (OpenAPI Event oneOf) —
|
|
2336
|
+
see docs/design/api/events-catalog.md.
|
|
2337
|
+
|
|
2338
|
+
* @summary List events
|
|
2339
|
+
*/
|
|
2340
|
+
const listEvents = async (params, options) => {
|
|
2341
|
+
return customFetch(getListEventsUrl(params), {
|
|
2342
|
+
...options,
|
|
2343
|
+
method: "GET"
|
|
2344
|
+
});
|
|
2403
2345
|
};
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
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
|
-
}
|
|
2346
|
+
const getGetEventUrl = (id, params) => {
|
|
2347
|
+
const normalizedParams = new URLSearchParams();
|
|
2348
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
2349
|
+
if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
|
|
2350
|
+
});
|
|
2351
|
+
const stringifiedParams = normalizedParams.toString();
|
|
2352
|
+
return stringifiedParams.length > 0 ? `${getProxyPath()}/events/${id}?${stringifiedParams}` : `${getProxyPath()}/events/${id}`;
|
|
2437
2353
|
};
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2354
|
+
/**
|
|
2355
|
+
* Loads a single event by `(project_id, id)`. Requires `events.read`.
|
|
2356
|
+
Events are not registered in `resource_scope_index`; project scope is
|
|
2357
|
+
required on the query (ADR 049). Misses and cross-project ids return 404.
|
|
2358
|
+
|
|
2359
|
+
Pre-claim projects return 404 (stored events stay invisible until claim).
|
|
2360
|
+
|
|
2361
|
+
* @summary Get event by ID
|
|
2362
|
+
*/
|
|
2363
|
+
const getEvent = async (id, params, options) => {
|
|
2364
|
+
return customFetch(getGetEventUrl(id, params), {
|
|
2365
|
+
...options,
|
|
2366
|
+
method: "GET"
|
|
2367
|
+
});
|
|
2444
2368
|
};
|
|
2445
2369
|
//#endregion
|
|
2446
|
-
//#region ../
|
|
2370
|
+
//#region ../api/dist/runtime/api-factory.mjs
|
|
2447
2371
|
/**
|
|
2448
|
-
*
|
|
2449
|
-
*
|
|
2372
|
+
* Factory for the typed Zitadel client every consumer reaches for.
|
|
2373
|
+
*
|
|
2374
|
+
* Wraps the orval-generated endpoints in a {@link Proxy} so each
|
|
2375
|
+
* generated function sees the right base URL and (optionally) the
|
|
2376
|
+
* right bearer token at call time — without the caller having to
|
|
2377
|
+
* thread either through every operation, and without exposing the
|
|
2378
|
+
* module-globals (`setProxyPath` / `setApiAuthToken`) as part of the
|
|
2379
|
+
* public API.
|
|
2380
|
+
*
|
|
2381
|
+
* Per-instance isolation: multiple clients can coexist in one process
|
|
2382
|
+
* (different servers, different tokens). Their Proxy `get` traps set
|
|
2383
|
+
* the globals synchronously before invoking the underlying generated
|
|
2384
|
+
* function, which reads them at the top of `customFetch` before any
|
|
2385
|
+
* `await`. Two clients running interleaved within a single JS turn
|
|
2386
|
+
* are safe; two clients running in parallel across awaits could
|
|
2387
|
+
* clobber each other — not a pattern any current consumer uses.
|
|
2450
2388
|
*/
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2389
|
+
/**
|
|
2390
|
+
* Build a typed Zitadel client pre-bound to a base URL and (optionally)
|
|
2391
|
+
* a bearer token. Every method on the returned object mirrors a
|
|
2392
|
+
* generated orval function:
|
|
2393
|
+
*
|
|
2394
|
+
* const client = createZitadelClient({ baseUrl, token });
|
|
2395
|
+
* await client.createProject({ preview_origins: [] });
|
|
2396
|
+
* await client.createSchema(body, { project_id });
|
|
2397
|
+
*/
|
|
2398
|
+
function createZitadelClient(opts) {
|
|
2399
|
+
let baseUrl = opts.baseUrl;
|
|
2400
|
+
while (baseUrl.endsWith("/")) baseUrl = baseUrl.slice(0, -1);
|
|
2401
|
+
return new Proxy(zitadelNextGen_exports, { get(target, prop, receiver) {
|
|
2402
|
+
const value = Reflect.get(target, prop, receiver);
|
|
2403
|
+
if (typeof value !== "function") return value;
|
|
2404
|
+
return (...args) => {
|
|
2405
|
+
setProxyPath(baseUrl);
|
|
2406
|
+
setApiAuthToken(opts.token);
|
|
2407
|
+
return value(...args);
|
|
2408
|
+
};
|
|
2409
|
+
} });
|
|
2410
|
+
}
|
|
2411
|
+
//#endregion
|
|
2412
|
+
//#region ../api/dist/runtime/config.mjs
|
|
2413
|
+
/**
|
|
2414
|
+
* Slot for the configured project, stored on `globalThis` under a
|
|
2415
|
+
* {@link Symbol.for} key. Every copy of this module evaluated in the
|
|
2416
|
+
* same JS realm resolves the symbol through the global symbol registry
|
|
2417
|
+
* to the same identity, so they all read and write a single slot —
|
|
2418
|
+
* needed because the standalone components bundle inlines its own copy
|
|
2419
|
+
* of this file, and dual-package hazards / monorepo duplicates can load
|
|
2420
|
+
* a second copy alongside the app's. With a module-local `let` each
|
|
2421
|
+
* instance kept its own singleton and `configureZitadel()` calls in one
|
|
2422
|
+
* were invisible to `getZitadelConfig()` calls in another. Sharing is
|
|
2423
|
+
* realm-scoped — separate realms (iframes, Node `vm` contexts, worker
|
|
2424
|
+
* threads) have their own registries and are not unified by this.
|
|
2425
|
+
*/
|
|
2426
|
+
const PROJECT_SLOT = Symbol.for("@zitadel/api/config:currentProject");
|
|
2427
|
+
function readSlot() {
|
|
2428
|
+
return globalThis[PROJECT_SLOT] ?? null;
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Per-project API client cache. Ensures `getApi(project)` returns the
|
|
2432
|
+
* same instance for the same project handle — no re-wrapping on every call.
|
|
2433
|
+
* Module-local on purpose: keyed by the shared frozen `ZitadelProject`
|
|
2434
|
+
* object identity, so cross-instance reads still resolve through the
|
|
2435
|
+
* same key and at worst memoize once per module instance.
|
|
2436
|
+
*/
|
|
2437
|
+
const apiCache = /* @__PURE__ */ new WeakMap();
|
|
2438
|
+
/**
|
|
2439
|
+
* Returns a typed API client for the given project, with the base URL
|
|
2440
|
+
* pre-bound. Cached per project handle — safe to call multiple times.
|
|
2441
|
+
*
|
|
2442
|
+
* ```ts
|
|
2443
|
+
* const project = configureZitadel({ ... });
|
|
2444
|
+
* export const api = getApi(project);
|
|
2445
|
+
* ```
|
|
2446
|
+
*/
|
|
2447
|
+
function getApi(project) {
|
|
2448
|
+
let api = apiCache.get(project);
|
|
2449
|
+
if (!api) {
|
|
2450
|
+
api = createZitadelClient({
|
|
2451
|
+
baseUrl: project.proxyPath,
|
|
2452
|
+
token: project.publishableKey
|
|
2453
|
+
});
|
|
2454
|
+
apiCache.set(project, api);
|
|
2455
|
+
}
|
|
2456
|
+
return api;
|
|
2457
|
+
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Returns the current project handle, or `null` if {@link configureZitadel}
|
|
2460
|
+
* has not been called yet.
|
|
2461
|
+
*/
|
|
2462
|
+
function getZitadelConfig() {
|
|
2463
|
+
return readSlot();
|
|
2464
|
+
}
|
|
2465
|
+
//#endregion
|
|
2466
|
+
//#region src/orchestrator/resolve-api.ts
|
|
2467
|
+
/** Matches the `configureZitadel()` default so attribute and JS config agree. */
|
|
2468
|
+
const DEFAULT_PROXY_PATH = "/__nextgen";
|
|
2469
|
+
/**
|
|
2470
|
+
* Cache of project handles synthesized from attributes, keyed by their value
|
|
2471
|
+
* tuple. `getApi()` caches its client in a `WeakMap` keyed by the project's
|
|
2472
|
+
* identity, so the same attribute values must yield the *same* frozen object
|
|
2473
|
+
* across the several `resolveApi()` calls a single flow makes — otherwise
|
|
2474
|
+
* every call misses the cache and re-wraps a fresh client. A real page uses a
|
|
2475
|
+
* handful of distinct configs at most; the FIFO bound below is just a guard so
|
|
2476
|
+
* a pathological page that churns through unique configs can't leak memory.
|
|
2477
|
+
*/
|
|
2478
|
+
const MAX_SYNTHETIC_PROJECTS = 32;
|
|
2479
|
+
const syntheticProjects = /* @__PURE__ */ new Map();
|
|
2480
|
+
/**
|
|
2481
|
+
* Builds a `ZitadelProject` from declarative attributes, or `undefined` when
|
|
2482
|
+
* no `project-id` was given (nothing to configure). Mirrors
|
|
2483
|
+
* `configureZitadel()`'s proxy-path defaulting so the two config paths are
|
|
2484
|
+
* interchangeable.
|
|
2485
|
+
*/
|
|
2486
|
+
function projectFromAttrs({ projectId, proxyPath, url }) {
|
|
2487
|
+
if (!projectId) return void 0;
|
|
2488
|
+
const resolvedProxyPath = proxyPath || DEFAULT_PROXY_PATH;
|
|
2489
|
+
const resolvedUrl = url || void 0;
|
|
2490
|
+
const key = JSON.stringify([
|
|
2491
|
+
projectId,
|
|
2492
|
+
resolvedProxyPath,
|
|
2493
|
+
resolvedUrl ?? ""
|
|
2494
|
+
]);
|
|
2495
|
+
let project = syntheticProjects.get(key);
|
|
2496
|
+
if (!project) {
|
|
2497
|
+
project = Object.freeze({
|
|
2498
|
+
projectId,
|
|
2499
|
+
proxyPath: resolvedProxyPath,
|
|
2500
|
+
url: resolvedUrl
|
|
2501
|
+
});
|
|
2502
|
+
if (syntheticProjects.size >= MAX_SYNTHETIC_PROJECTS) {
|
|
2503
|
+
const oldest = syntheticProjects.keys().next().value;
|
|
2504
|
+
if (oldest !== void 0) syntheticProjects.delete(oldest);
|
|
2505
|
+
}
|
|
2506
|
+
syntheticProjects.set(key, project);
|
|
2507
|
+
}
|
|
2508
|
+
return project;
|
|
2509
|
+
}
|
|
2510
|
+
/**
|
|
2511
|
+
* Resolves the effective SDK project handle and its typed API client for an
|
|
2512
|
+
* orchestrator element. Precedence, highest first:
|
|
2513
|
+
*
|
|
2514
|
+
* 1. the element's `project` property (an SDK handle set from JS / a framework),
|
|
2515
|
+
* 2. the global handle from `configureZitadel()`,
|
|
2516
|
+
* 3. the `project-id` / `proxy-path` / `url` attributes set declaratively in HTML.
|
|
2517
|
+
*
|
|
2518
|
+
* Both JS paths (property and global) win over the declarative attributes, so a
|
|
2519
|
+
* deliberate `configureZitadel()` is never silently overridden by fallback or
|
|
2520
|
+
* stale HTML attributes; the attributes are the no-JS fallback.
|
|
2521
|
+
*
|
|
2522
|
+
* Both `<zitadel-login>` and `<zitadel-logout>` need the same resolution and
|
|
2523
|
+
* the same "no config" failure, so it lives here rather than being copied
|
|
2524
|
+
* into each element. Callers run this inside their try/catch so a missing
|
|
2525
|
+
* configuration surfaces through the element's normal error path rather than
|
|
2526
|
+
* as an unhandled rejection.
|
|
2527
|
+
*
|
|
2528
|
+
* @param project Optional per-element override handle.
|
|
2529
|
+
* @param attrs Declarative config read from the element's attributes.
|
|
2530
|
+
* @param element Tag name used in the thrown error (e.g. `<zitadel-login>`).
|
|
2531
|
+
*/
|
|
2532
|
+
function resolveApi(project, attrs, element) {
|
|
2533
|
+
const cfg = project ?? getZitadelConfig() ?? projectFromAttrs(attrs);
|
|
2534
|
+
if (!cfg) throw new Error(`${element} requires a configured project: set the \`project-id\` attribute, set the \`project\` property, or call configureZitadel().`);
|
|
2535
|
+
return {
|
|
2536
|
+
project: cfg,
|
|
2537
|
+
api: getApi(cfg)
|
|
2538
|
+
};
|
|
2455
2539
|
}
|
|
2456
2540
|
//#endregion
|
|
2457
2541
|
//#region src/orchestrator/sanitiser.ts
|
|
@@ -2626,15 +2710,15 @@ const DENSITY_MAP = {
|
|
|
2626
2710
|
}
|
|
2627
2711
|
};
|
|
2628
2712
|
const PALETTE_MAP = {
|
|
2629
|
-
primary: ["--zl-color-surface-default-white"],
|
|
2630
|
-
on_primary: ["--zl-color-text-button-default"],
|
|
2713
|
+
primary: ["--zl-primary", "--zl-color-surface-default-white"],
|
|
2714
|
+
on_primary: ["--zl-primary-foreground", "--zl-color-text-button-default"],
|
|
2631
2715
|
background: ["--zl-color-surface-default-black"],
|
|
2632
2716
|
surface: ["--zl-color-surface-default-primary-gray"],
|
|
2633
2717
|
muted: ["--zl-color-surface-default-secondary-gray"],
|
|
2634
2718
|
border: ["--zl-color-border-default-gray-200", "--zl-color-border-default-gray-100"],
|
|
2635
2719
|
text: ["--zl-color-text-primary-white"],
|
|
2636
2720
|
text_muted: ["--zl-color-text-secondary-gray"],
|
|
2637
|
-
link: ["--zl-color-text-
|
|
2721
|
+
link: ["--zl-color-text-link"],
|
|
2638
2722
|
success: [
|
|
2639
2723
|
"--zl-color-text-success",
|
|
2640
2724
|
"--zl-color-border-success",
|
|
@@ -2969,6 +3053,13 @@ var ZitadelSurface = class extends LitElement {
|
|
|
2969
3053
|
set theme(value) {
|
|
2970
3054
|
this.#_theme_accessor_storage = value;
|
|
2971
3055
|
}
|
|
3056
|
+
#_suppressHeader_accessor_storage = false;
|
|
3057
|
+
get suppressHeader() {
|
|
3058
|
+
return this.#_suppressHeader_accessor_storage;
|
|
3059
|
+
}
|
|
3060
|
+
set suppressHeader(value) {
|
|
3061
|
+
this.#_suppressHeader_accessor_storage = value;
|
|
3062
|
+
}
|
|
2972
3063
|
themeController = new ThemeController(this);
|
|
2973
3064
|
/**
|
|
2974
3065
|
* Resolve the active theme and commit it to this element's surface: adopt
|
|
@@ -2997,9 +3088,31 @@ __decorate([property({
|
|
|
2997
3088
|
reflect: true
|
|
2998
3089
|
})], ZitadelSurface.prototype, "variant", null);
|
|
2999
3090
|
__decorate([property({ type: String })], ZitadelSurface.prototype, "theme", null);
|
|
3091
|
+
__decorate([property({
|
|
3092
|
+
type: Boolean,
|
|
3093
|
+
reflect: true,
|
|
3094
|
+
attribute: "suppress-header"
|
|
3095
|
+
})], ZitadelSurface.prototype, "suppressHeader", null);
|
|
3096
|
+
//#endregion
|
|
3097
|
+
//#region src/orchestrator/template-names.ts
|
|
3098
|
+
/**
|
|
3099
|
+
* Names of the bundled Liquid templates the `<zitadel-login>` orchestrator
|
|
3100
|
+
* registers.
|
|
3101
|
+
*
|
|
3102
|
+
* This lives in its own module — separate from `liquid.ts` — so the public
|
|
3103
|
+
* barrel can re-export `TEMPLATE_NAMES` without dragging LiquidJS' `Liquid`
|
|
3104
|
+
* type (and its Node ambient-type requirements) into the published
|
|
3105
|
+
* declaration surface. See `liquid.ts` / the orchestrator barrel.
|
|
3106
|
+
*/
|
|
3107
|
+
const TEMPLATE_NAMES = {
|
|
3108
|
+
default: "default",
|
|
3109
|
+
authForm: "auth-form",
|
|
3110
|
+
passkeyUpsell: "passkey-upsell",
|
|
3111
|
+
signedIn: "signed-in"
|
|
3112
|
+
};
|
|
3000
3113
|
//#endregion
|
|
3001
3114
|
//#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
|
|
3115
|
+
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\nimg[data-zl-asset-broken] {\n display: none;\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[data-suppress-header] .zl-card-title, [data-suppress-header] .zl-card-subtitle {\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n position: absolute;\n overflow: hidden;\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-text-link, 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-text-link, 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 width: 100%;\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 max-height: var(--zl-split-logo-max-height, 16rem);\n}\n\n.zl-split__logo:has( + .zl-split__hero) {\n max-height: var(--zl-split-logo-max-height, 8rem);\n}\n\n.zl-split__logo:has( + .zl-split__hero) + .zl-split__hero {\n max-height: min(var(--zl-split-hero-max-height, 27.5rem),\n max(6rem,\n calc(100vh - 104px - 6.5rem - 2.5rem - var(--zl-spacing-04, 1.5rem) -\n var(--zl-spacing-04, 1.5rem) - var(--zl-split-logo-max-height, 8rem))));\n}\n\n.zl-split__hero {\n width: 100%;\n max-height: var(--zl-split-hero-max-height, min(27.5rem, 60vh));\n border-radius: var(--zl-radius-m, .75rem);\n object-fit: cover;\n}\n\n.zl-split__placeholder {\n aspect-ratio: 4 / 5;\n border-radius: var(--zl-radius-m, .75rem);\n background: radial-gradient(120% 120% at 18% 12%,\n var(--zl-color-icon-default-purple, #bba5e4) 0%,\n transparent 60%),\n radial-gradient(130% 130% at 85% 92%,\n var(--zl-color-icon-default-pink, #f48aa0) 0%,\n transparent 60%),\n var(--zl-color-surface-default-primary-gray, #252528);\n width: min(22rem, 85%);\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\nzl-page-shell:has(.zl-split) .zl-attribution {\n grid-template-columns: var(--zl-split-columns, minmax(0, 1fr) minmax(0, 1fr));\n gap: var(--zl-spacing-05, 2rem);\n width: 100%;\n display: grid;\n}\n\nzl-page-shell:has(.zl-split) .zl-attribution > * {\n grid-column: 2;\n justify-self: center;\n}\n\nzl-page-shell:has(.zl-split--right) .zl-attribution > * {\n grid-column: 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 zl-page-shell:has(.zl-split) .zl-attribution {\n grid-template-columns: minmax(0, 1fr);\n }\n\n zl-page-shell:has(.zl-split) .zl-attribution > *, zl-page-shell:has(.zl-split--right) .zl-attribution > * {\n grid-column: 1;\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
3116
|
//#endregion
|
|
3004
3117
|
//#region src/orchestrator/zitadel-login.ts
|
|
3005
3118
|
/** Narrow a rendered named element to the `formValue` field-atom contract. */
|
|
@@ -3213,8 +3326,13 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
|
|
|
3213
3326
|
updated(changed) {
|
|
3214
3327
|
if (this.shadowRoot) {
|
|
3215
3328
|
stampExportparts(this.shadowRoot);
|
|
3329
|
+
armAssetFallbacks(this.shadowRoot);
|
|
3216
3330
|
const widget = this.variant !== "page";
|
|
3217
|
-
for (const shell of this.shadowRoot.querySelectorAll("zl-page-shell"))
|
|
3331
|
+
for (const shell of this.shadowRoot.querySelectorAll("zl-page-shell")) {
|
|
3332
|
+
shell.toggleAttribute("data-widget", widget);
|
|
3333
|
+
shell.toggleAttribute("data-suppress-header", this.suppressHeader);
|
|
3334
|
+
}
|
|
3335
|
+
for (const card of this.shadowRoot.querySelectorAll("zl-card")) card.toggleAttribute("data-suppress-header", this.suppressHeader);
|
|
3218
3336
|
}
|
|
3219
3337
|
const props = changed;
|
|
3220
3338
|
if (!props.has("response")) return;
|
|
@@ -3222,10 +3340,10 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
|
|
|
3222
3340
|
}
|
|
3223
3341
|
/**
|
|
3224
3342
|
* Apply captured values and move focus once the new step has fully
|
|
3225
|
-
* rendered. This commit produces the step's
|
|
3226
|
-
*
|
|
3227
|
-
*
|
|
3228
|
-
*
|
|
3343
|
+
* rendered. This commit produces the step's field/action atoms, but those
|
|
3344
|
+
* render their own shadow DOM on a later microtask — so await this element's
|
|
3345
|
+
* update *and* the child atoms' first render before touching them, rather
|
|
3346
|
+
* than guessing a frame with `requestAnimationFrame`.
|
|
3229
3347
|
*
|
|
3230
3348
|
* Focus on the *initial* response is page-mode-only: a dedicated login
|
|
3231
3349
|
* route should focus its first field, but a widget embedded further down
|
|
@@ -3234,14 +3352,18 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
|
|
|
3234
3352
|
*/
|
|
3235
3353
|
async hydrateStepAfterRender(initial = false) {
|
|
3236
3354
|
await this.updateComplete;
|
|
3237
|
-
const atoms = this.shadowRoot?.querySelectorAll("zl-field, zl-button");
|
|
3355
|
+
const atoms = this.shadowRoot?.querySelectorAll("zl-field, zl-select, zl-checkbox, zl-button");
|
|
3238
3356
|
if (atoms) await Promise.all(Array.from(atoms).map((atom) => atom.updateComplete));
|
|
3239
3357
|
this.applyValuesToFields();
|
|
3240
|
-
if (!initial || this.variant === "page") this.moveFocusToFirstField();
|
|
3358
|
+
if (!initial || this.variant === "page") this.moveFocusToFirstField(initial && this.variant === "page");
|
|
3241
3359
|
}
|
|
3242
3360
|
render() {
|
|
3243
3361
|
if (this.startupError) return html`<form class="zl-mount" novalidate>
|
|
3244
|
-
<zl-
|
|
3362
|
+
<zl-page-shell>
|
|
3363
|
+
<zl-card>
|
|
3364
|
+
<zl-alert severity="error">${this.startupError}</zl-alert>
|
|
3365
|
+
</zl-card>
|
|
3366
|
+
</zl-page-shell>
|
|
3245
3367
|
</form>`;
|
|
3246
3368
|
if (!this.response || !this.engine) return html`<slot name="loader"></slot>`;
|
|
3247
3369
|
const rendered = this.injectAttribution(this.renderStep(this.response.step, this.engine));
|
|
@@ -3334,7 +3456,7 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
|
|
|
3334
3456
|
applyResponse(wire) {
|
|
3335
3457
|
this.stepErrorDismissed = false;
|
|
3336
3458
|
this.response = wire;
|
|
3337
|
-
const { branding, issues } = validateBranding(wire.branding);
|
|
3459
|
+
const { branding, issues } = validateBranding(wire.branding, { renderingOrigin: this.ownerDocument.location.origin });
|
|
3338
3460
|
this.branding = branding;
|
|
3339
3461
|
this.themeController.setBranding(branding);
|
|
3340
3462
|
if (issues.length > 0) console.warn("[zitadel-login] branding payload has issues:", issues);
|
|
@@ -3694,11 +3816,17 @@ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
|
|
|
3694
3816
|
if (!root) return null;
|
|
3695
3817
|
return (root.querySelector("zl-button[hierarchy=\"primary\"][type=\"submit\"]") ?? root.querySelector("zl-button[hierarchy=\"primary\"]"))?.getAttribute("action") || null;
|
|
3696
3818
|
}
|
|
3697
|
-
|
|
3819
|
+
/**
|
|
3820
|
+
* On the initial paint, only a field earns focus: script-moved focus with
|
|
3821
|
+
* no prior interaction matches `:focus-visible`, so autofocusing a button
|
|
3822
|
+
* on a field-less step (passkey-first) paints a ring that reads as a
|
|
3823
|
+
* pre-selected state. Step swaps keep button focus — there the browser
|
|
3824
|
+
* derives the modality from the user's actual input.
|
|
3825
|
+
*/
|
|
3826
|
+
moveFocusToFirstField(fieldsOnly = false) {
|
|
3698
3827
|
const root = this.shadowRoot;
|
|
3699
3828
|
if (!root) return;
|
|
3700
|
-
|
|
3701
|
-
Array.from(focusables).find((el) => !el.hasAttribute("disabled"))?.focus();
|
|
3829
|
+
(fieldsOnly ? this.fieldAtoms() : Array.from(root.querySelectorAll("zl-field, zl-select, zl-checkbox, zl-button"))).find((el) => !el.hasAttribute("disabled") && !el.hasAttribute("hidden") && el.getAttribute("type") !== "hidden")?.focus();
|
|
3702
3830
|
}
|
|
3703
3831
|
async submit(action, challengeResponse) {
|
|
3704
3832
|
if (!this.response || this.loading) return;
|
|
@@ -4317,6 +4445,18 @@ let ZitadelSession = class ZitadelSession extends ZitadelSurface {
|
|
|
4317
4445
|
line-height: 1.25rem;
|
|
4318
4446
|
color: ${t.color.text.error};
|
|
4319
4447
|
}
|
|
4448
|
+
/* suppress-header: visually hidden, kept in the accessibility tree. */
|
|
4449
|
+
.title.sr-only {
|
|
4450
|
+
position: absolute;
|
|
4451
|
+
width: 1px;
|
|
4452
|
+
height: 1px;
|
|
4453
|
+
margin: -1px;
|
|
4454
|
+
padding: 0;
|
|
4455
|
+
border: 0;
|
|
4456
|
+
overflow: hidden;
|
|
4457
|
+
clip-path: inset(50%);
|
|
4458
|
+
white-space: nowrap;
|
|
4459
|
+
}
|
|
4320
4460
|
`];
|
|
4321
4461
|
#_project_accessor_storage;
|
|
4322
4462
|
get project() {
|
|
@@ -4480,9 +4620,13 @@ let ZitadelSession = class ZitadelSession extends ZitadelSurface {
|
|
|
4480
4620
|
render() {
|
|
4481
4621
|
return html`
|
|
4482
4622
|
<zl-page-shell ?data-widget=${this.variant !== "page"}>
|
|
4483
|
-
<zl-card>
|
|
4484
|
-
<h1 slot="header" class="title
|
|
4485
|
-
|
|
4623
|
+
<zl-card ?data-suppress-header=${this.suppressHeader}>
|
|
4624
|
+
<h1 slot="header" class="title ${this.suppressHeader ? "sr-only" : ""}">
|
|
4625
|
+
${this.heading}
|
|
4626
|
+
</h1>
|
|
4627
|
+
${this.identityLabel ? html`<p slot=${this.suppressHeader ? nothing : "header"} class="identity">
|
|
4628
|
+
${this.identityLabel}
|
|
4629
|
+
</p>` : nothing}
|
|
4486
4630
|
|
|
4487
4631
|
<zl-button
|
|
4488
4632
|
hierarchy="primary"
|
|
@@ -4526,6 +4670,6 @@ __decorate([state()], ZitadelSession.prototype, "loading", null);
|
|
|
4526
4670
|
__decorate([state()], ZitadelSession.prototype, "errorMessage", null);
|
|
4527
4671
|
ZitadelSession = __decorate([customElement("zitadel-session")], ZitadelSession);
|
|
4528
4672
|
//#endregion
|
|
4529
|
-
export { submitStep as C, startFlow as S, MANDATORY_GATES_MARKER as _,
|
|
4673
|
+
export { submitStep as C, startFlow as S, MANDATORY_GATES_MARKER as _, TEMPLATE_NAMES as a, validateBranding as b, applyBrandingTokens as c, createSanitiser as d, builtinLocales as f, en as g, de as h, layout_chrome_default as i, buildBrandingStylesheet as l, it as m, ZitadelLogout as n, ThemeController as o, businessLocales as p, ZitadelLogin as r, applyFontUrl as s, ZitadelSession as t, resolveTheme as u, mandatoryGatesMarkerComment as v, getCurrentStep as x, patchMandatoryGates as y };
|
|
4530
4674
|
|
|
4531
|
-
//# sourceMappingURL=orchestrator-
|
|
4675
|
+
//# sourceMappingURL=orchestrator-CsHP57zo.mjs.map
|