@zitadel/components 0.1.0-alpha.15 → 0.1.0-alpha.17
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 +5 -1
- package/dist/atoms/index.d.mts +1 -1
- package/dist/atoms/index.mjs +1 -1
- package/dist/{atoms-BSwVl736.mjs → atoms-KC7OJWXZ.mjs} +138 -12
- package/dist/atoms-KC7OJWXZ.mjs.map +1 -0
- package/dist/default-CfF7GoLv.mjs +6 -0
- package/dist/default-CfF7GoLv.mjs.map +1 -0
- package/dist/{index-EC-BnGsx.d.mts → index-BniwXaIC.d.mts} +3 -1
- package/dist/{index-EC-BnGsx.d.mts.map → index-BniwXaIC.d.mts.map} +1 -1
- package/dist/{index-Bypr00UG.d.mts → index-DSZnfOJH.d.mts} +368 -17
- package/dist/index-DSZnfOJH.d.mts.map +1 -0
- package/dist/{index-BP3l3mje.d.mts → index-DjYJwejg.d.mts} +51 -4
- package/dist/index-DjYJwejg.d.mts.map +1 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +4 -4
- package/dist/manifests.mjs +1 -1
- package/dist/orchestrator/index.d.mts +2 -2
- package/dist/orchestrator/index.mjs +2 -2
- package/dist/{orchestrator-Ty4uM5n_.mjs → orchestrator-BWqJw4Yl.mjs} +348 -25
- package/dist/orchestrator-BWqJw4Yl.mjs.map +1 -0
- package/dist/standalone.mjs +485 -34
- package/dist/tokens/index.d.mts +1 -1
- package/dist/tokens/index.mjs +1 -1
- package/dist/{tokens-C0j4iCrs.mjs → tokens-CbH2s1jV.mjs} +6 -4
- package/dist/tokens-CbH2s1jV.mjs.map +1 -0
- package/package.json +3 -2
- package/dist/atoms-BSwVl736.mjs.map +0 -1
- package/dist/default-BmX2ZEbg.mjs +0 -6
- package/dist/default-BmX2ZEbg.mjs.map +0 -1
- package/dist/index-BP3l3mje.d.mts.map +0 -1
- package/dist/index-Bypr00UG.d.mts.map +0 -1
- package/dist/orchestrator-Ty4uM5n_.mjs.map +0 -1
- package/dist/tokens-C0j4iCrs.mjs.map +0 -1
package/dist/standalone.mjs
CHANGED
|
@@ -823,7 +823,8 @@ const tokens = {
|
|
|
823
823
|
"container": {
|
|
824
824
|
"authCard": "24rem",
|
|
825
825
|
"page": "80rem"
|
|
826
|
-
}
|
|
826
|
+
},
|
|
827
|
+
"layout": {}
|
|
827
828
|
};
|
|
828
829
|
const cssVars = {
|
|
829
830
|
"color": {
|
|
@@ -942,9 +943,10 @@ const cssVars = {
|
|
|
942
943
|
"container": {
|
|
943
944
|
"authCard": "var(--zl-container-auth-card)",
|
|
944
945
|
"page": "var(--zl-container-page)"
|
|
945
|
-
}
|
|
946
|
+
},
|
|
947
|
+
"layout": {}
|
|
946
948
|
};
|
|
947
|
-
const tokensCss = "/* AUTOGENERATED by packages/design-tokens/scripts/build.ts — do not edit by hand.\n Source: src/generated/figma.tokens.json + src/overrides.ts.\n Re-run: moon run design-tokens:generate */\n\n:root,\n[data-theme=\"dark\"] {\n --zl-color-surface-default-black: #0f0f11;\n --zl-color-surface-default-white: #f4f4f6;\n --zl-color-surface-default-primary-gray: #252528;\n --zl-color-surface-default-secondary-gray: #37373a;\n --zl-color-text-primary-white: #f4f4f6;\n --zl-color-text-secondary-gray: #bfbfcf;\n --zl-color-text-button-default: #0f0f11;\n --zl-color-text-button-invert: #f4f4f6;\n --zl-color-text-disabled: #686883;\n --zl-color-text-error: #ea4f70;\n --zl-color-text-success: #33a779;\n --zl-color-text-subtitle-pink: #f48aa0;\n --zl-color-text-subtitle-purple: #bba5e4;\n --zl-color-text-subtitle-gray: #bfbfcf;\n --zl-color-text-subtitle-orange: #f25543;\n --zl-color-icon-default-black: #0f0f11;\n --zl-color-icon-default-white: #f4f4f6;\n --zl-color-icon-default-gray: #bfbfcf;\n --zl-color-icon-default-pink: #f48aa0;\n --zl-color-icon-default-purple: #bba5e4;\n --zl-color-icon-default-orange: #f25543;\n --zl-color-icon-disabled: #686883;\n --zl-color-icon-error: #ea4f70;\n --zl-color-icon-success: #33a779;\n --zl-color-border-default-black: #252528;\n --zl-color-border-default-white: #f4f4f6;\n --zl-color-border-default-gray-100: #37373a;\n --zl-color-border-default-gray-200: #484a57;\n --zl-color-border-default-gray-300: #686883;\n --zl-color-border-default-gray-400: #82829c;\n --zl-color-border-error: #ea4f70;\n --zl-color-border-success: #33a779;\n --zl-color-gray-50: #0f0f11;\n --zl-color-gray-75: #252528;\n --zl-color-gray-100: #37373a;\n --zl-color-gray-200: #484a57;\n --zl-color-gray-300: #686883;\n --zl-color-gray-400: #82829c;\n --zl-color-gray-500: #9e9eb2;\n --zl-color-gray-600: #cfcfde;\n --zl-color-gray-700: #bfbfcf;\n --zl-color-gray-800: #e1e1e7;\n --zl-color-gray-900: #f4f4f6;\n --zl-spacing-01: 0.25rem;\n --zl-spacing-02: 0.5rem;\n --zl-spacing-03: 1rem;\n --zl-spacing-04: 1.5rem;\n --zl-spacing-05: 2rem;\n --zl-spacing-06: 1.5rem;\n --zl-spacing-07: 2.5rem;\n --zl-spacing-08: 3rem;\n --zl-spacing-09: 3.5rem;\n --zl-spacing-10: 4rem;\n --zl-spacing-11: 5rem;\n --zl-spacing-12: 5.5rem;\n --zl-spacing-13: 6rem;\n --zl-spacing-14: 6.5rem;\n --zl-spacing-15: 7rem;\n --zl-spacing-16: 7.5rem;\n --zl-radius-xs: 0.25rem;\n --zl-radius-s: 0.5rem;\n --zl-radius-m: 0.75rem;\n --zl-radius-l: 1rem;\n --zl-radius-xl: 1.5rem;\n --zl-font-family-sans: \"Arimo\", system-ui, -apple-system, \"Segoe UI\", Helvetica, Arial, sans-serif;\n --zl-font-family-heading: \"Arimo\", system-ui, -apple-system, \"Segoe UI\", Helvetica, Arial, sans-serif;\n --zl-font-family-mono: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n --zl-duration-instant: 0ms;\n --zl-duration-fast: 120ms;\n --zl-duration-base: 200ms;\n --zl-duration-slow: 320ms;\n --zl-easing-standard: cubic-bezier(0.2, 0, 0, 1);\n --zl-easing-decelerate: cubic-bezier(0, 0, 0, 1);\n --zl-easing-accelerate: cubic-bezier(0.3, 0, 1, 1);\n --zl-focus-width: 2px;\n --zl-focus-offset: 2px;\n --zl-focus-color: #f4f4f6;\n --zl-breakpoint-xs: 26.5625rem;\n --zl-breakpoint-sm: 40rem;\n --zl-breakpoint-md: 48rem;\n --zl-breakpoint-lg: 64rem;\n --zl-breakpoint-xl: 80rem;\n --zl-breakpoint-2xl: 96rem;\n --zl-breakpoint-3xl: 120rem;\n --zl-breakpoint-4xl: 160rem;\n --zl-container-auth-card: 24rem;\n --zl-container-page: 80rem;\n}\n\n[data-theme=\"light\"] {\n
|
|
949
|
+
const tokensCss = "/* AUTOGENERATED by packages/design-tokens/scripts/build.ts — do not edit by hand.\n Source: src/generated/figma.tokens.json + src/overrides.ts.\n Re-run: moon run design-tokens:generate */\n\n:root,\n[data-theme=\"dark\"] {\n --zl-color-surface-default-black: #0f0f11;\n --zl-color-surface-default-white: #f4f4f6;\n --zl-color-surface-default-primary-gray: #252528;\n --zl-color-surface-default-secondary-gray: #37373a;\n --zl-color-text-primary-white: #f4f4f6;\n --zl-color-text-secondary-gray: #bfbfcf;\n --zl-color-text-button-default: #0f0f11;\n --zl-color-text-button-invert: #f4f4f6;\n --zl-color-text-disabled: #686883;\n --zl-color-text-error: #ea4f70;\n --zl-color-text-success: #33a779;\n --zl-color-text-subtitle-pink: #f48aa0;\n --zl-color-text-subtitle-purple: #bba5e4;\n --zl-color-text-subtitle-gray: #bfbfcf;\n --zl-color-text-subtitle-orange: #f25543;\n --zl-color-icon-default-black: #0f0f11;\n --zl-color-icon-default-white: #f4f4f6;\n --zl-color-icon-default-gray: #bfbfcf;\n --zl-color-icon-default-pink: #f48aa0;\n --zl-color-icon-default-purple: #bba5e4;\n --zl-color-icon-default-orange: #f25543;\n --zl-color-icon-disabled: #686883;\n --zl-color-icon-error: #ea4f70;\n --zl-color-icon-success: #33a779;\n --zl-color-border-default-black: #252528;\n --zl-color-border-default-white: #f4f4f6;\n --zl-color-border-default-gray-100: #37373a;\n --zl-color-border-default-gray-200: #484a57;\n --zl-color-border-default-gray-300: #686883;\n --zl-color-border-default-gray-400: #82829c;\n --zl-color-border-error: #ea4f70;\n --zl-color-border-success: #33a779;\n --zl-color-gray-50: #0f0f11;\n --zl-color-gray-75: #252528;\n --zl-color-gray-100: #37373a;\n --zl-color-gray-200: #484a57;\n --zl-color-gray-300: #686883;\n --zl-color-gray-400: #82829c;\n --zl-color-gray-500: #9e9eb2;\n --zl-color-gray-600: #cfcfde;\n --zl-color-gray-700: #bfbfcf;\n --zl-color-gray-800: #e1e1e7;\n --zl-color-gray-900: #f4f4f6;\n --zl-spacing-01: 0.25rem;\n --zl-spacing-02: 0.5rem;\n --zl-spacing-03: 1rem;\n --zl-spacing-04: 1.5rem;\n --zl-spacing-05: 2rem;\n --zl-spacing-06: 1.5rem;\n --zl-spacing-07: 2.5rem;\n --zl-spacing-08: 3rem;\n --zl-spacing-09: 3.5rem;\n --zl-spacing-10: 4rem;\n --zl-spacing-11: 5rem;\n --zl-spacing-12: 5.5rem;\n --zl-spacing-13: 6rem;\n --zl-spacing-14: 6.5rem;\n --zl-spacing-15: 7rem;\n --zl-spacing-16: 7.5rem;\n --zl-radius-xs: 0.25rem;\n --zl-radius-s: 0.5rem;\n --zl-radius-m: 0.75rem;\n --zl-radius-l: 1rem;\n --zl-radius-xl: 1.5rem;\n --zl-font-family-sans: \"Arimo\", system-ui, -apple-system, \"Segoe UI\", Helvetica, Arial, sans-serif;\n --zl-font-family-heading: \"Arimo\", system-ui, -apple-system, \"Segoe UI\", Helvetica, Arial, sans-serif;\n --zl-font-family-mono: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n --zl-duration-instant: 0ms;\n --zl-duration-fast: 120ms;\n --zl-duration-base: 200ms;\n --zl-duration-slow: 320ms;\n --zl-easing-standard: cubic-bezier(0.2, 0, 0, 1);\n --zl-easing-decelerate: cubic-bezier(0, 0, 0, 1);\n --zl-easing-accelerate: cubic-bezier(0.3, 0, 1, 1);\n --zl-focus-width: 2px;\n --zl-focus-offset: 2px;\n --zl-focus-color: #f4f4f6;\n --zl-breakpoint-xs: 26.5625rem;\n --zl-breakpoint-sm: 40rem;\n --zl-breakpoint-md: 48rem;\n --zl-breakpoint-lg: 64rem;\n --zl-breakpoint-xl: 80rem;\n --zl-breakpoint-2xl: 96rem;\n --zl-breakpoint-3xl: 120rem;\n --zl-breakpoint-4xl: 160rem;\n --zl-container-auth-card: 24rem;\n --zl-container-page: 80rem;\n}\n\n[data-theme=\"light\"] {\n}\n";
|
|
948
950
|
//#endregion
|
|
949
951
|
//#region src/styles/tokens.ts
|
|
950
952
|
function wrap(node) {
|
|
@@ -2056,6 +2058,36 @@ var text_field_host_default = ":host {\n display: block;\n}\n\n::slotted([slot=
|
|
|
2056
2058
|
//#region ../shared-component-styles/src/text-field.css?inline
|
|
2057
2059
|
var text_field_default = ".zr-field {\n align-items: stretch;\n gap: var(--zl-spacing-02, .5rem);\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n flex-direction: column;\n display: flex;\n}\n\n.zr-field__label-row {\n justify-content: space-between;\n align-items: flex-start;\n gap: var(--zl-spacing-02, .5rem);\n width: 100%;\n display: flex;\n}\n\n.zr-field__label {\n align-items: center;\n gap: var(--zl-spacing-01, .25rem);\n color: var(--zl-color-text-primary-white, #f4f4f6);\n font-size: 1rem;\n font-weight: 600;\n line-height: 1.5rem;\n display: inline-flex;\n}\n\n.zr-field__link {\n color: var(--zl-color-icon-default-purple, #bba5e4);\n flex-shrink: 0;\n font-size: .875rem;\n font-weight: 400;\n line-height: 1.25rem;\n text-decoration: none;\n}\n\n.zr-field__link:hover {\n text-decoration: underline;\n}\n\n.zr-field__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 border-radius: 2px;\n}\n\n.zr-field__required {\n color: var(--zl-color-text-error, #ea4f70);\n}\n\n.zr-field--disabled .zr-field__label {\n color: var(--zl-color-text-disabled, #686883);\n}\n\n.zr-field__wrap {\n align-items: center;\n gap: var(--zl-spacing-02, .5rem);\n padding-block: var(--zl-spacing-02, .5rem);\n padding-inline: var(--zl-spacing-03, 1rem);\n border-radius: var(--zl-radius-xs, .25rem);\n border: 1px solid var(--zl-color-border-default-gray-300, #686883);\n height: 2.5rem;\n transition: border-color var(--zl-duration-fast, .12s) var(--zl-easing-standard, cubic-bezier(.2, 0, 0, 1)),\n background-color var(--zl-duration-fast, .12s) var(--zl-easing-standard, cubic-bezier(.2, 0, 0, 1));\n box-sizing: border-box;\n background: none;\n display: flex;\n position: relative;\n overflow: visible;\n}\n\n.zr-field__wrap--trailing {\n padding-inline-end: 2.5rem;\n}\n\n.zr-field:not(.zr-field--disabled) .zr-field__wrap:hover:not(:focus-within) {\n border-color: var(--zl-color-gray-500, #9e9eb2);\n background-color: color-mix(in srgb, var(--zl-color-text-primary-white, #f4f4f6) 10%, transparent);\n}\n\n.zr-field:not(.zr-field--invalid):not(.zr-field--disabled):not(.zr-field--success) .zr-field__wrap:has(.zr-field__input:not(:placeholder-shown)):not(:focus-within) {\n border-color: var(--zl-color-border-default-gray-400, #82829c);\n}\n\n.zr-field:not(.zr-field--invalid):not(.zr-field--disabled):not(.zr-field--success) .zr-field__wrap:focus-within {\n border-color: var(--zl-color-border-default-white, #f4f4f6);\n outline: none;\n}\n\n.zr-field--invalid .zr-field__wrap {\n border-color: var(--zl-color-border-error, #ea4f70);\n}\n\n.zr-field--invalid .zr-field__wrap:focus-within {\n border-color: var(--zl-color-border-error, #ea4f70);\n outline: none;\n}\n\n.zr-field--success .zr-field__wrap {\n border-color: var(--zl-color-border-success, #33a779);\n}\n\n.zr-field--success .zr-field__wrap:focus-within {\n border-color: var(--zl-color-border-success, #33a779);\n outline: none;\n}\n\n.zr-field--disabled .zr-field__wrap {\n border-color: var(--zl-color-border-default-gray-200, #484a57);\n}\n\n.zr-field__input {\n min-width: 0;\n height: 100%;\n color: var(--zl-color-text-primary-white, #f4f4f6);\n background: none;\n border: 0;\n outline: none;\n flex: auto;\n padding: 0;\n font-family: inherit;\n font-size: .875rem;\n font-weight: 400;\n line-height: 1.25rem;\n}\n\n.zr-field__input[type=\"password\"] {\n letter-spacing: .125rem;\n}\n\n.zr-field__input::placeholder {\n color: var(--zl-color-text-secondary-gray, #bfbfcf);\n}\n\n.zr-field__input:focus, .zr-field__input:focus-visible {\n outline: none;\n}\n\n.zr-field__input:disabled {\n cursor: not-allowed;\n color: var(--zl-color-text-disabled, #686883);\n}\n\n.zr-field--disabled .zr-field__wrap:hover {\n border-color: var(--zl-color-border-default-gray-200, #484a57);\n background-color: #0000;\n}\n\n.zr-field__trailing {\n width: 1rem;\n height: 1rem;\n color: var(--zl-color-text-secondary-gray, #bfbfcf);\n justify-content: center;\n align-items: center;\n display: inline-flex;\n position: absolute;\n top: 50%;\n right: .9375rem;\n transform: translateY(-50%);\n}\n\n.zr-field__trailing-action {\n color: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n justify-content: center;\n align-items: center;\n margin: 0;\n padding: 0;\n line-height: 0;\n display: inline-flex;\n}\n\n.zr-field__trailing-action:focus-visible {\n outline: var(--zl-focus-width, 2px) solid var(--zl-focus-color, #f4f4f6);\n outline-offset: var(--zl-focus-offset, 2px);\n border-radius: 2px;\n}\n\n.zr-field__trailing-action:disabled {\n cursor: not-allowed;\n}\n\n.zr-field--invalid .zr-field__trailing {\n color: var(--zl-color-text-error, #ea4f70);\n}\n\n.zr-field--success .zr-field__trailing {\n color: var(--zl-color-text-success, #33a779);\n}\n\n.zr-field__help {\n color: var(--zl-color-text-primary-white, #f4f4f6);\n font-size: .75rem;\n line-height: 1rem;\n}\n\n.zr-field__success {\n color: var(--zl-color-text-success, #33a779);\n font-size: .75rem;\n line-height: 1rem;\n}\n\n.zr-field__error {\n color: var(--zl-color-text-error, #ea4f70);\n font-size: .75rem;\n line-height: 1rem;\n}\n";
|
|
2058
2060
|
//#endregion
|
|
2061
|
+
//#region src/internal/hook-name.ts
|
|
2062
|
+
/**
|
|
2063
|
+
* Reserved auth-method credential prefix of flow field names. Mirrors
|
|
2064
|
+
* `AUTH_METHOD_PREFIX` in `@zitadel/config` (`src/validate.ts`), which
|
|
2065
|
+
* owns the wire dialect; components has no dependency on that package,
|
|
2066
|
+
* so the literal is repeated here.
|
|
2067
|
+
*/
|
|
2068
|
+
const AUTH_METHOD_PREFIX = "x-auth-methods#";
|
|
2069
|
+
/**
|
|
2070
|
+
* Normalises a flow field name into its automation-hook token.
|
|
2071
|
+
*
|
|
2072
|
+
* Auth-method credential fields arrive from the flow engine as
|
|
2073
|
+
* `x-auth-methods#<method>` (e.g. `x-auth-methods#password`), but the
|
|
2074
|
+
* documented `data-testid` hooks are method-named
|
|
2075
|
+
* (`zitadel-field-password`, `zitadel-input-password` — see
|
|
2076
|
+
* packages/components/README.md and apps/cli/SKILLS.md). This helper
|
|
2077
|
+
* feeds both hook construction sites: the bundled template's `testid`
|
|
2078
|
+
* Liquid filter and `<zl-field>`'s native-input testid.
|
|
2079
|
+
*
|
|
2080
|
+
* Only the reserved credential prefix is stripped — user-schema
|
|
2081
|
+
* property names are tenant-authored arbitrary strings, so a `#`
|
|
2082
|
+
* anywhere else is part of the name and stays in the hook. The
|
|
2083
|
+
* after-prefix token is used verbatim (no case or spacing changes),
|
|
2084
|
+
* and the field's `name` attribute always keeps the raw wire/form key.
|
|
2085
|
+
*/
|
|
2086
|
+
function hookName(field) {
|
|
2087
|
+
if (field.startsWith(AUTH_METHOD_PREFIX) && field.length > 15) return field.slice(15);
|
|
2088
|
+
return field;
|
|
2089
|
+
}
|
|
2090
|
+
//#endregion
|
|
2059
2091
|
//#region src/atoms/zl-field.ts
|
|
2060
2092
|
let ZlField = class ZlField extends i$2 {
|
|
2061
2093
|
static formAssociated = true;
|
|
@@ -2395,7 +2427,7 @@ let ZlField = class ZlField extends i$2 {
|
|
|
2395
2427
|
}
|
|
2396
2428
|
};
|
|
2397
2429
|
nativeInputTestId() {
|
|
2398
|
-
if (this.name) return `zitadel-input-${this.name}`;
|
|
2430
|
+
if (this.name) return `zitadel-input-${hookName(this.name)}`;
|
|
2399
2431
|
if (this.testId) return `${this.testId}-input`;
|
|
2400
2432
|
}
|
|
2401
2433
|
dispatchNativeInput(source) {
|
|
@@ -2583,6 +2615,13 @@ function base64UrlToBuffer(base64url) {
|
|
|
2583
2615
|
}
|
|
2584
2616
|
//#endregion
|
|
2585
2617
|
//#region src/atoms/zl-passkey.ts
|
|
2618
|
+
/**
|
|
2619
|
+
* Slack subtracted from `options.timeout` when classifying a
|
|
2620
|
+
* `NotAllowedError` as a timeout: browsers clamp and fire the deadline
|
|
2621
|
+
* slightly early, so an exact comparison would label real timeouts as
|
|
2622
|
+
* user cancellations.
|
|
2623
|
+
*/
|
|
2624
|
+
const TIMEOUT_GRACE_MS = 1e3;
|
|
2586
2625
|
let ZlPasskey = class ZlPasskey extends i$2 {
|
|
2587
2626
|
#_ceremony_accessor_storage = "authenticate";
|
|
2588
2627
|
get ceremony() {
|
|
@@ -2619,7 +2658,37 @@ let ZlPasskey = class ZlPasskey extends i$2 {
|
|
|
2619
2658
|
set manual(value) {
|
|
2620
2659
|
this.#_manual_accessor_storage = value;
|
|
2621
2660
|
}
|
|
2661
|
+
#_pendingLabel_accessor_storage = "Waiting for your passkey…";
|
|
2662
|
+
get pendingLabel() {
|
|
2663
|
+
return this.#_pendingLabel_accessor_storage;
|
|
2664
|
+
}
|
|
2665
|
+
set pendingLabel(value) {
|
|
2666
|
+
this.#_pendingLabel_accessor_storage = value;
|
|
2667
|
+
}
|
|
2668
|
+
#_cancelLabel_accessor_storage = "Cancel";
|
|
2669
|
+
get cancelLabel() {
|
|
2670
|
+
return this.#_cancelLabel_accessor_storage;
|
|
2671
|
+
}
|
|
2672
|
+
set cancelLabel(value) {
|
|
2673
|
+
this.#_cancelLabel_accessor_storage = value;
|
|
2674
|
+
}
|
|
2675
|
+
#_silent_accessor_storage = false;
|
|
2676
|
+
get silent() {
|
|
2677
|
+
return this.#_silent_accessor_storage;
|
|
2678
|
+
}
|
|
2679
|
+
set silent(value) {
|
|
2680
|
+
this.#_silent_accessor_storage = value;
|
|
2681
|
+
}
|
|
2682
|
+
#_pending_accessor_storage = false;
|
|
2683
|
+
get pending() {
|
|
2684
|
+
return this.#_pending_accessor_storage;
|
|
2685
|
+
}
|
|
2686
|
+
set pending(value) {
|
|
2687
|
+
this.#_pending_accessor_storage = value;
|
|
2688
|
+
}
|
|
2622
2689
|
abortController = null;
|
|
2690
|
+
/** `Date.now()` at ceremony start, for timeout classification on reject. */
|
|
2691
|
+
startedAt = 0;
|
|
2623
2692
|
connectedCallback() {
|
|
2624
2693
|
super.connectedCallback();
|
|
2625
2694
|
if (!this.manual && this.options) this.startCeremony();
|
|
@@ -2630,11 +2699,14 @@ let ZlPasskey = class ZlPasskey extends i$2 {
|
|
|
2630
2699
|
}
|
|
2631
2700
|
/**
|
|
2632
2701
|
* Abort any in-flight WebAuthn ceremony. Called automatically when the
|
|
2633
|
-
* component disconnects (step change, page navigation)
|
|
2702
|
+
* component disconnects (step change, page navigation) and by the
|
|
2703
|
+
* pending UI's cancel button. Clears `pending` immediately — the
|
|
2704
|
+
* ceremony's own `finally` no longer owns it (its controller is gone).
|
|
2634
2705
|
*/
|
|
2635
2706
|
abort() {
|
|
2636
2707
|
this.abortController?.abort();
|
|
2637
2708
|
this.abortController = null;
|
|
2709
|
+
this.pending = false;
|
|
2638
2710
|
}
|
|
2639
2711
|
/**
|
|
2640
2712
|
* Trigger the WebAuthn ceremony. Resolves when the ceremony completes
|
|
@@ -2650,7 +2722,11 @@ let ZlPasskey = class ZlPasskey extends i$2 {
|
|
|
2650
2722
|
return;
|
|
2651
2723
|
}
|
|
2652
2724
|
this.abort();
|
|
2653
|
-
|
|
2725
|
+
const controller = new AbortController();
|
|
2726
|
+
this.abortController = controller;
|
|
2727
|
+
this.pending = true;
|
|
2728
|
+
this.startedAt = Date.now();
|
|
2729
|
+
this.emitStarted();
|
|
2654
2730
|
try {
|
|
2655
2731
|
const credential = this.ceremony === "register" ? await this.createCredential() : await this.getCredential();
|
|
2656
2732
|
if (!credential) {
|
|
@@ -2661,9 +2737,33 @@ let ZlPasskey = class ZlPasskey extends i$2 {
|
|
|
2661
2737
|
this.emitResult(proof);
|
|
2662
2738
|
} catch (error) {
|
|
2663
2739
|
const aborted = error instanceof DOMException && (error.name === "AbortError" || error.name === "NotAllowedError");
|
|
2664
|
-
|
|
2740
|
+
const timeoutMs = typeof this.options?.timeout === "number" ? this.options.timeout : null;
|
|
2741
|
+
const timedOut = error instanceof DOMException && error.name === "NotAllowedError" && timeoutMs !== null && Date.now() - this.startedAt >= timeoutMs - TIMEOUT_GRACE_MS;
|
|
2742
|
+
this.emitError(error instanceof Error ? error.message : "WebAuthn ceremony failed.", aborted, timedOut);
|
|
2743
|
+
} finally {
|
|
2744
|
+
if (this.abortController === controller) this.pending = false;
|
|
2665
2745
|
}
|
|
2666
2746
|
}
|
|
2747
|
+
handleCancelSubmit = (event) => {
|
|
2748
|
+
event.stopPropagation();
|
|
2749
|
+
this.abort();
|
|
2750
|
+
};
|
|
2751
|
+
render() {
|
|
2752
|
+
if (!this.pending || this.silent) return A;
|
|
2753
|
+
return b`
|
|
2754
|
+
<div class="zl-passkey-pending" data-testid="zitadel-passkey-pending">
|
|
2755
|
+
<zl-alert severity="info">${this.pendingLabel}</zl-alert>
|
|
2756
|
+
<zl-button
|
|
2757
|
+
hierarchy="secondary"
|
|
2758
|
+
type="button"
|
|
2759
|
+
block
|
|
2760
|
+
data-testid="zitadel-passkey-cancel"
|
|
2761
|
+
label=${this.cancelLabel}
|
|
2762
|
+
@zl-submit=${this.handleCancelSubmit}
|
|
2763
|
+
></zl-button>
|
|
2764
|
+
</div>
|
|
2765
|
+
`;
|
|
2766
|
+
}
|
|
2667
2767
|
/**
|
|
2668
2768
|
* `navigator.credentials.get()` — authentication ceremony.
|
|
2669
2769
|
*/
|
|
@@ -2765,11 +2865,12 @@ let ZlPasskey = class ZlPasskey extends i$2 {
|
|
|
2765
2865
|
detail
|
|
2766
2866
|
}));
|
|
2767
2867
|
}
|
|
2768
|
-
emitError(message, aborted) {
|
|
2868
|
+
emitError(message, aborted, timedOut = false) {
|
|
2769
2869
|
const detail = {
|
|
2770
2870
|
challenge_id: this.challengeId,
|
|
2771
2871
|
error: message,
|
|
2772
|
-
aborted
|
|
2872
|
+
aborted,
|
|
2873
|
+
timed_out: timedOut
|
|
2773
2874
|
};
|
|
2774
2875
|
this.dispatchEvent(new CustomEvent("zl-passkey-error", {
|
|
2775
2876
|
bubbles: true,
|
|
@@ -2777,7 +2878,23 @@ let ZlPasskey = class ZlPasskey extends i$2 {
|
|
|
2777
2878
|
detail
|
|
2778
2879
|
}));
|
|
2779
2880
|
}
|
|
2780
|
-
|
|
2881
|
+
emitStarted() {
|
|
2882
|
+
const detail = {
|
|
2883
|
+
challenge_id: this.challengeId,
|
|
2884
|
+
method: this.method
|
|
2885
|
+
};
|
|
2886
|
+
this.dispatchEvent(new CustomEvent("zl-passkey-started", {
|
|
2887
|
+
bubbles: true,
|
|
2888
|
+
composed: true,
|
|
2889
|
+
detail
|
|
2890
|
+
}));
|
|
2891
|
+
}
|
|
2892
|
+
/**
|
|
2893
|
+
* Light DOM on purpose: the pending UI must land inside the
|
|
2894
|
+
* orchestrator's shadow root (where the step's adopted styles apply)
|
|
2895
|
+
* without altering the `unsafeHTML` step markup — runtime children don't
|
|
2896
|
+
* participate in its string comparison, so the ceremony never restarts.
|
|
2897
|
+
*/
|
|
2781
2898
|
createRenderRoot() {
|
|
2782
2899
|
return this;
|
|
2783
2900
|
}
|
|
@@ -2802,6 +2919,10 @@ __decorate([n$1({
|
|
|
2802
2919
|
})], ZlPasskey.prototype, "options", null);
|
|
2803
2920
|
__decorate([n$1({ type: String })], ZlPasskey.prototype, "method", null);
|
|
2804
2921
|
__decorate([n$1({ type: Boolean })], ZlPasskey.prototype, "manual", null);
|
|
2922
|
+
__decorate([n$1({ attribute: "pending-label" })], ZlPasskey.prototype, "pendingLabel", null);
|
|
2923
|
+
__decorate([n$1({ attribute: "cancel-label" })], ZlPasskey.prototype, "cancelLabel", null);
|
|
2924
|
+
__decorate([n$1({ type: Boolean })], ZlPasskey.prototype, "silent", null);
|
|
2925
|
+
__decorate([r$1()], ZlPasskey.prototype, "pending", null);
|
|
2805
2926
|
ZlPasskey = __decorate([t$4("zl-passkey")], ZlPasskey);
|
|
2806
2927
|
const zlPasskeyManifest = {
|
|
2807
2928
|
tag: "zl-passkey",
|
|
@@ -2811,11 +2932,18 @@ const zlPasskeyManifest = {
|
|
|
2811
2932
|
"method",
|
|
2812
2933
|
"challenge-id",
|
|
2813
2934
|
"options",
|
|
2814
|
-
"manual"
|
|
2935
|
+
"manual",
|
|
2936
|
+
"pending-label",
|
|
2937
|
+
"cancel-label",
|
|
2938
|
+
"silent"
|
|
2815
2939
|
],
|
|
2816
2940
|
parts: [],
|
|
2817
2941
|
slots: [],
|
|
2818
|
-
events: [
|
|
2942
|
+
events: [
|
|
2943
|
+
"zl-passkey-result",
|
|
2944
|
+
"zl-passkey-error",
|
|
2945
|
+
"zl-passkey-started"
|
|
2946
|
+
]
|
|
2819
2947
|
};
|
|
2820
2948
|
//#endregion
|
|
2821
2949
|
//#region ../shared-component-styles/src/lit/pill-host.css?inline
|
|
@@ -3365,6 +3493,37 @@ function safeJsonParse(text) {
|
|
|
3365
3493
|
return { raw: text };
|
|
3366
3494
|
}
|
|
3367
3495
|
}
|
|
3496
|
+
/**
|
|
3497
|
+
* Extracts the server's `{code, message, details}` envelope from an
|
|
3498
|
+
* {@link ApiError} into a display string. Falls back to the fetch layer's
|
|
3499
|
+
* `"METHOD url returned N"` when the body isn't shaped like an envelope, so
|
|
3500
|
+
* transport-level failures (HTML from a proxy, empty 5xx) still say
|
|
3501
|
+
* something.
|
|
3502
|
+
*/
|
|
3503
|
+
function apiErrorMessage(error) {
|
|
3504
|
+
const body = error.body;
|
|
3505
|
+
if (!isRecord(body)) return error.message;
|
|
3506
|
+
const serverMessage = typeof body.message === "string" ? body.message : void 0;
|
|
3507
|
+
const detail = pickDetailString(body.details);
|
|
3508
|
+
if (!serverMessage) return error.message;
|
|
3509
|
+
return detail ? `${serverMessage}: ${detail}` : serverMessage;
|
|
3510
|
+
}
|
|
3511
|
+
/**
|
|
3512
|
+
* Reads the innermost human-readable string out of the spec's error
|
|
3513
|
+
* `details` field. Handles both `details: "..."` and the nested
|
|
3514
|
+
* `details: { details: "..." }` shape the platform emits for validation
|
|
3515
|
+
* failures.
|
|
3516
|
+
*/
|
|
3517
|
+
function pickDetailString(details) {
|
|
3518
|
+
if (typeof details === "string") return details;
|
|
3519
|
+
if (isRecord(details)) {
|
|
3520
|
+
if (typeof details.details === "string") return details.details;
|
|
3521
|
+
if (typeof details.message === "string") return details.message;
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
function isRecord(value) {
|
|
3525
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3526
|
+
}
|
|
3368
3527
|
//#endregion
|
|
3369
3528
|
//#region src/orchestrator/api-client.ts
|
|
3370
3529
|
const apiRequestInit = { credentials: "include" };
|
|
@@ -3401,7 +3560,7 @@ async function exchangeSession(api, body, params) {
|
|
|
3401
3560
|
* orchestrator's signed-in surfaces to render the user's identity.
|
|
3402
3561
|
*/
|
|
3403
3562
|
async function getSession$1(api) {
|
|
3404
|
-
return
|
|
3563
|
+
return api.getMySession(apiRequestInit);
|
|
3405
3564
|
}
|
|
3406
3565
|
/**
|
|
3407
3566
|
* Revokes the current session (`DELETE /sessions/me`) with credentials. The
|
|
@@ -3505,7 +3664,9 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
|
|
|
3505
3664
|
getMySession: () => getMySession,
|
|
3506
3665
|
getMyUser: () => getMyUser,
|
|
3507
3666
|
getOpenIDConfiguration: () => getOpenIDConfiguration,
|
|
3667
|
+
getPatchProjectUrl: () => getPatchProjectUrl,
|
|
3508
3668
|
getProject: () => getProject,
|
|
3669
|
+
getQueryProjectsUrl: () => getQueryProjectsUrl,
|
|
3509
3670
|
getReady: () => getReady,
|
|
3510
3671
|
getRevokeMySessionUrl: () => getRevokeMySessionUrl,
|
|
3511
3672
|
getRevokeSessionUrl: () => getRevokeSessionUrl,
|
|
@@ -3527,6 +3688,8 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
|
|
|
3527
3688
|
listSchemas: () => listSchemas,
|
|
3528
3689
|
listSessions: () => listSessions,
|
|
3529
3690
|
listUsers: () => listUsers,
|
|
3691
|
+
patchProject: () => patchProject,
|
|
3692
|
+
queryProjects: () => queryProjects,
|
|
3530
3693
|
revokeMySession: () => revokeMySession,
|
|
3531
3694
|
revokeSession: () => revokeSession,
|
|
3532
3695
|
revokeToken: () => revokeToken,
|
|
@@ -4065,6 +4228,23 @@ const createProject = async (createProjectBody, options) => {
|
|
|
4065
4228
|
body: JSON.stringify(createProjectBody)
|
|
4066
4229
|
});
|
|
4067
4230
|
};
|
|
4231
|
+
const getQueryProjectsUrl = () => {
|
|
4232
|
+
return `${getProxyPath()}/projects/query`;
|
|
4233
|
+
};
|
|
4234
|
+
/**
|
|
4235
|
+
* @summary Query projects
|
|
4236
|
+
*/
|
|
4237
|
+
const queryProjects = async (queryProjectsBody, options) => {
|
|
4238
|
+
return customFetch(getQueryProjectsUrl(), {
|
|
4239
|
+
...options,
|
|
4240
|
+
method: "POST",
|
|
4241
|
+
headers: {
|
|
4242
|
+
"Content-Type": "application/json",
|
|
4243
|
+
...options?.headers
|
|
4244
|
+
},
|
|
4245
|
+
body: JSON.stringify(queryProjectsBody)
|
|
4246
|
+
});
|
|
4247
|
+
};
|
|
4068
4248
|
const getGetProjectUrl = (projectId) => {
|
|
4069
4249
|
return `${getProxyPath()}/projects/${projectId}`;
|
|
4070
4250
|
};
|
|
@@ -4079,6 +4259,25 @@ const getProject = async (projectId, options) => {
|
|
|
4079
4259
|
method: "GET"
|
|
4080
4260
|
});
|
|
4081
4261
|
};
|
|
4262
|
+
const getPatchProjectUrl = (projectId) => {
|
|
4263
|
+
return `${getProxyPath()}/projects/${projectId}`;
|
|
4264
|
+
};
|
|
4265
|
+
/**
|
|
4266
|
+
* Updates the state of a project.
|
|
4267
|
+
|
|
4268
|
+
* @summary Update project
|
|
4269
|
+
*/
|
|
4270
|
+
const patchProject = async (projectId, patchProjectBody, options) => {
|
|
4271
|
+
return customFetch(getPatchProjectUrl(projectId), {
|
|
4272
|
+
...options,
|
|
4273
|
+
method: "PATCH",
|
|
4274
|
+
headers: {
|
|
4275
|
+
"Content-Type": "application/json",
|
|
4276
|
+
...options?.headers
|
|
4277
|
+
},
|
|
4278
|
+
body: JSON.stringify(patchProjectBody)
|
|
4279
|
+
});
|
|
4280
|
+
};
|
|
4082
4281
|
const getCreateSessionUrl = () => {
|
|
4083
4282
|
return `${getProxyPath()}/sessions`;
|
|
4084
4283
|
};
|
|
@@ -9506,7 +9705,7 @@ function lookup(locale, key) {
|
|
|
9506
9705
|
}
|
|
9507
9706
|
//#endregion
|
|
9508
9707
|
//#region src/orchestrator/templates/default.liquid
|
|
9509
|
-
var default_default = "<zl-page-shell data-zl-template-root>\n {% if branding.logo_url %}\n <img slot=\"header\" class=\"zl-card-logo\" src=\"{{ branding.logo_url }}\" alt=\"\" />\n {% endif %}\n <zl-card>\n <h1 slot=\"header\" class=\"zl-card-title\">{{ step.texts.title_key | t: identity.display_name }}</h1>\n {% if step.texts.description_key %}\n {% assign card_subtitle = step.texts.description_key | t: identity.display_name %}\n {% if card_subtitle != \"\" %}\n <p slot=\"header\" class=\"zl-card-subtitle\">{{ card_subtitle }}</p>\n {% endif %}\n {% endif %}\n\n {% if errors and errors.size > 0 %}\n {% for err in errors %}\n {% if err | formLevelError %}\n {% assign alert_heading = err.text_key | alertHeading %}\n {% assign alert_body = err.text_key | alertBody %}\n <zl-alert severity=\"error\"{% if alert_heading != \"\" %} heading=\"{{ alert_heading | t }}\"{% endif %}>\n {% if alert_body != \"\" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}\n </zl-alert>\n {% endif %}\n {% endfor %}\n {% endif %}\n\n {% for f in fields %}\n {% assign field_placeholder = f.text_key | fieldPlaceholder %}\n {% assign field_help = f.text_key | fieldHelp %}\n {% assign field_err = f.name | fieldError: errors %}\n {% case f.type %}\n {% when 'checkbox' %}\n <zl-checkbox\n name=\"{{ f.name }}\"\n data-testid=\"zitadel-field-{{ f.name }}\"\n label=\"{{ f.text_key | t }}\"\n value=\"true\"\n {% if f.value %}checked{% endif %}\n {% if f.required %}required{% endif %}\n ></zl-checkbox>\n {% when 'select' %}\n <zl-select\n name=\"{{ f.name }}\"\n data-testid=\"zitadel-field-{{ f.name }}\"\n label=\"{{ f.text_key | t }}\"\n value=\"{{ f.value }}\"\n options='{{ f.validation.enum | selectOptions | json }}'\n {% if f.required %}required{% endif %}\n {% if field_placeholder != \"\" %}placeholder=\"{{ field_placeholder }}\"{% endif %}\n ></zl-select>\n {% else %}\n {% assign autocomplete_val = '' %}\n {% if f.name == 'email' %}\n {% assign autocomplete_val = 'email' %}\n {% elsif f.type == 'password' and step.name contains 'register' %}\n {% assign autocomplete_val = 'new-password' %}\n {% elsif f.type == 'password' %}\n {% assign autocomplete_val = 'current-password' %}\n {% endif %}\n <zl-field\n name=\"{{ f.name }}\"\n data-testid=\"zitadel-field-{{ f.name }}\"\n label=\"{{ f.text_key | t }}\"\n type=\"{{ f.type }}\"\n value=\"{{ f.value }}\"\n {% if autocomplete_val != '' %}autocomplete=\"{{ autocomplete_val }}\"{% endif %}\n {% if f.required %}required{% endif %}\n {% if field_placeholder != \"\" %}placeholder=\"{{ field_placeholder }}\"{% endif %}\n {% if field_err != \"\" %}invalid error=\"{{ field_err }}\"{% endif %}\n >\n {% if field_help != \"\" %}\n <span slot=\"help\">{{ field_help }}</span>\n {% endif %}\n </zl-field>\n {% endcase %}\n {% endfor %}\n\n {% assign recover = actions | where: \"name\", \"recover\" | first %}\n {% if recover %}\n <p class=\"zl-card-forgot\">\n <a href=\"#\" class=\"zl-card-forgot__link\" data-action=\"recover\">{{ recover.text_key | t }}</a>\n </p>\n {% endif %}\n\n {% for a in actions %}\n {% if a.primary %}\n <zl-button\n hierarchy=\"primary\"\n size=\"medium\"\n type=\"submit\"\n block\n action=\"{{ a.name }}\"\n data-testid=\"zitadel-action-{{ a.name }}\"\n label=\"{{ a.text_key | t }}\"\n {% if loading %}loading{% endif %}\n ></zl-button>\n {% endif %}\n {% endfor %}\n\n {% for a in actions %}\n {% unless a.primary or a.name == 'passkey' or a.name == 'register' or a.name == 'sign_in' or a.name == 'recover' %}\n <zl-button\n hierarchy=\"secondary\"\n size=\"medium\"\n type=\"button\"\n block\n action=\"{{ a.name }}\"\n data-testid=\"zitadel-action-{{ a.name }}\"\n label=\"{{ a.text_key | t }}\"\n ></zl-button>\n {% endunless %}\n {% endfor %}\n\n {% assign passkey = actions | where: \"name\", \"passkey\" | first %}\n {% if passkey %}\n {% unless passkey.primary %}\n <zl-button\n hierarchy=\"secondary\"\n size=\"medium\"\n block\n action=\"passkey\"\n data-testid=\"zitadel-action-passkey\"\n label=\"{{ passkey.text_key | t }}\"\n ></zl-button>\n {% endunless %}\n {% endif %}\n\n\n {% assign register = actions | where: \"name\", \"register\" | first %}\n {% if register %}\n <p class=\"zl-card-nav\">\n {{ 'identifier.action.register.lead' | t }}<a href=\"#\" class=\"zl-card-nav__link\" data-action=\"register\" data-testid=\"zitadel-action-register-link\">{{ 'identifier.action.register.link' | t }}</a>\n </p>\n {% endif %}\n\n {% assign sign_in = actions | where: \"name\", \"sign_in\" | first %}\n {% if sign_in %}\n <p class=\"zl-card-nav\">\n {{ 'register.action.sign_in.lead' | t }}<a href=\"#\" class=\"zl-card-nav__link\" data-action=\"sign_in\" data-testid=\"zitadel-action-sign_in-link\">{{ 'register.action.sign_in.link' | t }}</a>\n </p>\n {% endif %}\n\n {% if challenge %}\n {% if challenge.method == \"passkey\" or challenge.method == \"passkey_register\" %}\n {% assign ceremony = \"authenticate\" %}\n {% assign submit_method = challenge.method %}\n {% if challenge.method == \"passkey_register\" or challenge.options.user %}\n {% assign ceremony = \"register\" %}\n {% assign submit_method = \"passkey_register\" %}\n {% endif %}\n <zl-passkey\n ceremony=\"{{ ceremony }}\"\n method=\"{{ submit_method }}\"\n challenge-id=\"{{ challenge.challenge_id }}\"\n options='{{ challenge.options | json }}'\n ></zl-passkey>\n {% endif %}\n {% endif %}\n\n {% mandatory_gates %}\n </zl-card>\n</zl-page-shell>\n";
|
|
9708
|
+
var default_default = "<zl-page-shell data-zl-template-root>\n {% if branding.logo_url %}\n <img slot=\"header\" class=\"zl-card-logo\" src=\"{{ branding.logo_url }}\" alt=\"\" />\n {% endif %}\n <zl-card>\n <h1 slot=\"header\" class=\"zl-card-title\">{{ step.texts.title_key | t: identity.display_name }}</h1>\n {% if step.texts.description_key %}\n {% assign card_subtitle = step.texts.description_key | t: identity.display_name %}\n {% if card_subtitle != \"\" %}\n <p slot=\"header\" class=\"zl-card-subtitle\">{{ card_subtitle }}</p>\n {% endif %}\n {% endif %}\n\n {% if errors and errors.size > 0 %}\n {% for err in errors %}\n {% if err | formLevelError %}\n {% assign alert_heading = err.text_key | alertHeading %}\n {% assign alert_body = err.text_key | alertBody %}\n <zl-alert severity=\"error\" dismissible data-zl-step-error{% if alert_heading != \"\" %} heading=\"{{ alert_heading | t }}\"{% endif %}>\n {% if alert_body != \"\" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}\n </zl-alert>\n {% endif %}\n {% endfor %}\n {% endif %}\n\n {% for f in fields %}\n {% assign field_placeholder = f.text_key | fieldPlaceholder %}\n {% assign field_help = f.text_key | fieldHelp %}\n {% assign field_err = f.name | fieldError: errors %}\n {% case f.type %}\n {% when 'checkbox' %}\n <zl-checkbox\n name=\"{{ f.name }}\"\n data-testid=\"zitadel-field-{{ f.name | testid }}\"\n label=\"{{ f.text_key | t }}\"\n value=\"true\"\n {% if f.value %}checked{% endif %}\n {% if f.required %}required{% endif %}\n ></zl-checkbox>\n {% when 'select' %}\n <zl-select\n name=\"{{ f.name }}\"\n data-testid=\"zitadel-field-{{ f.name | testid }}\"\n label=\"{{ f.text_key | t }}\"\n value=\"{{ f.value }}\"\n options='{{ f.validation.enum | selectOptions | json }}'\n {% if f.required %}required{% endif %}\n {% if field_placeholder != \"\" %}placeholder=\"{{ field_placeholder }}\"{% endif %}\n ></zl-select>\n {% else %}\n {% assign autocomplete_val = '' %}\n {% if f.name == 'email' %}\n {% assign autocomplete_val = 'email' %}\n {% elsif f.type == 'password' and step.name contains 'register' %}\n {% assign autocomplete_val = 'new-password' %}\n {% elsif f.type == 'password' %}\n {% assign autocomplete_val = 'current-password' %}\n {% endif %}\n <zl-field\n name=\"{{ f.name }}\"\n data-testid=\"zitadel-field-{{ f.name | testid }}\"\n label=\"{{ f.text_key | t }}\"\n type=\"{{ f.type }}\"\n value=\"{{ f.value }}\"\n {% if autocomplete_val != '' %}autocomplete=\"{{ autocomplete_val }}\"{% endif %}\n {% if f.required %}required{% endif %}\n {% if field_placeholder != \"\" %}placeholder=\"{{ field_placeholder }}\"{% endif %}\n {% if field_err != \"\" %}invalid error=\"{{ field_err }}\"{% endif %}\n >\n {% if field_help != \"\" %}\n <span slot=\"help\">{{ field_help }}</span>\n {% endif %}\n </zl-field>\n {% endcase %}\n {% endfor %}\n\n {% assign recover = actions | where: \"name\", \"recover\" | first %}\n {% if recover %}\n <p class=\"zl-card-forgot\">\n <a href=\"#\" class=\"zl-card-forgot__link\" data-action=\"recover\">{{ recover.text_key | t }}</a>\n </p>\n {% endif %}\n\n {% for a in actions %}\n {% if a.primary %}\n <zl-button\n hierarchy=\"primary\"\n size=\"medium\"\n type=\"submit\"\n block\n action=\"{{ a.name }}\"\n data-testid=\"zitadel-action-{{ a.name }}\"\n label=\"{{ a.text_key | t }}\"\n {% if loading %}loading{% endif %}\n ></zl-button>\n {% endif %}\n {% endfor %}\n\n {% for a in actions %}\n {% unless a.primary or a.name == 'passkey' or a.name == 'register' or a.name == 'sign_in' or a.name == 'recover' %}\n <zl-button\n hierarchy=\"secondary\"\n size=\"medium\"\n type=\"button\"\n block\n action=\"{{ a.name }}\"\n data-testid=\"zitadel-action-{{ a.name }}\"\n label=\"{{ a.text_key | t }}\"\n ></zl-button>\n {% endunless %}\n {% endfor %}\n\n {% assign passkey = actions | where: \"name\", \"passkey\" | first %}\n {% if passkey %}\n {% unless passkey.primary %}\n <zl-button\n hierarchy=\"secondary\"\n size=\"medium\"\n block\n action=\"passkey\"\n data-testid=\"zitadel-action-passkey\"\n label=\"{{ passkey.text_key | t }}\"\n ></zl-button>\n {% endunless %}\n {% endif %}\n\n\n {% assign register = actions | where: \"name\", \"register\" | first %}\n {% if register %}\n <p class=\"zl-card-nav\">\n {{ 'identifier.action.register.lead' | t }}<a href=\"#\" class=\"zl-card-nav__link\" data-action=\"register\" data-testid=\"zitadel-action-register-link\">{{ 'identifier.action.register.link' | t }}</a>\n </p>\n {% endif %}\n\n {% assign sign_in = actions | where: \"name\", \"sign_in\" | first %}\n {% if sign_in %}\n <p class=\"zl-card-nav\">\n {{ 'register.action.sign_in.lead' | t }}<a href=\"#\" class=\"zl-card-nav__link\" data-action=\"sign_in\" data-testid=\"zitadel-action-sign_in-link\">{{ 'register.action.sign_in.link' | t }}</a>\n </p>\n {% endif %}\n\n {% if challenge %}\n {% if challenge.method == \"passkey\" or challenge.method == \"passkey_register\" %}\n {% assign ceremony = \"authenticate\" %}\n {% assign submit_method = challenge.method %}\n {% if challenge.method == \"passkey_register\" or challenge.options.user %}\n {% assign ceremony = \"register\" %}\n {% assign submit_method = \"passkey_register\" %}\n {% endif %}\n <zl-passkey\n ceremony=\"{{ ceremony }}\"\n method=\"{{ submit_method }}\"\n challenge-id=\"{{ challenge.challenge_id }}\"\n options='{{ challenge.options | json }}'\n pending-label=\"{{ 'passkey.pending.status' | t }}\"\n cancel-label=\"{{ 'action.cancel' | t }}\"\n ></zl-passkey>\n {% endif %}\n {% endif %}\n\n {% mandatory_gates %}\n </zl-card>\n</zl-page-shell>\n";
|
|
9510
9709
|
//#endregion
|
|
9511
9710
|
//#region src/orchestrator/liquid.ts
|
|
9512
9711
|
/**
|
|
@@ -9525,6 +9724,21 @@ var default_default = "<zl-page-shell data-zl-template-root>\n {% if branding.l
|
|
|
9525
9724
|
* - Partials are loaded from an in-memory map; no filesystem access.
|
|
9526
9725
|
*/
|
|
9527
9726
|
const TEMPLATE_NAMES$1 = { default: "default" };
|
|
9727
|
+
/**
|
|
9728
|
+
* Maps `error.*` text keys to a field name (Figma inline-error
|
|
9729
|
+
* annotations). Shared by the template filters (`fieldError` routes these
|
|
9730
|
+
* inline, `formLevelError` suppresses their banner) and by
|
|
9731
|
+
* {@link localiseFlowErrorKeys}, which must downgrade them to a banner
|
|
9732
|
+
* message when the step doesn't render the mapped field.
|
|
9733
|
+
*/
|
|
9734
|
+
const fieldErrorKeys = {
|
|
9735
|
+
"error.email_required": "email",
|
|
9736
|
+
"error.email_invalid": "email",
|
|
9737
|
+
"error.email_exists": "email",
|
|
9738
|
+
"error.password_required": "password",
|
|
9739
|
+
"error.password_incorrect": "password",
|
|
9740
|
+
"error.invalid_credentials": "password"
|
|
9741
|
+
};
|
|
9528
9742
|
function createLiquidEngine(options) {
|
|
9529
9743
|
const engine = new Liquid({
|
|
9530
9744
|
templates: {
|
|
@@ -9541,7 +9755,7 @@ function createLiquidEngine(options) {
|
|
|
9541
9755
|
engine.registerFilter("raw", (value) => stringify(value));
|
|
9542
9756
|
engine.registerFilter("t", function tFilter(key, ...args) {
|
|
9543
9757
|
const lookupKey = stringify(key);
|
|
9544
|
-
return interpolate(options.locale[lookupKey] ?? lookupKey, args.map(stringify));
|
|
9758
|
+
return interpolate(options.locale[lookupKey] ?? injectedKeyFallback(options.locale, lookupKey) ?? fieldLabelFallback(lookupKey) ?? lookupKey, args.map(stringify));
|
|
9545
9759
|
});
|
|
9546
9760
|
/** Resolves `{text_key}.placeholder` — empty when undefined (not the raw key). */
|
|
9547
9761
|
engine.registerFilter("fieldPlaceholder", (textKey) => {
|
|
@@ -9569,15 +9783,6 @@ function createLiquidEngine(options) {
|
|
|
9569
9783
|
};
|
|
9570
9784
|
});
|
|
9571
9785
|
});
|
|
9572
|
-
/** Maps `error.*` text keys to a field name (Figma inline-error annotations). */
|
|
9573
|
-
const fieldErrorKeys = {
|
|
9574
|
-
"error.email_required": "email",
|
|
9575
|
-
"error.email_invalid": "email",
|
|
9576
|
-
"error.email_exists": "email",
|
|
9577
|
-
"error.password_required": "password",
|
|
9578
|
-
"error.password_incorrect": "password",
|
|
9579
|
-
"error.invalid_credentials": "password"
|
|
9580
|
-
};
|
|
9581
9786
|
/** Resolves `{text_key}.title` for form-level `<zl-alert heading>`. */
|
|
9582
9787
|
engine.registerFilter("alertHeading", (textKey) => {
|
|
9583
9788
|
const lookupKey = `${stringify(textKey)}.title`;
|
|
@@ -9598,6 +9803,13 @@ function createLiquidEngine(options) {
|
|
|
9598
9803
|
}
|
|
9599
9804
|
return "";
|
|
9600
9805
|
});
|
|
9806
|
+
/**
|
|
9807
|
+
* Automation-hook token for a field name: strips the
|
|
9808
|
+
* `x-auth-methods#` credential prefix so testids stay method-named
|
|
9809
|
+
* (`zitadel-field-password`), while the `name` attribute keeps the
|
|
9810
|
+
* raw wire key. See hookName for the contract.
|
|
9811
|
+
*/
|
|
9812
|
+
engine.registerFilter("testid", (fieldName) => hookName(stringify(fieldName)));
|
|
9601
9813
|
/** True when the error should render as `<zl-alert>`, not on a field. */
|
|
9602
9814
|
engine.registerFilter("formLevelError", (err) => {
|
|
9603
9815
|
const key = err?.text_key ?? "";
|
|
@@ -9611,6 +9823,123 @@ function createLiquidEngine(options) {
|
|
|
9611
9823
|
});
|
|
9612
9824
|
return engine;
|
|
9613
9825
|
}
|
|
9826
|
+
/**
|
|
9827
|
+
* Fallbacks for text keys the flow engine derives from tenant-chosen step
|
|
9828
|
+
* names (`<step>.action.back` for the injected back action — see
|
|
9829
|
+
* `internal/domain/flow_state_machine.go` `buildStep`). Step names are open,
|
|
9830
|
+
* so no dictionary can enumerate them; a missing step-specific key falls back
|
|
9831
|
+
* to its generic entry instead of leaking the raw key into the UI.
|
|
9832
|
+
*/
|
|
9833
|
+
const INJECTED_KEY_FALLBACKS = [{
|
|
9834
|
+
suffix: ".action.back",
|
|
9835
|
+
fallback: "action.back"
|
|
9836
|
+
}];
|
|
9837
|
+
function injectedKeyFallback(locale, key) {
|
|
9838
|
+
for (const { suffix, fallback } of INJECTED_KEY_FALLBACKS) if (key.endsWith(suffix)) return locale[fallback];
|
|
9839
|
+
}
|
|
9840
|
+
/**
|
|
9841
|
+
* Fallback for field-label keys (`<step>.field.<name>` — see
|
|
9842
|
+
* `FlowField.TextKey` in `internal/domain/flow_field_resolver.go`). Both
|
|
9843
|
+
* halves are tenant-chosen (step names and schema property names), so no
|
|
9844
|
+
* catalog can enumerate the keys; a miss renders a humanised property name
|
|
9845
|
+
* ("dateOfBirth" → "Date of birth") instead of leaking the raw key into the
|
|
9846
|
+
* form. Sub-keys like `.placeholder`/`.help` are excluded — they resolve
|
|
9847
|
+
* through their own filters, which stay empty on a miss.
|
|
9848
|
+
*/
|
|
9849
|
+
function fieldLabelFallback(key) {
|
|
9850
|
+
const index = key.lastIndexOf(".field.");
|
|
9851
|
+
if (index === -1) return void 0;
|
|
9852
|
+
const field = key.slice(index + 7);
|
|
9853
|
+
if (field === "" || field.includes(".")) return void 0;
|
|
9854
|
+
return capitaliseFirst(humaniseFieldName(field));
|
|
9855
|
+
}
|
|
9856
|
+
/**
|
|
9857
|
+
* Rule-suffix fallbacks for the server's field-validation keys
|
|
9858
|
+
* (`error.<field>_<rule>` — see `FlowFieldValidationError.TextKey` in
|
|
9859
|
+
* `internal/domain/flow_field_resolver.go`). Field names come from the
|
|
9860
|
+
* tenant's user schema, so no catalog can enumerate the specific keys;
|
|
9861
|
+
* a miss resolves to the rule's generic entry, interpolated with the
|
|
9862
|
+
* field's label (`{0}`). The server spells the format rule `_invalid`
|
|
9863
|
+
* (the catalog's existing convention, e.g. `error.email_invalid`), so
|
|
9864
|
+
* that suffix takes the format wording; `_unknown_field` (a submitted
|
|
9865
|
+
* name that is not a step field) takes the catch-all.
|
|
9866
|
+
*/
|
|
9867
|
+
const FLOW_ERROR_RULE_FALLBACKS = [
|
|
9868
|
+
{
|
|
9869
|
+
suffix: "_required",
|
|
9870
|
+
generic: "error.field_required"
|
|
9871
|
+
},
|
|
9872
|
+
{
|
|
9873
|
+
suffix: "_min_length",
|
|
9874
|
+
generic: "error.field_min_length"
|
|
9875
|
+
},
|
|
9876
|
+
{
|
|
9877
|
+
suffix: "_max_length",
|
|
9878
|
+
generic: "error.field_max_length"
|
|
9879
|
+
},
|
|
9880
|
+
{
|
|
9881
|
+
suffix: "_format",
|
|
9882
|
+
generic: "error.field_format"
|
|
9883
|
+
},
|
|
9884
|
+
{
|
|
9885
|
+
suffix: "_invalid",
|
|
9886
|
+
generic: "error.field_format"
|
|
9887
|
+
},
|
|
9888
|
+
{
|
|
9889
|
+
suffix: "_unknown_field",
|
|
9890
|
+
generic: "error.field_invalid"
|
|
9891
|
+
}
|
|
9892
|
+
];
|
|
9893
|
+
const FLOW_ERROR_CATCH_ALL_KEY = "error.field_invalid";
|
|
9894
|
+
/**
|
|
9895
|
+
* Localises a `step.error` payload of `error.*` catalog keys —
|
|
9896
|
+
* field-validation violations and general engine failures (e.g.
|
|
9897
|
+
* `error.invalid_credentials`, `error.passkey_invalid`) alike.
|
|
9898
|
+
* Returns `null` unless EVERY `"; "`-joined segment is an `error.*` key
|
|
9899
|
+
* — the caller keeps other payloads (outcome tokens such as
|
|
9900
|
+
* `user_not_found`) verbatim.
|
|
9901
|
+
*
|
|
9902
|
+
* Keys the locale knows pass through as `text_key` entries: the template
|
|
9903
|
+
* localises them via `| t`, and `fieldErrorKeys` routes the known ones
|
|
9904
|
+
* inline to their field. Unknown keys with a recognised rule suffix are
|
|
9905
|
+
* pre-localised here from their generic {@link FLOW_ERROR_RULE_FALLBACKS}
|
|
9906
|
+
* entry; unknown keys without one pass through as `text_key` (matching
|
|
9907
|
+
* `| t`'s behaviour for non-validation keys such as
|
|
9908
|
+
* `error.sign_in_server`, which localises via `.title`/`.body`).
|
|
9909
|
+
*/
|
|
9910
|
+
function localiseFlowErrorKeys(raw, ctx) {
|
|
9911
|
+
const segments = raw.split("; ");
|
|
9912
|
+
if (!segments.every((segment) => segment.startsWith("error."))) return null;
|
|
9913
|
+
return segments.map((key) => localiseFlowErrorKey(key, ctx));
|
|
9914
|
+
}
|
|
9915
|
+
function localiseFlowErrorKey(key, ctx) {
|
|
9916
|
+
const inlineField = fieldErrorKeys[key];
|
|
9917
|
+
const orphanedInline = inlineField !== void 0 && ctx.fields !== void 0 && !ctx.fields.includes(inlineField);
|
|
9918
|
+
if (!orphanedInline && ctx.locale[key] !== void 0) return { text_key: key };
|
|
9919
|
+
for (const { suffix, generic } of FLOW_ERROR_RULE_FALLBACKS) {
|
|
9920
|
+
if (!key.endsWith(suffix)) continue;
|
|
9921
|
+
const field = key.slice(6, key.length - suffix.length);
|
|
9922
|
+
if (field === "") break;
|
|
9923
|
+
return { message: capitaliseFirst(interpolate(ctx.locale[generic] ?? ctx.locale[FLOW_ERROR_CATCH_ALL_KEY] ?? "Please check {0}.", [fieldLabel(ctx, field)])) };
|
|
9924
|
+
}
|
|
9925
|
+
if (orphanedInline && ctx.locale[key] !== void 0) return { message: ctx.locale[key] };
|
|
9926
|
+
return { text_key: key };
|
|
9927
|
+
}
|
|
9928
|
+
/** `<step>.field.<name>` from the locale, else a humanised field name. */
|
|
9929
|
+
function fieldLabel(ctx, field) {
|
|
9930
|
+
return ctx.locale[`${ctx.stepName}.field.${field}`] ?? humaniseFieldName(field);
|
|
9931
|
+
}
|
|
9932
|
+
/**
|
|
9933
|
+
* `x-auth-methods#password` → "password", `givenName` → "given name",
|
|
9934
|
+
* `date_of_birth` → "date of birth".
|
|
9935
|
+
*/
|
|
9936
|
+
function humaniseFieldName(field) {
|
|
9937
|
+
return (field.includes("#") ? field.split("#").pop() : field).replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_.-]+/g, " ").trim().toLowerCase();
|
|
9938
|
+
}
|
|
9939
|
+
/** Messages may open with a lowercase field label; sentence-case them. */
|
|
9940
|
+
function capitaliseFirst(text) {
|
|
9941
|
+
return text.length > 0 ? text[0]?.toUpperCase() + text.slice(1) : text;
|
|
9942
|
+
}
|
|
9614
9943
|
function stringify(value) {
|
|
9615
9944
|
if (value == null) return "";
|
|
9616
9945
|
if (typeof value === "string") return value;
|
|
@@ -9621,6 +9950,11 @@ function stringify(value) {
|
|
|
9621
9950
|
return String(value);
|
|
9622
9951
|
}
|
|
9623
9952
|
}
|
|
9953
|
+
/**
|
|
9954
|
+
* Positional `{n}` substitution shared by the `| t` filter and the
|
|
9955
|
+
* pre-localising fallback in {@link localiseFlowErrorKeys}, so both
|
|
9956
|
+
* produce byte-identical output for the same template and args.
|
|
9957
|
+
*/
|
|
9624
9958
|
function interpolate(template, args) {
|
|
9625
9959
|
if (args.length === 0) return template;
|
|
9626
9960
|
return template.replace(/\{(\d+)\}/g, (match, index) => {
|
|
@@ -9738,6 +10072,10 @@ const en = {
|
|
|
9738
10072
|
"signed-in.continue": "Continue",
|
|
9739
10073
|
"signed-in.logout": "Sign out",
|
|
9740
10074
|
"passkey-login.title": "Sign in with your passkey",
|
|
10075
|
+
"passkey-first.title": "Sign in",
|
|
10076
|
+
"passkey-first.description": "Use your passkey to continue",
|
|
10077
|
+
"passkey-first.action.passkey": "Continue with passkey",
|
|
10078
|
+
"passkey-first.action.email_fallback": "Use email instead",
|
|
9741
10079
|
"recover.title": "Check your email",
|
|
9742
10080
|
"recover.description": "We sent a password reset link to your email address.",
|
|
9743
10081
|
"recover.action.back": "Back to sign in",
|
|
@@ -9745,12 +10083,17 @@ const en = {
|
|
|
9745
10083
|
"submit.signin": "Sign in",
|
|
9746
10084
|
"action.forgot_password": "Forgot password?",
|
|
9747
10085
|
"action.cancel": "Cancel",
|
|
10086
|
+
"action.back": "Back",
|
|
9748
10087
|
"sso.redirect.title": "Redirecting to your provider…",
|
|
9749
|
-
"
|
|
10088
|
+
"passkey.pending.status": "Waiting for your passkey…",
|
|
10089
|
+
"error.passkey_cancelled": "The passkey prompt was closed before completing.",
|
|
10090
|
+
"error.passkey_timeout": "The passkey request timed out. Please try again.",
|
|
9750
10091
|
"error.passkey_not_registered": "This passkey is not registered. Please sign in with your email and password.",
|
|
9751
10092
|
"error.passkey_setup_failed": "Passkey registration did not complete. Please try again.",
|
|
9752
10093
|
"error.passkey_unsupported": "This device does not support passkeys",
|
|
9753
10094
|
"error.passkey_failed": "Something went wrong. Please try again.",
|
|
10095
|
+
"error.passkey_invalid": "This passkey could not be verified. Please try again.",
|
|
10096
|
+
"error.passkey_registration_invalid": "The new passkey could not be verified. Please try registering it again.",
|
|
9754
10097
|
"error.email_required": "Please enter an email address",
|
|
9755
10098
|
"error.email_invalid": "Please enter a valid email",
|
|
9756
10099
|
"error.password_required": "Please enter a password",
|
|
@@ -9759,6 +10102,11 @@ const en = {
|
|
|
9759
10102
|
/** Figma sign-in error `6602:180268` — inline on password field. */
|
|
9760
10103
|
"error.invalid_credentials": "Wrong email or password.",
|
|
9761
10104
|
"error.required": "This field is required.",
|
|
10105
|
+
"error.field_required": "{0} is required.",
|
|
10106
|
+
"error.field_format": "Please enter a valid {0}.",
|
|
10107
|
+
"error.field_min_length": "{0} is too short.",
|
|
10108
|
+
"error.field_max_length": "{0} is too long.",
|
|
10109
|
+
"error.field_invalid": "Please check {0}.",
|
|
9762
10110
|
"error.sign_in_server.title": "We couldn't complete your sign in.",
|
|
9763
10111
|
"error.sign_in_server.body": "Please try again in a few minutes"
|
|
9764
10112
|
};
|
|
@@ -9840,6 +10188,10 @@ const de = {
|
|
|
9840
10188
|
"signed-in.continue": "Weiter",
|
|
9841
10189
|
"signed-in.logout": "Abmelden",
|
|
9842
10190
|
"passkey-login.title": "Mit Passkey anmelden",
|
|
10191
|
+
"passkey-first.title": "Anmelden",
|
|
10192
|
+
"passkey-first.description": "Verwende deinen Passkey, um fortzufahren",
|
|
10193
|
+
"passkey-first.action.passkey": "Weiter mit Passkey",
|
|
10194
|
+
"passkey-first.action.email_fallback": "Stattdessen E-Mail verwenden",
|
|
9843
10195
|
"recover.title": "E-Mail prüfen",
|
|
9844
10196
|
"recover.description": "Wir haben einen Link zum Zurücksetzen des Passworts an deine E-Mail-Adresse gesendet.",
|
|
9845
10197
|
"recover.action.back": "Zurück zur Anmeldung",
|
|
@@ -9847,12 +10199,17 @@ const de = {
|
|
|
9847
10199
|
"submit.signin": "Anmelden",
|
|
9848
10200
|
"action.forgot_password": "Passwort vergessen?",
|
|
9849
10201
|
"action.cancel": "Abbrechen",
|
|
10202
|
+
"action.back": "Zurück",
|
|
9850
10203
|
"sso.redirect.title": "Weiterleitung zum Anbieter…",
|
|
9851
|
-
"
|
|
10204
|
+
"passkey.pending.status": "Warten auf deinen Passkey…",
|
|
10205
|
+
"error.passkey_cancelled": "Die Passkey-Abfrage wurde vorzeitig geschlossen.",
|
|
10206
|
+
"error.passkey_timeout": "Die Passkey-Anfrage hat zu lange gedauert. Bitte versuche es erneut.",
|
|
9852
10207
|
"error.passkey_not_registered": "Dieser Passkey ist nicht registriert. Bitte melde dich mit E-Mail und Passwort an.",
|
|
9853
10208
|
"error.passkey_setup_failed": "Passkey-Registrierung wurde nicht abgeschlossen. Bitte versuche es erneut.",
|
|
9854
10209
|
"error.passkey_unsupported": "Dieses Gerät unterstützt keine Passkeys",
|
|
9855
10210
|
"error.passkey_failed": "Etwas ist schiefgelaufen. Bitte versuche es erneut.",
|
|
10211
|
+
"error.passkey_invalid": "Dieser Passkey konnte nicht bestätigt werden. Bitte versuche es erneut.",
|
|
10212
|
+
"error.passkey_registration_invalid": "Der neue Passkey konnte nicht bestätigt werden. Bitte registriere ihn erneut.",
|
|
9856
10213
|
"error.email_required": "Bitte gib eine E-Mail-Adresse ein",
|
|
9857
10214
|
"error.email_invalid": "Bitte gib eine gültige E-Mail-Adresse ein",
|
|
9858
10215
|
"error.password_required": "Bitte gib ein Passwort ein",
|
|
@@ -9860,6 +10217,11 @@ const de = {
|
|
|
9860
10217
|
"error.email_exists": "Ein Konto mit dieser E-Mail-Adresse existiert bereits.",
|
|
9861
10218
|
"error.invalid_credentials": "Falsche E-Mail oder falsches Passwort.",
|
|
9862
10219
|
"error.required": "Dieses Feld ist erforderlich.",
|
|
10220
|
+
"error.field_required": "{0} ist erforderlich.",
|
|
10221
|
+
"error.field_format": "Bitte gib einen gültigen Wert für {0} ein.",
|
|
10222
|
+
"error.field_min_length": "{0} ist zu kurz.",
|
|
10223
|
+
"error.field_max_length": "{0} ist zu lang.",
|
|
10224
|
+
"error.field_invalid": "Bitte überprüfe {0}.",
|
|
9863
10225
|
"error.sign_in_server.title": "Anmeldung konnte nicht abgeschlossen werden.",
|
|
9864
10226
|
"error.sign_in_server.body": "Bitte versuche es in einigen Minuten erneut"
|
|
9865
10227
|
};
|
|
@@ -9944,6 +10306,10 @@ const builtinLocales = {
|
|
|
9944
10306
|
"signed-in.continue": "Continua",
|
|
9945
10307
|
"signed-in.logout": "Esci",
|
|
9946
10308
|
"passkey-login.title": "Accedi con la tua passkey",
|
|
10309
|
+
"passkey-first.title": "Accedi",
|
|
10310
|
+
"passkey-first.description": "Usa la tua passkey per continuare",
|
|
10311
|
+
"passkey-first.action.passkey": "Continua con passkey",
|
|
10312
|
+
"passkey-first.action.email_fallback": "Usa invece l'email",
|
|
9947
10313
|
"recover.title": "Controlla la tua e-mail",
|
|
9948
10314
|
"recover.description": "Abbiamo inviato un link per reimpostare la password al tuo indirizzo e-mail.",
|
|
9949
10315
|
"recover.action.back": "Torna all'accesso",
|
|
@@ -9951,12 +10317,17 @@ const builtinLocales = {
|
|
|
9951
10317
|
"submit.signin": "Accedi",
|
|
9952
10318
|
"action.forgot_password": "Password dimenticata?",
|
|
9953
10319
|
"action.cancel": "Annulla",
|
|
10320
|
+
"action.back": "Indietro",
|
|
9954
10321
|
"sso.redirect.title": "Reindirizzamento al provider…",
|
|
9955
|
-
"
|
|
10322
|
+
"passkey.pending.status": "In attesa della tua passkey…",
|
|
10323
|
+
"error.passkey_cancelled": "La richiesta della passkey è stata chiusa prima del completamento.",
|
|
10324
|
+
"error.passkey_timeout": "La richiesta della passkey è scaduta. Riprova.",
|
|
9956
10325
|
"error.passkey_not_registered": "Questa passkey non è registrata. Accedi con e-mail e password.",
|
|
9957
10326
|
"error.passkey_setup_failed": "La registrazione della passkey non è stata completata. Riprova.",
|
|
9958
10327
|
"error.passkey_unsupported": "Questo dispositivo non supporta le passkey",
|
|
9959
10328
|
"error.passkey_failed": "Qualcosa è andato storto. Riprova.",
|
|
10329
|
+
"error.passkey_invalid": "Non è stato possibile verificare questa passkey. Riprova.",
|
|
10330
|
+
"error.passkey_registration_invalid": "Non è stato possibile verificare la nuova passkey. Riprova a registrarla.",
|
|
9960
10331
|
"error.email_required": "Inserisci un indirizzo e-mail",
|
|
9961
10332
|
"error.email_invalid": "Inserisci un indirizzo e-mail valido",
|
|
9962
10333
|
"error.password_required": "Inserisci una password",
|
|
@@ -9964,6 +10335,11 @@ const builtinLocales = {
|
|
|
9964
10335
|
"error.email_exists": "Esiste già un account con questo indirizzo e-mail.",
|
|
9965
10336
|
"error.invalid_credentials": "E-mail o password errata.",
|
|
9966
10337
|
"error.required": "Questo campo è obbligatorio.",
|
|
10338
|
+
"error.field_required": "{0} è obbligatorio.",
|
|
10339
|
+
"error.field_format": "Inserisci un valore valido per {0}.",
|
|
10340
|
+
"error.field_min_length": "{0} è troppo corto.",
|
|
10341
|
+
"error.field_max_length": "{0} è troppo lungo.",
|
|
10342
|
+
"error.field_invalid": "Controlla {0}.",
|
|
9967
10343
|
"error.sign_in_server.title": "Non è stato possibile completare l'accesso.",
|
|
9968
10344
|
"error.sign_in_server.body": "Riprova tra qualche minuto"
|
|
9969
10345
|
}
|
|
@@ -11897,6 +12273,13 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
|
|
|
11897
12273
|
set purpose(value) {
|
|
11898
12274
|
this.#_purpose_accessor_storage = value;
|
|
11899
12275
|
}
|
|
12276
|
+
#_flowName_accessor_storage = "";
|
|
12277
|
+
get flowName() {
|
|
12278
|
+
return this.#_flowName_accessor_storage;
|
|
12279
|
+
}
|
|
12280
|
+
set flowName(value) {
|
|
12281
|
+
this.#_flowName_accessor_storage = value;
|
|
12282
|
+
}
|
|
11900
12283
|
#_project_accessor_storage;
|
|
11901
12284
|
get project() {
|
|
11902
12285
|
return this.#_project_accessor_storage;
|
|
@@ -12004,6 +12387,8 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
|
|
|
12004
12387
|
sheet.replaceSync(layout_chrome_default);
|
|
12005
12388
|
root.adoptedStyleSheets = [...existing, sheet];
|
|
12006
12389
|
root.addEventListener("zl-input", this.handleAtomInput);
|
|
12390
|
+
root.addEventListener("zl-change", this.handleAtomEdited);
|
|
12391
|
+
root.addEventListener("zl-dismiss", this.handleAlertDismiss);
|
|
12007
12392
|
root.addEventListener("zl-submit", this.handleAtomSubmit);
|
|
12008
12393
|
root.addEventListener("click", this.handleDelegatedAction);
|
|
12009
12394
|
root.addEventListener("submit", this.handleFormSubmit);
|
|
@@ -12142,17 +12527,32 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
|
|
|
12142
12527
|
if (!cfg.projectId) throw new Error("<zitadel-login> requires a project id (the `project-id` attribute, `configureZitadel({ projectId })`, or a `project` handle) to start a flow.");
|
|
12143
12528
|
wire = await startFlow(api, {
|
|
12144
12529
|
project_id: cfg.projectId,
|
|
12145
|
-
purpose: this.purpose
|
|
12530
|
+
purpose: this.purpose,
|
|
12531
|
+
...this.flowName ? { flow_definition_name: this.flowName } : {}
|
|
12146
12532
|
});
|
|
12147
12533
|
}
|
|
12148
12534
|
this.applyResponse(wire);
|
|
12149
12535
|
} catch (error) {
|
|
12150
|
-
this.handleTransportError(error);
|
|
12536
|
+
this.handleTransportError(this.describeFlowSelectionError(error));
|
|
12151
12537
|
} finally {
|
|
12152
12538
|
this.loading = false;
|
|
12153
12539
|
}
|
|
12154
12540
|
}
|
|
12541
|
+
/**
|
|
12542
|
+
* When a `flow-name` lookup fails, the server's envelope only says
|
|
12543
|
+
* "not found" / "purpose mismatch" — it cannot know the name came from
|
|
12544
|
+
* an attribute. Rewrap those two codes with the attribute and the fix;
|
|
12545
|
+
* every other error passes through untouched.
|
|
12546
|
+
*/
|
|
12547
|
+
describeFlowSelectionError(error) {
|
|
12548
|
+
if (!this.flowName || !(error instanceof ApiError)) return error;
|
|
12549
|
+
const code = typeof error.body === "object" && error.body !== null && "code" in error.body ? String(error.body.code) : "";
|
|
12550
|
+
if (code === "flowdef.not_found") return /* @__PURE__ */ new Error(`<zitadel-login> flow-name="${this.flowName}" does not match any active flow definition in this project. Check the \`name\` in your flow file and that it has been applied (\`zitadel apply\`).`);
|
|
12551
|
+
if (code === "flowdef.purpose_mismatch") return /* @__PURE__ */ new Error(`<zitadel-login> flow-name="${this.flowName}" matched a flow definition that does not serve purpose "${this.purpose}".`);
|
|
12552
|
+
return error;
|
|
12553
|
+
}
|
|
12155
12554
|
applyResponse(wire) {
|
|
12555
|
+
this.stepErrorDismissed = false;
|
|
12156
12556
|
this.response = wire;
|
|
12157
12557
|
const { branding, issues } = validateBranding(wire.branding);
|
|
12158
12558
|
this.branding = branding;
|
|
@@ -12206,7 +12606,12 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
|
|
|
12206
12606
|
}
|
|
12207
12607
|
renderStep(step, engine) {
|
|
12208
12608
|
const tenantSource = typeof this.branding?.liquid_template === "string" && this.branding.liquid_template.length > 0 ? this.branding.liquid_template : null;
|
|
12209
|
-
const
|
|
12609
|
+
const rawErrors = step.error ? localiseFlowErrorKeys(step.error, {
|
|
12610
|
+
locale: this.resolveLocale(),
|
|
12611
|
+
stepName: step.name ?? "",
|
|
12612
|
+
fields: (step.fields ?? []).map((field) => field.name)
|
|
12613
|
+
}) ?? [{ message: step.error }] : [];
|
|
12614
|
+
const errors = this.loading && this.stepErrorDismissed ? [] : rawErrors;
|
|
12210
12615
|
const fields = step.fields ?? [];
|
|
12211
12616
|
const actions = step.actions ?? [];
|
|
12212
12617
|
const context = {
|
|
@@ -12300,6 +12705,17 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
|
|
|
12300
12705
|
if (native && native.value !== field.value) return native.value;
|
|
12301
12706
|
return typeof field.value === "string" ? field.value : void 0;
|
|
12302
12707
|
}
|
|
12708
|
+
/**
|
|
12709
|
+
* True once the user retired the current step error by editing a field or
|
|
12710
|
+
* dismissing the alert. Deliberately NON-reactive: consulting reactive
|
|
12711
|
+
* state in `renderStep` would change its output string on the first
|
|
12712
|
+
* post-dismiss keystroke, and `unsafeHTML` would rebuild the whole step
|
|
12713
|
+
* subtree — wiping typed values (`hydrateStepAfterRender` only re-applies
|
|
12714
|
+
* them on response changes) and reconnecting atoms. Reset on every new
|
|
12715
|
+
* response; consulted only by the `loading` re-render, which rebuilds
|
|
12716
|
+
* anyway.
|
|
12717
|
+
*/
|
|
12718
|
+
stepErrorDismissed = false;
|
|
12303
12719
|
handleAtomInput = (event) => {
|
|
12304
12720
|
if (!event.detail) return;
|
|
12305
12721
|
const { name, value } = event.detail;
|
|
@@ -12309,11 +12725,41 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
|
|
|
12309
12725
|
[name]: value
|
|
12310
12726
|
};
|
|
12311
12727
|
this.syncFieldElementValue(name, value);
|
|
12728
|
+
this.clearStaleErrors(name);
|
|
12312
12729
|
emit(this, "zitadel-flow-input", {
|
|
12313
12730
|
name,
|
|
12314
12731
|
value
|
|
12315
12732
|
});
|
|
12316
12733
|
};
|
|
12734
|
+
/** `zl-change` from <zl-checkbox>/<zl-select>: only error clearing. */
|
|
12735
|
+
handleAtomEdited = (event) => {
|
|
12736
|
+
const name = event.detail?.name;
|
|
12737
|
+
if (name) this.clearStaleErrors(name);
|
|
12738
|
+
};
|
|
12739
|
+
/** Explicit dismiss of the step-error alert (it removes itself). */
|
|
12740
|
+
handleAlertDismiss = (event) => {
|
|
12741
|
+
if (event.target?.matches?.("zl-alert[data-zl-step-error]")) this.stepErrorDismissed = true;
|
|
12742
|
+
};
|
|
12743
|
+
/**
|
|
12744
|
+
* Retire the current step error after the user edits `fieldName`:
|
|
12745
|
+
* remove the form-level alert(s) and clear the edited field's inline
|
|
12746
|
+
* error — other fields' inline errors stay until they are edited.
|
|
12747
|
+
* Imperative on purpose; see {@link stepErrorDismissed}.
|
|
12748
|
+
*/
|
|
12749
|
+
clearStaleErrors(fieldName) {
|
|
12750
|
+
if (!this.response?.step.error) return;
|
|
12751
|
+
const root = this.shadowRoot;
|
|
12752
|
+
if (!root) return;
|
|
12753
|
+
if (!this.stepErrorDismissed) {
|
|
12754
|
+
this.stepErrorDismissed = true;
|
|
12755
|
+
for (const alert of root.querySelectorAll("zl-alert[data-zl-step-error]")) alert.remove();
|
|
12756
|
+
}
|
|
12757
|
+
for (const field of root.querySelectorAll("zl-field")) {
|
|
12758
|
+
if (field.getAttribute("name") !== fieldName || !field.invalid) continue;
|
|
12759
|
+
field.invalid = false;
|
|
12760
|
+
field.error = "";
|
|
12761
|
+
}
|
|
12762
|
+
}
|
|
12317
12763
|
syncFieldElementValue(name, value) {
|
|
12318
12764
|
const root = this.shadowRoot;
|
|
12319
12765
|
if (!root) return;
|
|
@@ -12373,9 +12819,10 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
|
|
|
12373
12819
|
*/
|
|
12374
12820
|
handlePasskeyError = (event) => {
|
|
12375
12821
|
if (!this.response) return;
|
|
12376
|
-
const { error: message, aborted } = event.detail;
|
|
12377
|
-
const errorKey = aborted ? "error.passkey_cancelled" : "error.passkey_failed";
|
|
12822
|
+
const { error: message, aborted, timed_out: timedOut } = event.detail;
|
|
12823
|
+
const errorKey = timedOut ? "error.passkey_timeout" : aborted ? "error.passkey_cancelled" : "error.passkey_failed";
|
|
12378
12824
|
if (this.response.step.error === errorKey) return;
|
|
12825
|
+
this.stepErrorDismissed = false;
|
|
12379
12826
|
const { challenge: _dropped, ...stepWithoutChallenge } = this.response.step;
|
|
12380
12827
|
this.response = {
|
|
12381
12828
|
...this.response,
|
|
@@ -12384,7 +12831,7 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
|
|
|
12384
12831
|
error: errorKey
|
|
12385
12832
|
}
|
|
12386
12833
|
};
|
|
12387
|
-
console.warn(`[zitadel-login] passkey ceremony ${aborted ? "cancelled" : "failed"}: ${message}`);
|
|
12834
|
+
console.warn(`[zitadel-login] passkey ceremony ${timedOut ? "timed out" : aborted ? "cancelled" : "failed"}: ${message}`);
|
|
12388
12835
|
};
|
|
12389
12836
|
findPrimaryAction() {
|
|
12390
12837
|
const root = this.shadowRoot;
|
|
@@ -12426,13 +12873,17 @@ let ZitadelLogin = class ZitadelLogin extends i$2 {
|
|
|
12426
12873
|
}
|
|
12427
12874
|
}
|
|
12428
12875
|
handleTransportError(error) {
|
|
12429
|
-
const message = error instanceof Error ? error.message : "Unexpected error contacting the Flow API.";
|
|
12876
|
+
const message = error instanceof ApiError ? apiErrorMessage(error) : error instanceof Error ? error.message : "Unexpected error contacting the Flow API.";
|
|
12430
12877
|
this.startupError = message;
|
|
12431
12878
|
console.error("[zitadel-login]", error);
|
|
12432
12879
|
emit(this, "zitadel-flow-error", { message });
|
|
12433
12880
|
}
|
|
12434
12881
|
};
|
|
12435
12882
|
__decorate([n$1({ type: String })], ZitadelLogin.prototype, "purpose", null);
|
|
12883
|
+
__decorate([n$1({
|
|
12884
|
+
type: String,
|
|
12885
|
+
attribute: "flow-name"
|
|
12886
|
+
})], ZitadelLogin.prototype, "flowName", null);
|
|
12436
12887
|
__decorate([n$1({ attribute: false })], ZitadelLogin.prototype, "project", null);
|
|
12437
12888
|
__decorate([n$1({
|
|
12438
12889
|
type: String,
|