@zitadel/config 0.1.0-alpha.18 → 1.0.0-alpha.20
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 +27 -0
- package/defaults/README-branding.md +36 -2
- package/defaults/README-flows.md +21 -4
- package/defaults/README-schemas.md +9 -2
- package/defaults/branding/centered/login.liquid +30 -14
- package/defaults/branding/hero/login.liquid +33 -17
- package/defaults/branding/minimal/login.liquid +25 -14
- package/defaults/branding/split/login.liquid +34 -15
- package/defaults/branding/split-right/login.liquid +34 -15
- package/defaults/default-human-user.json +3 -4
- package/defaults/default-login.json +20 -0
- package/defaults/presets/passkey-first/human-user.json +3 -4
- package/defaults/presets/passkey-first/login.json +30 -0
- package/dist/branding-url.d.mts +14 -0
- package/dist/branding-url.d.mts.map +1 -0
- package/dist/branding-url.mjs +23 -0
- package/dist/branding-url.mjs.map +1 -0
- package/dist/{defaults-CXTnFMHj.mjs → defaults-DPq0o3iU.mjs} +132 -77
- package/dist/{defaults-CXTnFMHj.mjs.map → defaults-DPq0o3iU.mjs.map} +1 -1
- package/dist/defaults.mjs +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +4 -3
- package/dist/{meta-schemas-B4YiHAX_.mjs → meta-schemas-z4IlqbLd.mjs} +41 -34
- package/dist/{meta-schemas-B4YiHAX_.mjs.map → meta-schemas-z4IlqbLd.mjs.map} +1 -1
- package/dist/meta-schemas.mjs +1 -1
- package/dist/normalize.d.mts +11 -9
- package/dist/normalize.d.mts.map +1 -1
- package/dist/normalize.mjs +25 -19
- package/dist/normalize.mjs.map +1 -1
- package/dist/schemas.d.mts.map +1 -1
- package/dist/schemas.mjs +8 -6
- package/dist/schemas.mjs.map +1 -1
- package/dist/validate.mjs +72 -13
- package/dist/validate.mjs.map +1 -1
- package/meta-schemas/auth-method.json +2 -7
- package/meta-schemas/branding.json +8 -16
- package/meta-schemas/flow-definition.json +20 -1
- package/meta-schemas/user-property.json +2 -23
- package/meta-schemas/user-schema.json +14 -0
- package/package.json +7 -2
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @zitadel/config
|
|
2
|
+
|
|
3
|
+
Versioned Zitadel local config schemas and defaults — the contract behind the
|
|
4
|
+
`.zitadel/` directory that `zitadel setup` scaffolds into your project.
|
|
5
|
+
|
|
6
|
+
## What's in here
|
|
7
|
+
|
|
8
|
+
| Import | What it carries |
|
|
9
|
+
| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
10
|
+
| `@zitadel/config/schemas` | Zod schemas for the local config files (`schemaConfigSchema`, `flowConfigSchema`, `brandingConfigSchema`) |
|
|
11
|
+
| `@zitadel/config/branding-url` | Canonical loopback HTTP URL predicate shared by config and browser render gates |
|
|
12
|
+
| `@zitadel/config/validate` | Validation entry points the CLI runs on `plan`/`apply` |
|
|
13
|
+
| `@zitadel/config/normalize` | Normalization used to compare local files against server state |
|
|
14
|
+
| `@zitadel/config/defaults` | The versioned default user schema, login flow, and branding designs |
|
|
15
|
+
| `@zitadel/config/meta-schemas` | The JSON meta-schemas the defaults pin |
|
|
16
|
+
| `@zitadel/config/template` | Liquid template helpers |
|
|
17
|
+
| `@zitadel/config/defaults/default-login.json` | The default login flow — the authority for flow step shape |
|
|
18
|
+
| `@zitadel/config/defaults/default-human-user.json` | The default user schema |
|
|
19
|
+
|
|
20
|
+
## The customer-facing part
|
|
21
|
+
|
|
22
|
+
`zitadel setup` copies the defaults into your repo as
|
|
23
|
+
`.zitadel/{schemas,flows,branding}/`, each with a README explaining how to
|
|
24
|
+
edit it (`defaults/README-schemas.md`, `README-flows.md`,
|
|
25
|
+
`README-branding.md` in this package become those files). Those files are
|
|
26
|
+
yours to edit; `zitadel plan` / `zitadel apply` reconcile them with the
|
|
27
|
+
server, and edits publish new revisions rather than mutating state in place.
|
|
@@ -6,7 +6,7 @@ and asset URLs) plus `login.liquid`, the LiquidJS template the
|
|
|
6
6
|
|
|
7
7
|
## Workflow
|
|
8
8
|
|
|
9
|
-
1. Edit `login.liquid` (and `branding.json` for logo/
|
|
9
|
+
1. Edit `login.liquid` (and `branding.json` for logo/hero URLs).
|
|
10
10
|
2. `zitadel plan` — validates the template (LiquidJS parse, banned patterns,
|
|
11
11
|
the required `{% mandatory_gates %}` tag) and shows the pending revision.
|
|
12
12
|
3. `zitadel apply` — publishes an immutable branding revision. The login UI
|
|
@@ -27,6 +27,40 @@ by re-applying an earlier template.
|
|
|
27
27
|
|
|
28
28
|
## Make it yours
|
|
29
29
|
|
|
30
|
+
- Brand asset URLs go in `branding.json` and must use `https://`. Each template
|
|
31
|
+
decides which fields it renders: `centered` and `hero` use `logo_url`; `split`
|
|
32
|
+
and `split-right` use both `logo_url` and `hero_url`; `minimal` uses neither
|
|
33
|
+
until you add them to `login.liquid`. The `hero` design's brand pane is
|
|
34
|
+
editable markup rather than a `hero_url` background. Custom fonts are not
|
|
35
|
+
configurable here yet; load them from the embedding page.
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"$schema": "../meta/branding.json",
|
|
40
|
+
"layout": "split",
|
|
41
|
+
"liquid_template_file": "./login.liquid",
|
|
42
|
+
"logo_url": "https://example.com/logo.svg",
|
|
43
|
+
"hero_url": "https://example.com/hero.jpg"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A well-formed URL that serves nothing is the one branding mistake nothing
|
|
48
|
+
else catches — it passes validation, publishes a revision, and then renders
|
|
49
|
+
as an invisible image. So `zitadel plan` probes each asset URL and warns
|
|
50
|
+
(never fails) when it is unreachable, bodyless, or not an image, and the
|
|
51
|
+
login UI hides an asset that fails to load, restoring the shipped design's
|
|
52
|
+
no-asset content rather than leaving a gap. If the host is only
|
|
53
|
+
reachable from where the login page renders — or you are offline — set
|
|
54
|
+
`ZITADEL_SKIP_ASSET_PROBE=1` to skip the check. The probe contacts public
|
|
55
|
+
HTTPS destinations only and re-checks redirects; loopback/private/internal
|
|
56
|
+
targets are left to the browser-side fallback instead of being requested by
|
|
57
|
+
the machine running `plan`.
|
|
58
|
+
|
|
59
|
+
`layout` is the degrade preset (`centered` or `split`), **not** the complete
|
|
60
|
+
design catalog. All named designs (`centered`, `split`, `split-right`, `hero`,
|
|
61
|
+
`minimal`) are delivered as templates and map onto those two values. Switch
|
|
62
|
+
designs with `zitadel branding eject --design <name>`, don't edit `layout`.
|
|
63
|
+
|
|
30
64
|
- In the split-family designs (`split`, `split-right`, `hero`) the
|
|
31
65
|
`.zl-split__brand` pane is yours: structural HTML plus inline `style=""`
|
|
32
66
|
attributes are allowed; `button`, `input`, and `form` tags are stripped —
|
|
@@ -38,7 +72,7 @@ by re-applying an earlier template.
|
|
|
38
72
|
- The split chrome is tunable from your template root's `style` attribute
|
|
39
73
|
(`--zl-split-columns`, `--zl-split-align`, `--zl-split-brand-mobile`), and
|
|
40
74
|
`zl-split--right` on the wrapper mirrors the panes. Knob reference:
|
|
41
|
-
`docs/design/branding/templates.md`
|
|
75
|
+
[`docs/design/branding/templates.md`](https://github.com/zitadel/nextgen/blob/main/docs/design/branding/templates.md).
|
|
42
76
|
- The "Secured with Zitadel" attribution is licence-gated and on by default.
|
|
43
77
|
- Back-navigation: the engine injects a `kind: "back"` action on steps
|
|
44
78
|
that can return to their predecessor, and the browser's back gesture
|
package/defaults/README-flows.md
CHANGED
|
@@ -37,8 +37,25 @@ Typical edits:
|
|
|
37
37
|
tokens).
|
|
38
38
|
- **Add or remove a step** — extend `steps[]`.
|
|
39
39
|
- **Rewire transitions** — edit `steps[].transitions` to point at a
|
|
40
|
-
different next step
|
|
41
|
-
|
|
40
|
+
different next step. Cross-flow jumps (`action: switch` / `pivot`)
|
|
41
|
+
validate in a definition but are **not yet executed by the runtime** —
|
|
42
|
+
submitting through one fails with a 400 (`code: "flow.unsupported"`);
|
|
43
|
+
keep transitions within one flow, using a purpose switch (next bullet)
|
|
44
|
+
for login ↔ register navigation.
|
|
45
|
+
- **Offer a purpose switch in the card** — pair a `kind: navigate`
|
|
46
|
+
action with a transition that declares a local `purpose`. The default
|
|
47
|
+
login flow ships this in both directions:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
"register": { "target": "register", "purpose": "register" }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Navigation skips field validation (an empty email box can't block the
|
|
54
|
+
link), and the declared purpose is what makes the landing leg run with
|
|
55
|
+
the right semantics — register creates the user instead of trying to
|
|
56
|
+
verify one. Rules: the purpose must be one this flow's `purposes`
|
|
57
|
+
serves, the target must be that purpose's entry step, and `purpose`
|
|
58
|
+
never combines with `action` (which targets another flow instead).
|
|
42
59
|
- **Add another flow** — drop a new JSON file with its own `purposes`
|
|
43
60
|
and a distinct `name` (e.g. a per-team login). See
|
|
44
61
|
[Multiple flows](#multiple-flows) for how it gets selected at runtime.
|
|
@@ -70,8 +87,8 @@ experiment can't silently take over `/login` — scope it or pin
|
|
|
70
87
|
|
|
71
88
|
## Presets
|
|
72
89
|
|
|
73
|
-
`zitadel setup` scaffolds this folder from a preset (
|
|
74
|
-
|
|
90
|
+
`zitadel setup` scaffolds this folder from a preset: `password-first` (the
|
|
91
|
+
default root files) or `--preset passkey-first`. The passkey-first flow
|
|
75
92
|
enters login on a fields-less passkey step with an email → password
|
|
76
93
|
fallback path. The preset only decides the starting point — edit
|
|
77
94
|
anything here afterwards.
|
|
@@ -16,7 +16,13 @@ Here's a simplified example:
|
|
|
16
16
|
``` json
|
|
17
17
|
{
|
|
18
18
|
"objectType": "customer",
|
|
19
|
+
"x-identifier": "email",
|
|
19
20
|
"properties": {
|
|
21
|
+
"email": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"format": "email",
|
|
24
|
+
"x-unique": "project"
|
|
25
|
+
},
|
|
20
26
|
"firstName": {
|
|
21
27
|
"type": "string"
|
|
22
28
|
},
|
|
@@ -25,11 +31,12 @@ Here's a simplified example:
|
|
|
25
31
|
}
|
|
26
32
|
},
|
|
27
33
|
"required": [
|
|
34
|
+
"email",
|
|
28
35
|
"firstName"
|
|
29
36
|
],
|
|
30
37
|
"x-auth-methods": {
|
|
31
|
-
"password": { "enabled": true
|
|
32
|
-
"passkey": { "enabled": true
|
|
38
|
+
"password": { "enabled": true },
|
|
39
|
+
"passkey": { "enabled": true }
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
42
|
```
|
|
@@ -11,18 +11,9 @@
|
|
|
11
11
|
{% endif %}
|
|
12
12
|
{% endif %}
|
|
13
13
|
|
|
14
|
-
{%
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{% assign alert_heading = err.text_key | alertHeading %}
|
|
18
|
-
{% assign alert_body = err.text_key | alertBody %}
|
|
19
|
-
<zl-alert severity="error" dismissible data-zl-step-error{% if alert_heading != "" %} heading="{{ alert_heading | t }}"{% endif %}>
|
|
20
|
-
{% if alert_body != "" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}
|
|
21
|
-
</zl-alert>
|
|
22
|
-
{% endif %}
|
|
23
|
-
{% endfor %}
|
|
24
|
-
{% endif %}
|
|
25
|
-
|
|
14
|
+
{% assign recover = actions | where: "name", "recover" | first %}
|
|
15
|
+
{% assign password_field = fields | where: "type", "password" | first %}
|
|
16
|
+
<div class="zl-field-group">
|
|
26
17
|
{% for f in fields %}
|
|
27
18
|
{% assign field_placeholder = f.text_key | fieldPlaceholder %}
|
|
28
19
|
{% assign field_help = f.text_key | fieldHelp %}
|
|
@@ -67,6 +58,7 @@
|
|
|
67
58
|
{% if f.required %}required{% endif %}
|
|
68
59
|
{% if field_placeholder != "" %}placeholder="{{ field_placeholder }}"{% endif %}
|
|
69
60
|
{% if field_err != "" %}invalid error="{{ field_err }}"{% endif %}
|
|
61
|
+
{% if recover and f.type == 'password' %}forgot-password-href="#" forgot-password-action="{{ recover.name }}" forgot-password-label="{{ recover.text_key | t }}"{% endif %}
|
|
70
62
|
>
|
|
71
63
|
{% if field_help != "" %}
|
|
72
64
|
<span slot="help">{{ field_help }}</span>
|
|
@@ -75,8 +67,31 @@
|
|
|
75
67
|
{% endcase %}
|
|
76
68
|
{% endfor %}
|
|
77
69
|
|
|
78
|
-
{%
|
|
79
|
-
|
|
70
|
+
{% comment %}
|
|
71
|
+
Form-level errors close the field group rather than opening the card: the
|
|
72
|
+
design puts the alert directly above the actions, where it reads as a
|
|
73
|
+
reason the last attempt failed rather than a banner about the screen.
|
|
74
|
+
{% endcomment %}
|
|
75
|
+
{% if errors and errors.size > 0 %}
|
|
76
|
+
{% for err in errors %}
|
|
77
|
+
{% if err | formLevelError %}
|
|
78
|
+
{% assign alert_heading = err.text_key | alertHeading %}
|
|
79
|
+
{% assign alert_body = err.text_key | alertBody %}
|
|
80
|
+
<zl-alert data-zl-step-error{% if alert_heading != "" %} heading="{{ alert_heading | t }}"{% endif %}>
|
|
81
|
+
{% if alert_body != "" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}
|
|
82
|
+
</zl-alert>
|
|
83
|
+
{% endif %}
|
|
84
|
+
{% endfor %}
|
|
85
|
+
{% endif %}
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="zl-card-actions">
|
|
89
|
+
{% comment %}
|
|
90
|
+
A step can offer recovery without asking for a password (an identifier-only
|
|
91
|
+
screen, say). With no label row to sit on, the affordance keeps its own row
|
|
92
|
+
rather than disappearing.
|
|
93
|
+
{% endcomment %}
|
|
94
|
+
{% if recover and password_field == nil %}
|
|
80
95
|
<p class="zl-card-forgot">
|
|
81
96
|
<a href="#" class="zl-card-forgot__link" data-action="recover">{{ recover.text_key | t }}</a>
|
|
82
97
|
</p>
|
|
@@ -139,6 +154,7 @@
|
|
|
139
154
|
{{ '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>
|
|
140
155
|
</p>
|
|
141
156
|
{% endif %}
|
|
157
|
+
</div>
|
|
142
158
|
|
|
143
159
|
{% if challenge %}
|
|
144
160
|
{% if challenge.method == "passkey" or challenge.method == "passkey_register" %}
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
<nav class="zl-hero__nav">
|
|
20
20
|
<span class="zl-hero__brand">
|
|
21
21
|
{% if branding.logo_url %}
|
|
22
|
-
<img class="zl-hero__brand-logo" src="{{ branding.logo_url }}" alt="" />
|
|
22
|
+
<img class="zl-hero__brand-logo" src="{{ branding.logo_url }}" alt="" data-zl-asset-fallback />
|
|
23
|
+
<span class="zl-hero__brand-mark" aria-hidden="true" hidden data-zl-asset-fallback-content></span>
|
|
23
24
|
{% else %}
|
|
24
25
|
<span class="zl-hero__brand-mark" aria-hidden="true"></span>
|
|
25
26
|
{% endif %}
|
|
@@ -54,7 +55,8 @@
|
|
|
54
55
|
</aside>
|
|
55
56
|
<section class="zl-split__form">
|
|
56
57
|
{% if branding.logo_url %}
|
|
57
|
-
<img class="zl-split__compact" src="{{ branding.logo_url }}" alt="" />
|
|
58
|
+
<img class="zl-split__compact" src="{{ branding.logo_url }}" alt="" data-zl-asset-fallback />
|
|
59
|
+
<p class="zl-split__compact zl-hero__compact-brand" hidden data-zl-asset-fallback-content>Your brand</p>
|
|
58
60
|
{% else %}
|
|
59
61
|
<p class="zl-split__compact zl-hero__compact-brand">Your brand</p>
|
|
60
62
|
{% endif %}
|
|
@@ -67,18 +69,9 @@
|
|
|
67
69
|
{% endif %}
|
|
68
70
|
{% endif %}
|
|
69
71
|
|
|
70
|
-
{%
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{% assign alert_heading = err.text_key | alertHeading %}
|
|
74
|
-
{% assign alert_body = err.text_key | alertBody %}
|
|
75
|
-
<zl-alert severity="error" dismissible data-zl-step-error{% if alert_heading != "" %} heading="{{ alert_heading | t }}"{% endif %}>
|
|
76
|
-
{% if alert_body != "" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}
|
|
77
|
-
</zl-alert>
|
|
78
|
-
{% endif %}
|
|
79
|
-
{% endfor %}
|
|
80
|
-
{% endif %}
|
|
81
|
-
|
|
72
|
+
{% assign recover = actions | where: "name", "recover" | first %}
|
|
73
|
+
{% assign password_field = fields | where: "type", "password" | first %}
|
|
74
|
+
<div class="zl-field-group">
|
|
82
75
|
{% for f in fields %}
|
|
83
76
|
{% assign field_placeholder = f.text_key | fieldPlaceholder %}
|
|
84
77
|
{% assign field_help = f.text_key | fieldHelp %}
|
|
@@ -123,6 +116,7 @@
|
|
|
123
116
|
{% if f.required %}required{% endif %}
|
|
124
117
|
{% if field_placeholder != "" %}placeholder="{{ field_placeholder }}"{% endif %}
|
|
125
118
|
{% if field_err != "" %}invalid error="{{ field_err }}"{% endif %}
|
|
119
|
+
{% if recover and f.type == 'password' %}forgot-password-href="#" forgot-password-action="{{ recover.name }}" forgot-password-label="{{ recover.text_key | t }}"{% endif %}
|
|
126
120
|
>
|
|
127
121
|
{% if field_help != "" %}
|
|
128
122
|
<span slot="help">{{ field_help }}</span>
|
|
@@ -131,8 +125,26 @@
|
|
|
131
125
|
{% endcase %}
|
|
132
126
|
{% endfor %}
|
|
133
127
|
|
|
134
|
-
{%
|
|
135
|
-
|
|
128
|
+
{% if errors and errors.size > 0 %}
|
|
129
|
+
{% for err in errors %}
|
|
130
|
+
{% if err | formLevelError %}
|
|
131
|
+
{% assign alert_heading = err.text_key | alertHeading %}
|
|
132
|
+
{% assign alert_body = err.text_key | alertBody %}
|
|
133
|
+
<zl-alert data-zl-step-error{% if alert_heading != "" %} heading="{{ alert_heading | t }}"{% endif %}>
|
|
134
|
+
{% if alert_body != "" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}
|
|
135
|
+
</zl-alert>
|
|
136
|
+
{% endif %}
|
|
137
|
+
{% endfor %}
|
|
138
|
+
{% endif %}
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div class="zl-card-actions">
|
|
142
|
+
{% comment %}
|
|
143
|
+
A step can offer recovery without asking for a password (an identifier-only
|
|
144
|
+
screen, say). With no label row to sit on, the affordance keeps its own row
|
|
145
|
+
rather than disappearing.
|
|
146
|
+
{% endcomment %}
|
|
147
|
+
{% if recover and password_field == nil %}
|
|
136
148
|
<p class="zl-card-forgot">
|
|
137
149
|
<a href="#" class="zl-card-forgot__link" data-action="recover">{{ recover.text_key | t }}</a>
|
|
138
150
|
</p>
|
|
@@ -181,7 +193,6 @@
|
|
|
181
193
|
{% endunless %}
|
|
182
194
|
{% endif %}
|
|
183
195
|
|
|
184
|
-
|
|
185
196
|
{% assign register = actions | where: "name", "register" | first %}
|
|
186
197
|
{% if register %}
|
|
187
198
|
<p class="zl-card-nav">
|
|
@@ -195,6 +206,7 @@
|
|
|
195
206
|
{{ '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>
|
|
196
207
|
</p>
|
|
197
208
|
{% endif %}
|
|
209
|
+
</div>
|
|
198
210
|
|
|
199
211
|
{% if challenge %}
|
|
200
212
|
{% if challenge.method == "passkey" or challenge.method == "passkey_register" %}
|
|
@@ -217,6 +229,10 @@
|
|
|
217
229
|
|
|
218
230
|
{% mandatory_gates %}
|
|
219
231
|
</zl-card>
|
|
232
|
+
{% comment %} The orchestrator swaps this for the trustmark, so it sits with
|
|
233
|
+
the card rather than under the seam between the panes. Removing it falls
|
|
234
|
+
back to the page-shell footer. {% endcomment %}
|
|
235
|
+
<div data-zl-attribution-anchor></div>
|
|
220
236
|
</section>
|
|
221
237
|
</div>
|
|
222
238
|
</zl-page-shell>
|
|
@@ -2,18 +2,9 @@
|
|
|
2
2
|
<section class="zl-minimal">
|
|
3
3
|
<h1 class="zl-card-title">{{ step.texts.title_key | t: identity.display_name }}</h1>
|
|
4
4
|
|
|
5
|
-
{%
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
{% assign alert_heading = err.text_key | alertHeading %}
|
|
9
|
-
{% assign alert_body = err.text_key | alertBody %}
|
|
10
|
-
<zl-alert severity="error" dismissible data-zl-step-error{% if alert_heading != "" %} heading="{{ alert_heading | t }}"{% endif %}>
|
|
11
|
-
{% if alert_body != "" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}
|
|
12
|
-
</zl-alert>
|
|
13
|
-
{% endif %}
|
|
14
|
-
{% endfor %}
|
|
15
|
-
{% endif %}
|
|
16
|
-
|
|
5
|
+
{% assign recover = actions | where: "name", "recover" | first %}
|
|
6
|
+
{% assign password_field = fields | where: "type", "password" | first %}
|
|
7
|
+
<div class="zl-field-group">
|
|
17
8
|
{% for f in fields %}
|
|
18
9
|
{% assign field_placeholder = f.text_key | fieldPlaceholder %}
|
|
19
10
|
{% assign field_help = f.text_key | fieldHelp %}
|
|
@@ -58,6 +49,7 @@
|
|
|
58
49
|
{% if f.required %}required{% endif %}
|
|
59
50
|
{% if field_placeholder != "" %}placeholder="{{ field_placeholder }}"{% endif %}
|
|
60
51
|
{% if field_err != "" %}invalid error="{{ field_err }}"{% endif %}
|
|
52
|
+
{% if recover and f.type == 'password' %}forgot-password-href="#" forgot-password-action="{{ recover.name }}" forgot-password-label="{{ recover.text_key | t }}"{% endif %}
|
|
61
53
|
>
|
|
62
54
|
{% if field_help != "" %}
|
|
63
55
|
<span slot="help">{{ field_help }}</span>
|
|
@@ -66,8 +58,26 @@
|
|
|
66
58
|
{% endcase %}
|
|
67
59
|
{% endfor %}
|
|
68
60
|
|
|
69
|
-
{%
|
|
70
|
-
|
|
61
|
+
{% if errors and errors.size > 0 %}
|
|
62
|
+
{% for err in errors %}
|
|
63
|
+
{% if err | formLevelError %}
|
|
64
|
+
{% assign alert_heading = err.text_key | alertHeading %}
|
|
65
|
+
{% assign alert_body = err.text_key | alertBody %}
|
|
66
|
+
<zl-alert data-zl-step-error{% if alert_heading != "" %} heading="{{ alert_heading | t }}"{% endif %}>
|
|
67
|
+
{% if alert_body != "" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}
|
|
68
|
+
</zl-alert>
|
|
69
|
+
{% endif %}
|
|
70
|
+
{% endfor %}
|
|
71
|
+
{% endif %}
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div class="zl-card-actions">
|
|
75
|
+
{% comment %}
|
|
76
|
+
A step can offer recovery without asking for a password (an identifier-only
|
|
77
|
+
screen, say). With no label row to sit on, the affordance keeps its own row
|
|
78
|
+
rather than disappearing.
|
|
79
|
+
{% endcomment %}
|
|
80
|
+
{% if recover and password_field == nil %}
|
|
71
81
|
<p class="zl-card-forgot">
|
|
72
82
|
<a href="#" class="zl-card-forgot__link" data-action="recover">{{ recover.text_key | t }}</a>
|
|
73
83
|
</p>
|
|
@@ -129,6 +139,7 @@
|
|
|
129
139
|
{{ '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>
|
|
130
140
|
</p>
|
|
131
141
|
{% endif %}
|
|
142
|
+
</div>
|
|
132
143
|
|
|
133
144
|
{% if challenge %}
|
|
134
145
|
{% if challenge.method == "passkey" or challenge.method == "passkey_register" %}
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
{% if branding.hero_url %}
|
|
8
8
|
<img class="zl-split__hero" src="{{ branding.hero_url }}" alt="" />
|
|
9
9
|
{% endif %}
|
|
10
|
+
{% unless branding.logo_url or branding.hero_url %}
|
|
11
|
+
{% comment %} Until branding.json names an asset, a decorative panel
|
|
12
|
+
keeps the split composition readable. {% endcomment %}
|
|
13
|
+
<div class="zl-split__placeholder" aria-hidden="true"></div>
|
|
14
|
+
{% endunless %}
|
|
10
15
|
</aside>
|
|
11
16
|
<section class="zl-split__form">
|
|
12
17
|
{% if branding.logo_url %}
|
|
@@ -23,18 +28,9 @@
|
|
|
23
28
|
{% endif %}
|
|
24
29
|
{% endif %}
|
|
25
30
|
|
|
26
|
-
{%
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{% assign alert_heading = err.text_key | alertHeading %}
|
|
30
|
-
{% assign alert_body = err.text_key | alertBody %}
|
|
31
|
-
<zl-alert severity="error" dismissible data-zl-step-error{% if alert_heading != "" %} heading="{{ alert_heading | t }}"{% endif %}>
|
|
32
|
-
{% if alert_body != "" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}
|
|
33
|
-
</zl-alert>
|
|
34
|
-
{% endif %}
|
|
35
|
-
{% endfor %}
|
|
36
|
-
{% endif %}
|
|
37
|
-
|
|
31
|
+
{% assign recover = actions | where: "name", "recover" | first %}
|
|
32
|
+
{% assign password_field = fields | where: "type", "password" | first %}
|
|
33
|
+
<div class="zl-field-group">
|
|
38
34
|
{% for f in fields %}
|
|
39
35
|
{% assign field_placeholder = f.text_key | fieldPlaceholder %}
|
|
40
36
|
{% assign field_help = f.text_key | fieldHelp %}
|
|
@@ -79,6 +75,7 @@
|
|
|
79
75
|
{% if f.required %}required{% endif %}
|
|
80
76
|
{% if field_placeholder != "" %}placeholder="{{ field_placeholder }}"{% endif %}
|
|
81
77
|
{% if field_err != "" %}invalid error="{{ field_err }}"{% endif %}
|
|
78
|
+
{% if recover and f.type == 'password' %}forgot-password-href="#" forgot-password-action="{{ recover.name }}" forgot-password-label="{{ recover.text_key | t }}"{% endif %}
|
|
82
79
|
>
|
|
83
80
|
{% if field_help != "" %}
|
|
84
81
|
<span slot="help">{{ field_help }}</span>
|
|
@@ -87,8 +84,26 @@
|
|
|
87
84
|
{% endcase %}
|
|
88
85
|
{% endfor %}
|
|
89
86
|
|
|
90
|
-
{%
|
|
91
|
-
|
|
87
|
+
{% if errors and errors.size > 0 %}
|
|
88
|
+
{% for err in errors %}
|
|
89
|
+
{% if err | formLevelError %}
|
|
90
|
+
{% assign alert_heading = err.text_key | alertHeading %}
|
|
91
|
+
{% assign alert_body = err.text_key | alertBody %}
|
|
92
|
+
<zl-alert data-zl-step-error{% if alert_heading != "" %} heading="{{ alert_heading | t }}"{% endif %}>
|
|
93
|
+
{% if alert_body != "" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}
|
|
94
|
+
</zl-alert>
|
|
95
|
+
{% endif %}
|
|
96
|
+
{% endfor %}
|
|
97
|
+
{% endif %}
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div class="zl-card-actions">
|
|
101
|
+
{% comment %}
|
|
102
|
+
A step can offer recovery without asking for a password (an identifier-only
|
|
103
|
+
screen, say). With no label row to sit on, the affordance keeps its own row
|
|
104
|
+
rather than disappearing.
|
|
105
|
+
{% endcomment %}
|
|
106
|
+
{% if recover and password_field == nil %}
|
|
92
107
|
<p class="zl-card-forgot">
|
|
93
108
|
<a href="#" class="zl-card-forgot__link" data-action="recover">{{ recover.text_key | t }}</a>
|
|
94
109
|
</p>
|
|
@@ -137,7 +152,6 @@
|
|
|
137
152
|
{% endunless %}
|
|
138
153
|
{% endif %}
|
|
139
154
|
|
|
140
|
-
|
|
141
155
|
{% assign register = actions | where: "name", "register" | first %}
|
|
142
156
|
{% if register %}
|
|
143
157
|
<p class="zl-card-nav">
|
|
@@ -151,6 +165,7 @@
|
|
|
151
165
|
{{ '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>
|
|
152
166
|
</p>
|
|
153
167
|
{% endif %}
|
|
168
|
+
</div>
|
|
154
169
|
|
|
155
170
|
{% if challenge %}
|
|
156
171
|
{% if challenge.method == "passkey" or challenge.method == "passkey_register" %}
|
|
@@ -173,6 +188,10 @@
|
|
|
173
188
|
|
|
174
189
|
{% mandatory_gates %}
|
|
175
190
|
</zl-card>
|
|
191
|
+
{% comment %} The orchestrator swaps this for the trustmark, so it sits with
|
|
192
|
+
the card rather than under the seam between the panes. Removing it falls
|
|
193
|
+
back to the page-shell footer. {% endcomment %}
|
|
194
|
+
<div data-zl-attribution-anchor></div>
|
|
176
195
|
</section>
|
|
177
196
|
</div>
|
|
178
197
|
</zl-page-shell>
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
{% if branding.hero_url %}
|
|
8
8
|
<img class="zl-split__hero" src="{{ branding.hero_url }}" alt="" />
|
|
9
9
|
{% endif %}
|
|
10
|
+
{% unless branding.logo_url or branding.hero_url %}
|
|
11
|
+
{% comment %} Until branding.json names an asset, a decorative panel
|
|
12
|
+
keeps the split composition readable. {% endcomment %}
|
|
13
|
+
<div class="zl-split__placeholder" aria-hidden="true"></div>
|
|
14
|
+
{% endunless %}
|
|
10
15
|
</aside>
|
|
11
16
|
<section class="zl-split__form">
|
|
12
17
|
{% if branding.logo_url %}
|
|
@@ -23,18 +28,9 @@
|
|
|
23
28
|
{% endif %}
|
|
24
29
|
{% endif %}
|
|
25
30
|
|
|
26
|
-
{%
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{% assign alert_heading = err.text_key | alertHeading %}
|
|
30
|
-
{% assign alert_body = err.text_key | alertBody %}
|
|
31
|
-
<zl-alert severity="error" dismissible data-zl-step-error{% if alert_heading != "" %} heading="{{ alert_heading | t }}"{% endif %}>
|
|
32
|
-
{% if alert_body != "" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}
|
|
33
|
-
</zl-alert>
|
|
34
|
-
{% endif %}
|
|
35
|
-
{% endfor %}
|
|
36
|
-
{% endif %}
|
|
37
|
-
|
|
31
|
+
{% assign recover = actions | where: "name", "recover" | first %}
|
|
32
|
+
{% assign password_field = fields | where: "type", "password" | first %}
|
|
33
|
+
<div class="zl-field-group">
|
|
38
34
|
{% for f in fields %}
|
|
39
35
|
{% assign field_placeholder = f.text_key | fieldPlaceholder %}
|
|
40
36
|
{% assign field_help = f.text_key | fieldHelp %}
|
|
@@ -79,6 +75,7 @@
|
|
|
79
75
|
{% if f.required %}required{% endif %}
|
|
80
76
|
{% if field_placeholder != "" %}placeholder="{{ field_placeholder }}"{% endif %}
|
|
81
77
|
{% if field_err != "" %}invalid error="{{ field_err }}"{% endif %}
|
|
78
|
+
{% if recover and f.type == 'password' %}forgot-password-href="#" forgot-password-action="{{ recover.name }}" forgot-password-label="{{ recover.text_key | t }}"{% endif %}
|
|
82
79
|
>
|
|
83
80
|
{% if field_help != "" %}
|
|
84
81
|
<span slot="help">{{ field_help }}</span>
|
|
@@ -87,8 +84,26 @@
|
|
|
87
84
|
{% endcase %}
|
|
88
85
|
{% endfor %}
|
|
89
86
|
|
|
90
|
-
{%
|
|
91
|
-
|
|
87
|
+
{% if errors and errors.size > 0 %}
|
|
88
|
+
{% for err in errors %}
|
|
89
|
+
{% if err | formLevelError %}
|
|
90
|
+
{% assign alert_heading = err.text_key | alertHeading %}
|
|
91
|
+
{% assign alert_body = err.text_key | alertBody %}
|
|
92
|
+
<zl-alert data-zl-step-error{% if alert_heading != "" %} heading="{{ alert_heading | t }}"{% endif %}>
|
|
93
|
+
{% if alert_body != "" %}{{ alert_body | t }}{% elsif err.text_key %}{{ err.text_key | t }}{% else %}{{ err.message }}{% endif %}
|
|
94
|
+
</zl-alert>
|
|
95
|
+
{% endif %}
|
|
96
|
+
{% endfor %}
|
|
97
|
+
{% endif %}
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div class="zl-card-actions">
|
|
101
|
+
{% comment %}
|
|
102
|
+
A step can offer recovery without asking for a password (an identifier-only
|
|
103
|
+
screen, say). With no label row to sit on, the affordance keeps its own row
|
|
104
|
+
rather than disappearing.
|
|
105
|
+
{% endcomment %}
|
|
106
|
+
{% if recover and password_field == nil %}
|
|
92
107
|
<p class="zl-card-forgot">
|
|
93
108
|
<a href="#" class="zl-card-forgot__link" data-action="recover">{{ recover.text_key | t }}</a>
|
|
94
109
|
</p>
|
|
@@ -137,7 +152,6 @@
|
|
|
137
152
|
{% endunless %}
|
|
138
153
|
{% endif %}
|
|
139
154
|
|
|
140
|
-
|
|
141
155
|
{% assign register = actions | where: "name", "register" | first %}
|
|
142
156
|
{% if register %}
|
|
143
157
|
<p class="zl-card-nav">
|
|
@@ -151,6 +165,7 @@
|
|
|
151
165
|
{{ '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>
|
|
152
166
|
</p>
|
|
153
167
|
{% endif %}
|
|
168
|
+
</div>
|
|
154
169
|
|
|
155
170
|
{% if challenge %}
|
|
156
171
|
{% if challenge.method == "passkey" or challenge.method == "passkey_register" %}
|
|
@@ -173,6 +188,10 @@
|
|
|
173
188
|
|
|
174
189
|
{% mandatory_gates %}
|
|
175
190
|
</zl-card>
|
|
191
|
+
{% comment %} The orchestrator swaps this for the trustmark, so it sits with
|
|
192
|
+
the card rather than under the seam between the panes. Removing it falls
|
|
193
|
+
back to the page-shell footer. {% endcomment %}
|
|
194
|
+
<div data-zl-attribution-anchor></div>
|
|
176
195
|
</section>
|
|
177
196
|
</div>
|
|
178
197
|
</zl-page-shell>
|
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|
"objectType": "human-user",
|
|
7
7
|
"kind": "user-schema",
|
|
8
8
|
"type": "object",
|
|
9
|
+
"x-identifier": "email",
|
|
9
10
|
"description": "The default editable schema for human users.",
|
|
10
11
|
"x-auth-methods": {
|
|
11
12
|
"password": {
|
|
12
|
-
"enabled": true
|
|
13
|
-
"position": 1
|
|
13
|
+
"enabled": true
|
|
14
14
|
},
|
|
15
15
|
"passkey": {
|
|
16
|
-
"enabled": true
|
|
17
|
-
"position": 2
|
|
16
|
+
"enabled": true
|
|
18
17
|
}
|
|
19
18
|
},
|
|
20
19
|
"required": [
|
|
@@ -25,6 +25,12 @@
|
|
|
25
25
|
"kind": "passkey",
|
|
26
26
|
"primary": false,
|
|
27
27
|
"text_key": "identifier.action.passkey"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "register",
|
|
31
|
+
"kind": "navigate",
|
|
32
|
+
"primary": false,
|
|
33
|
+
"text_key": "identifier.action.register.link"
|
|
28
34
|
}
|
|
29
35
|
],
|
|
30
36
|
"transitions": {
|
|
@@ -36,6 +42,10 @@
|
|
|
36
42
|
},
|
|
37
43
|
"user_not_found": {
|
|
38
44
|
"target": "register"
|
|
45
|
+
},
|
|
46
|
+
"register": {
|
|
47
|
+
"target": "register",
|
|
48
|
+
"purpose": "register"
|
|
39
49
|
}
|
|
40
50
|
}
|
|
41
51
|
},
|
|
@@ -84,6 +94,12 @@
|
|
|
84
94
|
"kind": "passkey_register",
|
|
85
95
|
"primary": false,
|
|
86
96
|
"text_key": "register.action.passkey"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "sign_in",
|
|
100
|
+
"kind": "navigate",
|
|
101
|
+
"primary": false,
|
|
102
|
+
"text_key": "register.action.sign_in.link"
|
|
87
103
|
}
|
|
88
104
|
],
|
|
89
105
|
"transitions": {
|
|
@@ -95,6 +111,10 @@
|
|
|
95
111
|
},
|
|
96
112
|
"user_already_exists": {
|
|
97
113
|
"target": "password"
|
|
114
|
+
},
|
|
115
|
+
"sign_in": {
|
|
116
|
+
"target": "identifier",
|
|
117
|
+
"purpose": "login"
|
|
98
118
|
}
|
|
99
119
|
}
|
|
100
120
|
},
|