@zitadel/config 1.0.0-alpha.23 → 1.0.0-alpha.24
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/defaults/README-branding.md +26 -34
- package/dist/{defaults-DV6hY2KT.mjs → defaults-BnRHT7RQ.mjs} +17 -18
- package/dist/defaults-BnRHT7RQ.mjs.map +1 -0
- package/dist/{defaults-BxGCGdqS.d.mts → defaults-BnWXllBk.d.mts} +12 -9
- package/dist/defaults-BnWXllBk.d.mts.map +1 -0
- package/dist/defaults.d.mts +1 -1
- package/dist/defaults.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{meta-schemas-OowgSUfU.mjs → meta-schemas-K8f7YZYz.mjs} +12 -12
- package/dist/{meta-schemas-OowgSUfU.mjs.map → meta-schemas-K8f7YZYz.mjs.map} +1 -1
- package/dist/meta-schemas.mjs +1 -1
- package/dist/template.mjs +1 -1
- package/dist/template.mjs.map +1 -1
- package/meta-schemas/branding.json +5 -5
- package/meta-schemas/idp-connection.json +6 -6
- package/package.json +10 -6
- package/defaults/branding/hero/login.liquid +0 -238
- package/defaults/branding/split/login.liquid +0 -197
- package/defaults/branding/split-right/login.liquid +0 -197
- package/dist/defaults-BxGCGdqS.d.mts.map +0 -1
- package/dist/defaults-DV6hY2KT.mjs.map +0 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# Branding
|
|
2
2
|
|
|
3
|
-
This directory owns
|
|
4
|
-
|
|
5
|
-
`<zitadel-login>` component renders for every step.
|
|
3
|
+
This directory owns the structure of your login widget: `branding.json`
|
|
4
|
+
(asset URLs and appearance) plus `login.liquid`, the LiquidJS template the
|
|
5
|
+
`<zitadel-login>` component renders for every step. The page around the
|
|
6
|
+
widget (a split screen, a hero pane, marketing copy) belongs in your
|
|
7
|
+
application, not in this template.
|
|
6
8
|
|
|
7
9
|
## Workflow
|
|
8
10
|
|
|
9
|
-
1. Edit `login.liquid` (and `branding.json` for logo
|
|
11
|
+
1. Edit `login.liquid` (and `branding.json` for the logo URL).
|
|
10
12
|
2. `zitadel plan` — validates the template (LiquidJS parse, banned patterns,
|
|
11
13
|
the required `{% mandatory_gates %}` tag) and shows the pending revision.
|
|
12
14
|
3. `zitadel apply` — publishes an immutable branding revision. The login UI
|
|
@@ -22,31 +24,31 @@ by re-applying an earlier template.
|
|
|
22
24
|
tokens, not template CSS.
|
|
23
25
|
- User-facing copy goes through translation keys: `{{ "key" | t }}`.
|
|
24
26
|
- Keep the trailing `{% mandatory_gates %}` tag: it appends any required
|
|
25
|
-
field
|
|
26
|
-
|
|
27
|
+
field and the primary action your template left out, so the step stays
|
|
28
|
+
submittable.
|
|
27
29
|
|
|
28
30
|
## Make it yours
|
|
29
31
|
|
|
30
32
|
- Brand asset URLs go in `branding.json` and must use `https://`. Each template
|
|
31
|
-
decides which fields it renders: `centered`
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
decides which fields it renders: `centered` uses `logo_url`; `minimal` uses
|
|
34
|
+
none until you add it to `login.liquid`. Set the typeface with
|
|
35
|
+
`typography.font_family`. `typography.font_url` is loaded when the component
|
|
36
|
+
runs as `variant="page"`, which the sign-in pages `zitadel setup` generates
|
|
37
|
+
use: the component then adds that stylesheet to your page. With
|
|
38
|
+
`variant="widget"` it is not loaded, and your page loads the font.
|
|
36
39
|
|
|
37
40
|
```json
|
|
38
41
|
{
|
|
39
42
|
"$schema": "../meta/branding.json",
|
|
40
|
-
"layout": "
|
|
43
|
+
"layout": "centered",
|
|
41
44
|
"liquid_template": { "$file": "./login.liquid" },
|
|
42
|
-
"logo_url": "https://example.com/logo.svg"
|
|
43
|
-
"hero_url": "https://example.com/hero.jpg"
|
|
45
|
+
"logo_url": "https://example.com/logo.svg"
|
|
44
46
|
}
|
|
45
47
|
```
|
|
46
48
|
|
|
47
49
|
A well-formed URL that serves nothing is the one branding mistake nothing
|
|
48
50
|
else catches — it passes validation, publishes a revision, and then renders
|
|
49
|
-
as an invisible image. So `zitadel plan` probes
|
|
51
|
+
as an invisible image. So `zitadel plan` probes `logo_url` and `hero_url` and warns
|
|
50
52
|
(never fails) when it is unreachable, bodyless, or not an image, and the
|
|
51
53
|
login UI hides an asset that fails to load, restoring the shipped design's
|
|
52
54
|
no-asset content rather than leaving a gap. If the host is only
|
|
@@ -56,24 +58,15 @@ by re-applying an earlier template.
|
|
|
56
58
|
targets are left to the browser-side fallback instead of being requested by
|
|
57
59
|
the machine running `plan`.
|
|
58
60
|
|
|
59
|
-
`layout` is
|
|
60
|
-
|
|
61
|
-
`minimal`)
|
|
62
|
-
|
|
61
|
+
`layout` is **not** a design picker. The shipped template does not read it,
|
|
62
|
+
so changing it alone changes nothing on screen. Both shipped designs
|
|
63
|
+
(`centered`, `minimal`) carry `centered`. Switch designs with
|
|
64
|
+
`zitadel branding eject --design <name>`, don't edit `layout`.
|
|
63
65
|
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
starting point on token-styled `zl-hero__*` classes.
|
|
69
|
-
- On narrow viewports the brand pane collapses and the `.zl-split__compact`
|
|
70
|
-
node inside the form pane takes over (logo or brand line) — keep one so
|
|
71
|
-
your identity survives on phones.
|
|
72
|
-
- The split chrome is tunable from your template root's `style` attribute
|
|
73
|
-
(`--zl-split-columns`, `--zl-split-align`, `--zl-split-brand-mobile`), and
|
|
74
|
-
`zl-split--right` on the wrapper mirrors the panes. Knob reference:
|
|
75
|
-
[`docs/design/branding/templates.md`](https://github.com/zitadel/nextgen/blob/main/docs/design/branding/templates.md).
|
|
76
|
-
- The "Secured with Zitadel" attribution is licence-gated and on by default.
|
|
66
|
+
- The "Secured with Zitadel" attribution is always shown, and `branding.json`
|
|
67
|
+
has no setting for it. It sits at the foot of the login; to place it
|
|
68
|
+
elsewhere, put
|
|
69
|
+
`<div data-zl-attribution-anchor></div>` where you want it in `login.liquid`.
|
|
77
70
|
- Back-navigation: the engine injects a `kind: "back"` action on steps
|
|
78
71
|
that can return to their predecessor, and the browser's back gesture
|
|
79
72
|
submits it automatically — the shipped designs deliberately render **no
|
|
@@ -92,5 +85,4 @@ by re-applying an earlier template.
|
|
|
92
85
|
loops so the injected action doesn't render as a stray secondary button.
|
|
93
86
|
|
|
94
87
|
Start over anytime with `zitadel branding eject --design <name>` (designs:
|
|
95
|
-
`centered`, `
|
|
96
|
-
overwrite).
|
|
88
|
+
`centered`, `minimal`; add `--force` to overwrite).
|
|
@@ -348,32 +348,31 @@ function presetTemplates(preset) {
|
|
|
348
348
|
return PRESET_TEMPLATES[preset];
|
|
349
349
|
}
|
|
350
350
|
/**
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
351
|
+
* Widget-template starting points `zitadel branding eject --design <name>`
|
|
352
|
+
* scaffolds into `.zitadel/branding/` (setup never does, #1039). Only
|
|
353
|
+
* widget structure belongs here: `centered` is the bundled default card and
|
|
354
|
+
* `minimal` the same form without card chrome. Page layouts (the former
|
|
355
|
+
* `split`, `split-right`, `hero`) were page chrome and live in the embedding
|
|
356
|
+
* application instead; revisions already published from them keep rendering
|
|
357
|
+
* (see docs/design/branding/customization-strategy.md). Add a design here
|
|
358
|
+
* only when it changes the widget's structure, not the page around it.
|
|
356
359
|
*/
|
|
357
|
-
const BRANDING_DESIGNS = [
|
|
358
|
-
"centered",
|
|
359
|
-
"split",
|
|
360
|
-
"split-right",
|
|
361
|
-
"hero",
|
|
362
|
-
"minimal"
|
|
363
|
-
];
|
|
360
|
+
const BRANDING_DESIGNS = ["centered", "minimal"];
|
|
364
361
|
const DEFAULT_BRANDING_DESIGN = "centered";
|
|
365
|
-
/**
|
|
362
|
+
/**
|
|
363
|
+
* Descriptor `layout` each design's `branding.json` carries. Both shipped
|
|
364
|
+
* designs are widget structure and carry `centered`; the wire enum still has
|
|
365
|
+
* `split` for revisions published from the retired page-layout designs
|
|
366
|
+
* (#1039), which no catalog entry produces any more.
|
|
367
|
+
*/
|
|
366
368
|
const DESIGN_LAYOUTS = {
|
|
367
369
|
centered: "centered",
|
|
368
|
-
split: "split",
|
|
369
|
-
"split-right": "split",
|
|
370
|
-
hero: "split",
|
|
371
370
|
minimal: "centered"
|
|
372
371
|
};
|
|
373
372
|
/**
|
|
374
373
|
* Renders the scaffold files for a branding design. The `centered` template
|
|
375
374
|
* is a drift-tested copy of the bundled default in `@zitadel/components`;
|
|
376
|
-
*
|
|
375
|
+
* `minimal` is an authored variant of it.
|
|
377
376
|
*/
|
|
378
377
|
function getDefaultBrandingConfig(design = DEFAULT_BRANDING_DESIGN) {
|
|
379
378
|
if (!Object.hasOwn(DESIGN_LAYOUTS, design)) throw new Error(`unknown branding design ${JSON.stringify(design)} (known designs: ${BRANDING_DESIGNS.join(", ")})`);
|
|
@@ -540,4 +539,4 @@ function trimTrailingSlash(value) {
|
|
|
540
539
|
//#endregion
|
|
541
540
|
export { brandingReadmeContent as _, DEFAULT_BUILTIN_SCHEMA_BASE as a, DEFAULT_SCHEMA_CONFIG_PATH as c, SETUP_PRESETS as d, SETUP_USE_CASES as f, getDefaultLoginFlow as g, getDefaultHumanUserSchema as h, DEFAULT_BRANDING_TEMPLATE_PATH as i, DEFAULT_SETUP_PRESET as l, getDefaultBrandingConfig as m, DEFAULT_BRANDING_CONFIG_PATH as n, DEFAULT_FLOW_CONFIG_PATH as o, defaultHumanUserSchemaUrl as p, DEFAULT_BRANDING_DESIGN as r, DEFAULT_FLOW_SCHEMA_URI as s, BRANDING_DESIGNS as t, DEFAULT_SETUP_USE_CASE as u, flowsReadmeContent as v, schemasReadmeContent as y };
|
|
542
541
|
|
|
543
|
-
//# sourceMappingURL=defaults-
|
|
542
|
+
//# sourceMappingURL=defaults-BnRHT7RQ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults-BnRHT7RQ.mjs","names":["defaultHumanUserSchemaTemplate","defaultLoginFlowTemplate","passkeyFirstHumanUserSchemaTemplate","passkeyFirstLoginFlowTemplate"],"sources":["../defaults/default-human-user.json","../defaults/default-login.json","../defaults/presets/passkey-first/human-user.json","../defaults/presets/passkey-first/login.json","../src/readmes.ts","../src/defaults.ts"],"sourcesContent":["","","","","import { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * README content the CLI copies to `.zitadel/schemas/README.md` during setup.\n * Source of truth is `packages/config/defaults/README-schemas.md`; the Go\n * server-side embed reads the same file (see `defaults.go`).\n */\nexport function schemasReadmeContent(): string {\n return readFileSync(readmeUrl(\"README-schemas.md\"), \"utf8\");\n}\n\n/**\n * README content the CLI copies to `.zitadel/flows/README.md` during setup.\n * Source of truth is `packages/config/defaults/README-flows.md`.\n */\nexport function flowsReadmeContent(): string {\n return readFileSync(readmeUrl(\"README-flows.md\"), \"utf8\");\n}\n\n/**\n * README content the CLI copies to `.zitadel/branding/README.md` when a\n * branding design is ejected. Source of truth is\n * `packages/config/defaults/README-branding.md`.\n */\nexport function brandingReadmeContent(): string {\n return readFileSync(readmeUrl(\"README-branding.md\"), \"utf8\");\n}\n\nfunction readmeUrl(filename: string): string {\n return fileURLToPath(new URL(`../defaults/${filename}`, import.meta.url));\n}\n","import { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type {\n CreateFlowDefinitionBodyFlowDefinition,\n CreateSchemaBody,\n} from \"@zitadel/api/generated/model\";\n\nimport defaultHumanUserSchemaTemplate from \"../defaults/default-human-user.json\" with {\n type: \"json\",\n};\nimport defaultLoginFlowTemplate from \"../defaults/default-login.json\" with {\n type: \"json\",\n};\nimport passkeyFirstHumanUserSchemaTemplate from \"../defaults/presets/passkey-first/human-user.json\" with {\n type: \"json\",\n};\nimport passkeyFirstLoginFlowTemplate from \"../defaults/presets/passkey-first/login.json\" with {\n type: \"json\",\n};\n\nexport { brandingReadmeContent, flowsReadmeContent, schemasReadmeContent } from \"./readmes.js\";\n\nexport const DEFAULT_BUILTIN_SCHEMA_BASE = \"https://nextgen.com/api/schemas\";\nexport const DEFAULT_FLOW_SCHEMA_URI = \"https://nextgen.com/flow-definition.json\";\nexport const DEFAULT_SCHEMA_CONFIG_PATH = \".zitadel/schemas/default-human-user.json\";\nexport const DEFAULT_FLOW_CONFIG_PATH = \".zitadel/flows/default-login.json\";\nexport const DEFAULT_BRANDING_CONFIG_PATH = \".zitadel/branding/branding.json\";\nexport const DEFAULT_BRANDING_TEMPLATE_PATH = \".zitadel/branding/login.liquid\";\n\n/**\n * Named schema+flow bundles `zitadel setup` can scaffold (#448: the prompt\n * fires before any `.zitadel/` file is written; each preset maps to a\n * pre-defined bundle the CLI copies on first setup). `password-first` is\n * today's default; `passkey-first` puts a passkey ceremony on the login\n * entry step with an email→password fallback path.\n */\nexport const SETUP_PRESETS = [\"password-first\", \"passkey-first\"] as const;\n\nexport type SetupPreset = (typeof SETUP_PRESETS)[number];\n\nexport const DEFAULT_SETUP_PRESET: SetupPreset = \"password-first\";\n\nconst PRESET_TEMPLATES: Record<SetupPreset, { schema: unknown; flow: unknown }> = {\n \"password-first\": {\n schema: defaultHumanUserSchemaTemplate,\n flow: defaultLoginFlowTemplate,\n },\n \"passkey-first\": {\n schema: passkeyFirstHumanUserSchemaTemplate,\n flow: passkeyFirstLoginFlowTemplate,\n },\n};\n\nfunction presetTemplates(preset: string): { schema: unknown; flow: unknown } {\n // Own-property check: indexing a plain object with an arbitrary string\n // would let \"__proto__\"/\"constructor\" resolve via the prototype chain\n // and bypass the unknown-preset error.\n if (!Object.hasOwn(PRESET_TEMPLATES, preset)) {\n throw new Error(\n `unknown setup preset ${JSON.stringify(preset)} (known presets: ${SETUP_PRESETS.join(\", \")})`,\n );\n }\n return PRESET_TEMPLATES[preset as SetupPreset];\n}\n\n/**\n * Widget-template starting points `zitadel branding eject --design <name>`\n * scaffolds into `.zitadel/branding/` (setup never does, #1039). Only\n * widget structure belongs here: `centered` is the bundled default card and\n * `minimal` the same form without card chrome. Page layouts (the former\n * `split`, `split-right`, `hero`) were page chrome and live in the embedding\n * application instead; revisions already published from them keep rendering\n * (see docs/design/branding/customization-strategy.md). Add a design here\n * only when it changes the widget's structure, not the page around it.\n */\nexport const BRANDING_DESIGNS = [\"centered\", \"minimal\"] as const;\n\nexport type BrandingDesign = (typeof BRANDING_DESIGNS)[number];\n\nexport const DEFAULT_BRANDING_DESIGN: BrandingDesign = \"centered\";\n\n/**\n * Descriptor `layout` each design's `branding.json` carries. Both shipped\n * designs are widget structure and carry `centered`; the wire enum still has\n * `split` for revisions published from the retired page-layout designs\n * (#1039), which no catalog entry produces any more.\n */\nconst DESIGN_LAYOUTS: Record<BrandingDesign, \"centered\"> = {\n centered: \"centered\",\n minimal: \"centered\",\n};\n\nexport type DefaultBrandingConfig = {\n /** The `.zitadel/branding/branding.json` descriptor body (sans `$schema`). */\n branding: {\n layout: \"centered\";\n /** A `$file` reference to the sibling template; the CLI inlines it before publishing. */\n liquid_template: { $file: string };\n };\n /** The `.zitadel/branding/login.liquid` template content. */\n template: string;\n};\n\n/**\n * Renders the scaffold files for a branding design. The `centered` template\n * is a drift-tested copy of the bundled default in `@zitadel/components`;\n * `minimal` is an authored variant of it.\n */\nexport function getDefaultBrandingConfig(\n design: string = DEFAULT_BRANDING_DESIGN,\n): DefaultBrandingConfig {\n if (!Object.hasOwn(DESIGN_LAYOUTS, design)) {\n throw new Error(\n `unknown branding design ${JSON.stringify(design)} (known designs: ${BRANDING_DESIGNS.join(\", \")})`,\n );\n }\n const known = design as BrandingDesign;\n return {\n branding: {\n layout: DESIGN_LAYOUTS[known],\n liquid_template: { $file: \"./login.liquid\" },\n },\n template: readFileSync(packageFilePath(`../defaults/branding/${known}/login.liquid`), \"utf8\"),\n };\n}\n\n/**\n * Resolves a package-relative file to a real filesystem path. Vite-driven\n * runtimes (vitest in dependent packages) rewrite `import.meta.url` to a\n * `/@fs/...` URL, which `fileURLToPath` passes through verbatim — strip the\n * prefix so `readFileSync` gets an actual path.\n */\nfunction packageFilePath(relative: string): string {\n const path = fileURLToPath(new URL(relative, import.meta.url));\n return path.startsWith(\"/@fs/\") ? path.slice(\"/@fs\".length) : path;\n}\n\n/**\n * The second setup axis (#448): *who* signs in, orthogonal to the sign-in\n * preset (*how* they sign in). The sign-in preset owns the flow shape and\n * auth methods; the use case owns which profile fields the schema collects.\n * Composing the two keeps us at one field catalog + one flow-per-preset\n * instead of a bundle per (use case × preset) pair. `minimal` is the\n * default and never blocks non-interactive runs.\n */\nexport const SETUP_USE_CASES = [\"minimal\", \"consumer\", \"business\"] as const;\n\nexport type SetupUseCase = (typeof SETUP_USE_CASES)[number];\n\nexport const DEFAULT_SETUP_USE_CASE: SetupUseCase = \"minimal\";\n\n/**\n * Fields each use case collects, in register/display order. `email` is\n * always first and stays the only required property; the rest are optional\n * profile attributes gathered on the flow's register step. `givenName`/\n * `familyName` are the attributes the backend reads for a user's identity\n * (`internal/domain/user.go`); `companyName` is stored as a plain user\n * attribute today — there is no org/team model behind it yet.\n */\nconst USE_CASE_FIELDS: Record<SetupUseCase, readonly string[]> = {\n minimal: [\"email\"],\n consumer: [\"email\", \"givenName\", \"familyName\"],\n business: [\"email\", \"givenName\", \"familyName\", \"companyName\"],\n};\n\n/**\n * JSON-Schema bodies for the optional profile fields the CLI composes in per\n * use case. The shipped templates are an email-only baseline (shared verbatim\n * with the Go server fallback), so this catalog owns every field beyond\n * `email`. Authored in the templates' style — no `x-claim`, since the backend\n * maps identity by attribute name and the user-property meta-schema defines no\n * claim keyword. `givenName`/`familyName` are the attributes the backend reads\n * for a user's identity (`internal/domain/user.go`).\n */\nconst EXTRA_FIELD_BODIES: Record<string, Record<string, unknown>> = {\n givenName: {\n type: \"string\",\n maxLength: 50,\n description: \"The user's given (first) name.\",\n },\n familyName: {\n type: \"string\",\n maxLength: 50,\n description: \"The user's family (last) name.\",\n },\n companyName: {\n type: \"string\",\n maxLength: 200,\n description: \"The user's company name.\",\n },\n};\n\nfunction useCaseFields(useCase: string): readonly string[] {\n // Own-property check, same rationale as presetTemplates: keep prototype\n // keys (\"__proto__\" etc.) from resolving to a real field list.\n if (!Object.hasOwn(USE_CASE_FIELDS, useCase)) {\n throw new Error(\n `unknown setup use case ${JSON.stringify(useCase)} (known use cases: ${SETUP_USE_CASES.join(\", \")})`,\n );\n }\n return USE_CASE_FIELDS[useCase as SetupUseCase];\n}\n\n/**\n * The body for one use-case field: reuse the template's authored body when it\n * has one (so composed schemas match the shipped defaults for shared fields),\n * else the extra catalog, else a generic editable string so composition stays\n * total for any field a future use case introduces.\n */\nfunction fieldBody(\n field: string,\n templateProps: Record<string, Record<string, unknown>>,\n): Record<string, unknown> {\n if (Object.hasOwn(templateProps, field)) {\n return { ...templateProps[field] };\n }\n if (Object.hasOwn(EXTRA_FIELD_BODIES, field)) {\n return { ...EXTRA_FIELD_BODIES[field] };\n }\n return { type: \"string\", description: `The user's ${field}.` };\n}\n\n/**\n * Narrow a rendered schema to the chosen use case: keep the preset-owned\n * header and `x-auth-methods`, but replace `properties`/`required` with the\n * use case's field set. `email` is the sole required property.\n */\nfunction applyUseCaseToSchema(\n schema: Record<string, unknown>,\n useCase: string,\n): Record<string, unknown> {\n const templateProps = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const properties: Record<string, unknown> = {};\n for (const field of useCaseFields(useCase)) {\n properties[field] = fieldBody(field, templateProps);\n }\n return { ...schema, required: [\"email\"], properties };\n}\n\n/**\n * Derive the flow's register-step fields from the use case rather than\n * copying a hard-coded list into every bundle (#448): the register step\n * collects exactly what the schema defines. Other steps are untouched.\n */\nfunction applyUseCaseToFlow(\n flow: Record<string, unknown>,\n useCase: string,\n): Record<string, unknown> {\n const fields = [...useCaseFields(useCase)];\n const steps = (flow.steps as Array<Record<string, unknown>>).map((step) =>\n step.name === \"register\" ? { ...step, fields } : step,\n );\n return { ...flow, steps };\n}\n\nexport type DefaultConfigRenderOptions = {\n builtinSchemaBase?: string;\n userSchemaUrl?: string;\n /** Which flow/auth bundle to render; defaults to {@link DEFAULT_SETUP_PRESET}. */\n preset?: string;\n /** Which field set to collect; defaults to {@link DEFAULT_SETUP_USE_CASE}. */\n useCase?: string;\n};\n\nexport function defaultHumanUserSchemaUrl(\n builtinSchemaBase = DEFAULT_BUILTIN_SCHEMA_BASE,\n): string {\n return `${trimTrailingSlash(builtinSchemaBase)}/default-human-user.json`;\n}\n\nexport function getDefaultHumanUserSchema(\n options: DefaultConfigRenderOptions = {},\n): CreateSchemaBody {\n const builtinSchemaBase = trimTrailingSlash(\n options.builtinSchemaBase ?? DEFAULT_BUILTIN_SCHEMA_BASE,\n );\n const rendered = renderTemplate(presetTemplates(options.preset ?? DEFAULT_SETUP_PRESET).schema, {\n SERVER_URL: builtinSchemaBase,\n USER_SCHEMA_URL: options.userSchemaUrl ?? defaultHumanUserSchemaUrl(builtinSchemaBase),\n }) as Record<string, unknown>;\n return applyUseCaseToSchema(\n rendered,\n options.useCase ?? DEFAULT_SETUP_USE_CASE,\n ) as CreateSchemaBody;\n}\n\nexport function getDefaultLoginFlow(\n options: DefaultConfigRenderOptions = {},\n): CreateFlowDefinitionBodyFlowDefinition {\n const builtinSchemaBase = trimTrailingSlash(\n options.builtinSchemaBase ?? DEFAULT_BUILTIN_SCHEMA_BASE,\n );\n const rendered = renderTemplate(presetTemplates(options.preset ?? DEFAULT_SETUP_PRESET).flow, {\n SERVER_URL: builtinSchemaBase,\n USER_SCHEMA_URL: options.userSchemaUrl ?? defaultHumanUserSchemaUrl(builtinSchemaBase),\n }) as Record<string, unknown>;\n return applyUseCaseToFlow(\n rendered,\n options.useCase ?? DEFAULT_SETUP_USE_CASE,\n ) as CreateFlowDefinitionBodyFlowDefinition;\n}\n\nfunction renderTemplate(value: unknown, replacements: Record<string, string>): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => renderTemplate(item, replacements));\n }\n if (value !== null && typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value).map(([key, item]) => [key, renderTemplate(item, replacements)]),\n );\n }\n if (typeof value === \"string\") {\n return value.replaceAll(/\\$\\{([A-Z_]+)\\}/g, (match, name: string) => {\n return replacements[name] ?? match;\n });\n }\n return value;\n}\n\nfunction trimTrailingSlash(value: string): string {\n let trimmed = value;\n while (trimmed.endsWith(\"/\")) {\n trimmed = trimmed.slice(0, -1);\n }\n return trimmed;\n}\n"],"mappings":"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"}
|
|
@@ -37,18 +37,21 @@ declare const SETUP_PRESETS: readonly ["password-first", "passkey-first"];
|
|
|
37
37
|
type SetupPreset = (typeof SETUP_PRESETS)[number];
|
|
38
38
|
declare const DEFAULT_SETUP_PRESET: SetupPreset;
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
40
|
+
* Widget-template starting points `zitadel branding eject --design <name>`
|
|
41
|
+
* scaffolds into `.zitadel/branding/` (setup never does, #1039). Only
|
|
42
|
+
* widget structure belongs here: `centered` is the bundled default card and
|
|
43
|
+
* `minimal` the same form without card chrome. Page layouts (the former
|
|
44
|
+
* `split`, `split-right`, `hero`) were page chrome and live in the embedding
|
|
45
|
+
* application instead; revisions already published from them keep rendering
|
|
46
|
+
* (see docs/design/branding/customization-strategy.md). Add a design here
|
|
47
|
+
* only when it changes the widget's structure, not the page around it.
|
|
45
48
|
*/
|
|
46
|
-
declare const BRANDING_DESIGNS: readonly ["centered", "
|
|
49
|
+
declare const BRANDING_DESIGNS: readonly ["centered", "minimal"];
|
|
47
50
|
type BrandingDesign = (typeof BRANDING_DESIGNS)[number];
|
|
48
51
|
declare const DEFAULT_BRANDING_DESIGN: BrandingDesign;
|
|
49
52
|
type DefaultBrandingConfig = {
|
|
50
53
|
/** The `.zitadel/branding/branding.json` descriptor body (sans `$schema`). */branding: {
|
|
51
|
-
layout: "centered"
|
|
54
|
+
layout: "centered"; /** A `$file` reference to the sibling template; the CLI inlines it before publishing. */
|
|
52
55
|
liquid_template: {
|
|
53
56
|
$file: string;
|
|
54
57
|
};
|
|
@@ -58,7 +61,7 @@ type DefaultBrandingConfig = {
|
|
|
58
61
|
/**
|
|
59
62
|
* Renders the scaffold files for a branding design. The `centered` template
|
|
60
63
|
* is a drift-tested copy of the bundled default in `@zitadel/components`;
|
|
61
|
-
*
|
|
64
|
+
* `minimal` is an authored variant of it.
|
|
62
65
|
*/
|
|
63
66
|
declare function getDefaultBrandingConfig(design?: string): DefaultBrandingConfig;
|
|
64
67
|
/**
|
|
@@ -83,4 +86,4 @@ declare function getDefaultHumanUserSchema(options?: DefaultConfigRenderOptions)
|
|
|
83
86
|
declare function getDefaultLoginFlow(options?: DefaultConfigRenderOptions): CreateFlowDefinitionBodyFlowDefinition;
|
|
84
87
|
//#endregion
|
|
85
88
|
export { flowsReadmeContent as C, brandingReadmeContent as S, SetupUseCase as _, DEFAULT_BRANDING_TEMPLATE_PATH as a, getDefaultHumanUserSchema as b, DEFAULT_FLOW_SCHEMA_URI as c, DEFAULT_SETUP_USE_CASE as d, DefaultBrandingConfig as f, SetupPreset as g, SETUP_USE_CASES as h, DEFAULT_BRANDING_DESIGN as i, DEFAULT_SCHEMA_CONFIG_PATH as l, SETUP_PRESETS as m, BrandingDesign as n, DEFAULT_BUILTIN_SCHEMA_BASE as o, DefaultConfigRenderOptions as p, DEFAULT_BRANDING_CONFIG_PATH as r, DEFAULT_FLOW_CONFIG_PATH as s, BRANDING_DESIGNS as t, DEFAULT_SETUP_PRESET as u, defaultHumanUserSchemaUrl as v, schemasReadmeContent as w, getDefaultLoginFlow as x, getDefaultBrandingConfig as y };
|
|
86
|
-
//# sourceMappingURL=defaults-
|
|
89
|
+
//# sourceMappingURL=defaults-BnWXllBk.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults-BnWXllBk.d.mts","names":[],"sources":["../src/readmes.ts","../src/defaults.ts"],"mappings":";;;;;;AAQA;;iBAAgB,oBAAA,CAAA;;;AAQhB;;iBAAgB,kBAAA,CAAA;;;AAShB;;;iBAAgB,qBAAA,CAAA;;;cCFH,2BAAA;AAAA,cACA,uBAAA;AAAA,cACA,0BAAA;AAAA,cACA,wBAAA;AAAA,cACA,4BAAA;AAAA,cACA,8BAAA;ADZb;;;;;AASA;;AATA,cCqBa,aAAA;AAAA,KAED,WAAA,WAAsB,aAAA;AAAA,cAErB,oBAAA,EAAsB,WAAA;;;;AAlBnC;;;;;AACA;;cAoDa,gBAAA;AAAA,KAED,cAAA,WAAyB,gBAAA;AAAA,cAExB,uBAAA,EAAyB,cAAA;AAAA,KAa1B,qBAAA;EApE2B,8EAsErC,QAAA;IACE,MAAA,cAvEmC;IAyEnC,eAAA;MAAmB,KAAA;IAAA;EAAA,GAxEc;EA2EnC,QAAA;AAAA;;;;AAzEF;;iBAiFgB,wBAAA,CACd,MAAA,YACC,qBAAA;;;AA1EH;;;;;AAEA;cA2Ga,eAAA;AAAA,KAED,YAAA,WAAuB,eAAA;AAAA,cAEtB,sBAAA,EAAwB,YAAA;AAAA,KA0GzB,0BAAA;EACV,iBAAA;EACA,aAAA;EAEA,MAAA,WA3N+D;EA6N/D,OAAA;AAAA;AAAA,iBAGc,yBAAA,CACd,iBAAA;AAAA,iBAKc,yBAAA,CACd,OAAA,GAAS,0BAAA,GACR,gBAAA;AAAA,iBAca,mBAAA,CACd,OAAA,GAAS,0BAAA,GACR,sCAAA"}
|
package/dist/defaults.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as flowsReadmeContent, S as brandingReadmeContent, _ as SetupUseCase, a as DEFAULT_BRANDING_TEMPLATE_PATH, b as getDefaultHumanUserSchema, c as DEFAULT_FLOW_SCHEMA_URI, d as DEFAULT_SETUP_USE_CASE, f as DefaultBrandingConfig, g as SetupPreset, h as SETUP_USE_CASES, i as DEFAULT_BRANDING_DESIGN, l as DEFAULT_SCHEMA_CONFIG_PATH, m as SETUP_PRESETS, n as BrandingDesign, o as DEFAULT_BUILTIN_SCHEMA_BASE, p as DefaultConfigRenderOptions, r as DEFAULT_BRANDING_CONFIG_PATH, s as DEFAULT_FLOW_CONFIG_PATH, t as BRANDING_DESIGNS, u as DEFAULT_SETUP_PRESET, v as defaultHumanUserSchemaUrl, w as schemasReadmeContent, x as getDefaultLoginFlow, y as getDefaultBrandingConfig } from "./defaults-
|
|
1
|
+
import { C as flowsReadmeContent, S as brandingReadmeContent, _ as SetupUseCase, a as DEFAULT_BRANDING_TEMPLATE_PATH, b as getDefaultHumanUserSchema, c as DEFAULT_FLOW_SCHEMA_URI, d as DEFAULT_SETUP_USE_CASE, f as DefaultBrandingConfig, g as SetupPreset, h as SETUP_USE_CASES, i as DEFAULT_BRANDING_DESIGN, l as DEFAULT_SCHEMA_CONFIG_PATH, m as SETUP_PRESETS, n as BrandingDesign, o as DEFAULT_BUILTIN_SCHEMA_BASE, p as DefaultConfigRenderOptions, r as DEFAULT_BRANDING_CONFIG_PATH, s as DEFAULT_FLOW_CONFIG_PATH, t as BRANDING_DESIGNS, u as DEFAULT_SETUP_PRESET, v as defaultHumanUserSchemaUrl, w as schemasReadmeContent, x as getDefaultLoginFlow, y as getDefaultBrandingConfig } from "./defaults-BnWXllBk.mjs";
|
|
2
2
|
export { BRANDING_DESIGNS, BrandingDesign, DEFAULT_BRANDING_CONFIG_PATH, DEFAULT_BRANDING_DESIGN, DEFAULT_BRANDING_TEMPLATE_PATH, DEFAULT_BUILTIN_SCHEMA_BASE, DEFAULT_FLOW_CONFIG_PATH, DEFAULT_FLOW_SCHEMA_URI, DEFAULT_SCHEMA_CONFIG_PATH, DEFAULT_SETUP_PRESET, DEFAULT_SETUP_USE_CASE, DefaultBrandingConfig, DefaultConfigRenderOptions, SETUP_PRESETS, SETUP_USE_CASES, SetupPreset, SetupUseCase, brandingReadmeContent, defaultHumanUserSchemaUrl, flowsReadmeContent, getDefaultBrandingConfig, getDefaultHumanUserSchema, getDefaultLoginFlow, schemasReadmeContent };
|
package/dist/defaults.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as brandingReadmeContent, a as DEFAULT_BUILTIN_SCHEMA_BASE, c as DEFAULT_SCHEMA_CONFIG_PATH, d as SETUP_PRESETS, f as SETUP_USE_CASES, g as getDefaultLoginFlow, h as getDefaultHumanUserSchema, i as DEFAULT_BRANDING_TEMPLATE_PATH, l as DEFAULT_SETUP_PRESET, m as getDefaultBrandingConfig, n as DEFAULT_BRANDING_CONFIG_PATH, o as DEFAULT_FLOW_CONFIG_PATH, p as defaultHumanUserSchemaUrl, r as DEFAULT_BRANDING_DESIGN, s as DEFAULT_FLOW_SCHEMA_URI, t as BRANDING_DESIGNS, u as DEFAULT_SETUP_USE_CASE, v as flowsReadmeContent, y as schemasReadmeContent } from "./defaults-
|
|
1
|
+
import { _ as brandingReadmeContent, a as DEFAULT_BUILTIN_SCHEMA_BASE, c as DEFAULT_SCHEMA_CONFIG_PATH, d as SETUP_PRESETS, f as SETUP_USE_CASES, g as getDefaultLoginFlow, h as getDefaultHumanUserSchema, i as DEFAULT_BRANDING_TEMPLATE_PATH, l as DEFAULT_SETUP_PRESET, m as getDefaultBrandingConfig, n as DEFAULT_BRANDING_CONFIG_PATH, o as DEFAULT_FLOW_CONFIG_PATH, p as defaultHumanUserSchemaUrl, r as DEFAULT_BRANDING_DESIGN, s as DEFAULT_FLOW_SCHEMA_URI, t as BRANDING_DESIGNS, u as DEFAULT_SETUP_USE_CASE, v as flowsReadmeContent, y as schemasReadmeContent } from "./defaults-BnRHT7RQ.mjs";
|
|
2
2
|
export { BRANDING_DESIGNS, DEFAULT_BRANDING_CONFIG_PATH, DEFAULT_BRANDING_DESIGN, DEFAULT_BRANDING_TEMPLATE_PATH, DEFAULT_BUILTIN_SCHEMA_BASE, DEFAULT_FLOW_CONFIG_PATH, DEFAULT_FLOW_SCHEMA_URI, DEFAULT_SCHEMA_CONFIG_PATH, DEFAULT_SETUP_PRESET, DEFAULT_SETUP_USE_CASE, SETUP_PRESETS, SETUP_USE_CASES, brandingReadmeContent, defaultHumanUserSchemaUrl, flowsReadmeContent, getDefaultBrandingConfig, getDefaultHumanUserSchema, getDefaultLoginFlow, schemasReadmeContent };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isCanonicalLoopbackHttpUrl } from "./branding-url.mjs";
|
|
2
|
-
import { C as flowsReadmeContent, S as brandingReadmeContent, _ as SetupUseCase, a as DEFAULT_BRANDING_TEMPLATE_PATH, b as getDefaultHumanUserSchema, c as DEFAULT_FLOW_SCHEMA_URI, d as DEFAULT_SETUP_USE_CASE, f as DefaultBrandingConfig, g as SetupPreset, h as SETUP_USE_CASES, i as DEFAULT_BRANDING_DESIGN, l as DEFAULT_SCHEMA_CONFIG_PATH, m as SETUP_PRESETS, n as BrandingDesign, o as DEFAULT_BUILTIN_SCHEMA_BASE, p as DefaultConfigRenderOptions, r as DEFAULT_BRANDING_CONFIG_PATH, s as DEFAULT_FLOW_CONFIG_PATH, t as BRANDING_DESIGNS, u as DEFAULT_SETUP_PRESET, v as defaultHumanUserSchemaUrl, w as schemasReadmeContent, x as getDefaultLoginFlow, y as getDefaultBrandingConfig } from "./defaults-
|
|
2
|
+
import { C as flowsReadmeContent, S as brandingReadmeContent, _ as SetupUseCase, a as DEFAULT_BRANDING_TEMPLATE_PATH, b as getDefaultHumanUserSchema, c as DEFAULT_FLOW_SCHEMA_URI, d as DEFAULT_SETUP_USE_CASE, f as DefaultBrandingConfig, g as SetupPreset, h as SETUP_USE_CASES, i as DEFAULT_BRANDING_DESIGN, l as DEFAULT_SCHEMA_CONFIG_PATH, m as SETUP_PRESETS, n as BrandingDesign, o as DEFAULT_BUILTIN_SCHEMA_BASE, p as DefaultConfigRenderOptions, r as DEFAULT_BRANDING_CONFIG_PATH, s as DEFAULT_FLOW_CONFIG_PATH, t as BRANDING_DESIGNS, u as DEFAULT_SETUP_PRESET, v as defaultHumanUserSchemaUrl, w as schemasReadmeContent, x as getDefaultLoginFlow, y as getDefaultBrandingConfig } from "./defaults-BnWXllBk.mjs";
|
|
3
3
|
import { BRANDING_FILE_SCHEMA_REF, FLOW_FILE_SCHEMA_REF, META_SCHEMA_DIR, MetaSchemaFile, metaSchemaFiles } from "./meta-schemas.mjs";
|
|
4
4
|
import { USER_PROPERTY_DEFAULTS, normalizeFlowBody, normalizeSchemaBody } from "./normalize.mjs";
|
|
5
5
|
import { brandingConfigSchema, brandingWireSchema, createFlowDefinitionRequestSchema, flowConfigSchema, localFileReferenceSchema, schemaConfigSchema } from "./schemas.mjs";
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isCanonicalLoopbackHttpUrl } from "./branding-url.mjs";
|
|
2
|
-
import { _ as brandingReadmeContent, a as DEFAULT_BUILTIN_SCHEMA_BASE, c as DEFAULT_SCHEMA_CONFIG_PATH, d as SETUP_PRESETS, f as SETUP_USE_CASES, g as getDefaultLoginFlow, h as getDefaultHumanUserSchema, i as DEFAULT_BRANDING_TEMPLATE_PATH, l as DEFAULT_SETUP_PRESET, m as getDefaultBrandingConfig, n as DEFAULT_BRANDING_CONFIG_PATH, o as DEFAULT_FLOW_CONFIG_PATH, p as defaultHumanUserSchemaUrl, r as DEFAULT_BRANDING_DESIGN, s as DEFAULT_FLOW_SCHEMA_URI, t as BRANDING_DESIGNS, u as DEFAULT_SETUP_USE_CASE, v as flowsReadmeContent, y as schemasReadmeContent } from "./defaults-
|
|
3
|
-
import { i as metaSchemaFiles, n as FLOW_FILE_SCHEMA_REF, r as META_SCHEMA_DIR, t as BRANDING_FILE_SCHEMA_REF } from "./meta-schemas-
|
|
2
|
+
import { _ as brandingReadmeContent, a as DEFAULT_BUILTIN_SCHEMA_BASE, c as DEFAULT_SCHEMA_CONFIG_PATH, d as SETUP_PRESETS, f as SETUP_USE_CASES, g as getDefaultLoginFlow, h as getDefaultHumanUserSchema, i as DEFAULT_BRANDING_TEMPLATE_PATH, l as DEFAULT_SETUP_PRESET, m as getDefaultBrandingConfig, n as DEFAULT_BRANDING_CONFIG_PATH, o as DEFAULT_FLOW_CONFIG_PATH, p as defaultHumanUserSchemaUrl, r as DEFAULT_BRANDING_DESIGN, s as DEFAULT_FLOW_SCHEMA_URI, t as BRANDING_DESIGNS, u as DEFAULT_SETUP_USE_CASE, v as flowsReadmeContent, y as schemasReadmeContent } from "./defaults-BnRHT7RQ.mjs";
|
|
3
|
+
import { i as metaSchemaFiles, n as FLOW_FILE_SCHEMA_REF, r as META_SCHEMA_DIR, t as BRANDING_FILE_SCHEMA_REF } from "./meta-schemas-K8f7YZYz.mjs";
|
|
4
4
|
import { USER_PROPERTY_DEFAULTS, normalizeFlowBody, normalizeSchemaBody } from "./normalize.mjs";
|
|
5
5
|
import { a as localFileReferenceSchema, i as flowConfigSchema, n as brandingWireSchema, o as schemaConfigSchema, r as createFlowDefinitionRequestSchema, t as brandingConfigSchema } from "./schemas-7oNU25Qz.mjs";
|
|
6
6
|
import { BANNED_TEMPLATE_PATTERNS, MANDATORY_GATES_TAG, MAX_TEMPLATE_BYTES, TEMPLATE_TAGS, TEMPLATE_VALIDATION_RULES, validateLoginTemplate } from "./template.mjs";
|
|
@@ -46,7 +46,7 @@ var branding_default = {
|
|
|
46
46
|
"layout": {
|
|
47
47
|
"type": "string",
|
|
48
48
|
"enum": ["centered", "split"],
|
|
49
|
-
"description": "
|
|
49
|
+
"description": "Layout preset the revision declares, `centered` or `split`. It is\nvalidated on publish and handed to the template as `branding.layout`.\nThe bundled default template does not read it, so changing it alone\nchanges nothing on screen, and it is not an appearance control.\nNot a design catalog: the ejectable designs (`centered`, `minimal`)\nship as templates and both carry `centered`; switch designs with\n`zitadel branding eject --design <name>` instead of editing this field.\n`split` is carried by revisions published from the earlier split\ndesigns.\n",
|
|
50
50
|
"default": "centered"
|
|
51
51
|
},
|
|
52
52
|
"liquid_template": {
|
|
@@ -68,7 +68,7 @@ var branding_default = {
|
|
|
68
68
|
"type": "string",
|
|
69
69
|
"format": "uri",
|
|
70
70
|
"maxLength": 2048,
|
|
71
|
-
"description": "Hero
|
|
71
|
+
"description": "Hero image URL, for templates that reference `branding.hero_url`. The\nbundled default template does not use it.\n",
|
|
72
72
|
"pattern": "^([hH][tT][tT][pP][sS]://(?:\\[[0-9A-Fa-f:.]+\\]|[^/:?#@]+)(?::0*(?:[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?(?:[/?#]|$)|[hH][tT][tT][pP]://([lL][oO][cC][aA][lL][hH][oO][sS][tT]|127\\.(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\\.){2}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])|\\[::1\\])(?::0*(?:[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?([/?#]|$))"
|
|
73
73
|
},
|
|
74
74
|
"theme": { "$ref": "#/$defs/BrandingTheme" },
|
|
@@ -89,7 +89,7 @@ var branding_default = {
|
|
|
89
89
|
"BrandingTheme": {
|
|
90
90
|
"type": "object",
|
|
91
91
|
"additionalProperties": false,
|
|
92
|
-
"description": "The theme sides this project publishes, and which of them may run.\n\nPublishing one side makes it the only theme that resolves, even under `auto`\nor an operating-system preference for the other. Publishing both lets `auto`\nfollow the operating system, each side using its own logo and palette.\n\nThe element-level `theme` property overrides `mode
|
|
92
|
+
"description": "The theme sides this project publishes, and which of them may run.\n\nPublishing one side makes it the only theme that resolves, even under `auto`\nor an operating-system preference for the other. Publishing both lets `auto`\nfollow the operating system, each side using its own logo and palette.\n\nThe element-level `theme` property overrides `mode`. When one side is\npublished it cannot select the other. Publishing neither side leaves\n`mode`, the `theme` property and the operating system to pick one, painted\nwith the maintained defaults.\n",
|
|
93
93
|
"properties": {
|
|
94
94
|
"mode": {
|
|
95
95
|
"type": "string",
|
|
@@ -108,7 +108,7 @@ var branding_default = {
|
|
|
108
108
|
"BrandingThemeSide": {
|
|
109
109
|
"type": "object",
|
|
110
110
|
"additionalProperties": false,
|
|
111
|
-
"description": "One complete theme side. Light and dark are independent surfaces — neither\ninherits from the other
|
|
111
|
+
"description": "One complete theme side. Light and dark are independent surfaces — neither\ninherits from the other. When a revision publishes one side, the absent\nside is never used, whatever `mode` or the operating system asks for. When\nit publishes neither, either side can resolve and paints the maintained\ndefaults.\n",
|
|
112
112
|
"properties": {
|
|
113
113
|
"logo_url": {
|
|
114
114
|
"type": "string",
|
|
@@ -213,7 +213,7 @@ var branding_default = {
|
|
|
213
213
|
"type": "string",
|
|
214
214
|
"format": "uri",
|
|
215
215
|
"maxLength": 2048,
|
|
216
|
-
"description": "Stylesheet that loads `font_family` — a Google Fonts CSS URL, for\nexample. Must be https; unlike the logo and hero assets there is no\nloopback carve-out. Rejected without `font_family`, which would name\nnothing to paint with.\n\
|
|
216
|
+
"description": "Stylesheet that loads `font_family` — a Google Fonts CSS URL, for\nexample. Must be https; unlike the logo and hero assets there is no\nloopback carve-out. Rejected without `font_family`, which would name\nnothing to paint with.\n\nLoaded only when the login component runs as `variant=\"page\"`, the\nfull-page login. In the default `variant=\"widget\"` it is stored but not\nloaded: the component injects no stylesheet, and the embedding page\nloads the face.\n",
|
|
217
217
|
"pattern": "^[hH][tT][tT][pP][sS]://(?:\\[[0-9A-Fa-f:.]+\\]|[^/:?#@]+)(?::0*(?:[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?(?:[/?#]|$)",
|
|
218
218
|
"examples": ["https://fonts.googleapis.com/css2?family=Inter&display=swap"]
|
|
219
219
|
},
|
|
@@ -717,7 +717,7 @@ var idp_connection_default = {
|
|
|
717
717
|
"oidc": {
|
|
718
718
|
"type": "object",
|
|
719
719
|
"additionalProperties": false,
|
|
720
|
-
"description": "OIDC connection details. Endpoints come from discovery
|
|
720
|
+
"description": "OIDC connection details. Endpoints come from the issuer's discovery document. A provider that exposes no .well-known document names them all instead: authorization_endpoint, token_endpoint, jwks_uri, and userinfo_endpoint unless id_token_mapping is set. Setting some but not all is rejected.",
|
|
721
721
|
"required": [
|
|
722
722
|
"issuer",
|
|
723
723
|
"client_id",
|
|
@@ -729,13 +729,13 @@ var idp_connection_default = {
|
|
|
729
729
|
"type": "string",
|
|
730
730
|
"format": "uri",
|
|
731
731
|
"pattern": "^(https://[^\\s/?#]+|http://(localhost|127\\.0\\.0\\.1)(:[0-9]+)?)(/[^\\s?#]*)?$",
|
|
732
|
-
"description": "Discovery base. Endpoints are resolved from its .well-known document unless
|
|
732
|
+
"description": "Discovery base. Endpoints are resolved from its .well-known document unless the connection names them all."
|
|
733
733
|
},
|
|
734
734
|
"jwks_uri": {
|
|
735
735
|
"type": "string",
|
|
736
736
|
"format": "uri",
|
|
737
737
|
"pattern": "^(https://[^\\s/?#]+|http://(localhost|127\\.0\\.0\\.1)(:[0-9]+)?)([/?][^\\s#]*)?$",
|
|
738
|
-
"description": "
|
|
738
|
+
"description": "JWKS endpoint, for a provider that exposes no .well-known document. Set together with the other endpoints."
|
|
739
739
|
},
|
|
740
740
|
"id_token_mapping": {
|
|
741
741
|
"type": "boolean",
|
|
@@ -746,19 +746,19 @@ var idp_connection_default = {
|
|
|
746
746
|
"type": "string",
|
|
747
747
|
"format": "uri",
|
|
748
748
|
"pattern": "^(https://[^\\s/?#]+|http://(localhost|127\\.0\\.0\\.1)(:[0-9]+)?)([/?][^\\s#]*)?$",
|
|
749
|
-
"description": "
|
|
749
|
+
"description": "For a provider that exposes no .well-known document. Set together with the other endpoints."
|
|
750
750
|
},
|
|
751
751
|
"token_endpoint": {
|
|
752
752
|
"type": "string",
|
|
753
753
|
"format": "uri",
|
|
754
754
|
"pattern": "^(https://[^\\s/?#]+|http://(localhost|127\\.0\\.0\\.1)(:[0-9]+)?)([/?][^\\s#]*)?$",
|
|
755
|
-
"description": "
|
|
755
|
+
"description": "For a provider that exposes no .well-known document. Set together with the other endpoints."
|
|
756
756
|
},
|
|
757
757
|
"userinfo_endpoint": {
|
|
758
758
|
"type": "string",
|
|
759
759
|
"format": "uri",
|
|
760
760
|
"pattern": "^(https://[^\\s/?#]+|http://(localhost|127\\.0\\.0\\.1)(:[0-9]+)?)([/?][^\\s#]*)?$",
|
|
761
|
-
"description": "
|
|
761
|
+
"description": "For a provider that exposes no .well-known document. Set together with the other endpoints; not needed when id_token_mapping is set."
|
|
762
762
|
},
|
|
763
763
|
"client_id": {
|
|
764
764
|
"type": "string",
|
|
@@ -1170,4 +1170,4 @@ function metaSchemaFiles() {
|
|
|
1170
1170
|
//#endregion
|
|
1171
1171
|
export { metaSchemaFiles as i, FLOW_FILE_SCHEMA_REF as n, META_SCHEMA_DIR as r, BRANDING_FILE_SCHEMA_REF as t };
|
|
1172
1172
|
|
|
1173
|
-
//# sourceMappingURL=meta-schemas-
|
|
1173
|
+
//# sourceMappingURL=meta-schemas-K8f7YZYz.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta-schemas-
|
|
1
|
+
{"version":3,"file":"meta-schemas-K8f7YZYz.mjs","names":["flowDefinitionMetaSchema","userSchemaMetaSchema","userPropertyMetaSchema","propertyNameMetaSchema","authMethodsMetaSchema","authMethodMetaSchema","ssoAuthMethodMetaSchema","idpConnectionMetaSchema","brandingMetaSchema"],"sources":["../meta-schemas/auth-method.json","../meta-schemas/auth-methods.json","../meta-schemas/branding.json","../meta-schemas/flow-definition.json","../meta-schemas/idp-connection.json","../meta-schemas/property-name.json","../meta-schemas/sso-auth-method.json","../meta-schemas/user-property.json","../meta-schemas/user-schema.json","../src/meta-schemas.ts"],"sourcesContent":["","","","","","","","","","/**\n * The dialect meta-schemas `zitadel setup` copies into a project's\n * `.zitadel/meta/` so the flow/schema dialect is machine-readable offline:\n * editors validate flow files against `flow-definition.json` via a relative\n * `$schema` pointer, and agents read the dialect files before authoring\n * edits — no docs crawl, no hosted schema required.\n *\n * The files under `meta-schemas/` are generated: `scripts/sync-meta-schemas.mjs`\n * copies them from `api/openapi/endpoints/schemas/*.json` (the files the\n * server embeds) ahead of build, typecheck and test. Those are generated in\n * turn from the OpenAPI YAML by `scripts/generate-meta-schemas.ts` at the repo\n * root, so the YAML is the only thing to edit.\n */\nimport authMethodMetaSchema from \"../meta-schemas/auth-method.json\" with {\n type: \"json\",\n};\nimport authMethodsMetaSchema from \"../meta-schemas/auth-methods.json\" with {\n type: \"json\",\n};\nimport brandingMetaSchema from \"../meta-schemas/branding.json\" with {\n type: \"json\",\n};\nimport flowDefinitionMetaSchema from \"../meta-schemas/flow-definition.json\" with {\n type: \"json\",\n};\nimport idpConnectionMetaSchema from \"../meta-schemas/idp-connection.json\" with {\n type: \"json\",\n};\nimport propertyNameMetaSchema from \"../meta-schemas/property-name.json\" with {\n type: \"json\",\n};\nimport ssoAuthMethodMetaSchema from \"../meta-schemas/sso-auth-method.json\" with {\n type: \"json\",\n};\nimport userPropertyMetaSchema from \"../meta-schemas/user-property.json\" with {\n type: \"json\",\n};\nimport userSchemaMetaSchema from \"../meta-schemas/user-schema.json\" with {\n type: \"json\",\n};\n\n/** Project-relative directory setup writes the meta-schemas to. */\nexport const META_SCHEMA_DIR = \".zitadel/meta\";\n\n/**\n * The `$schema` value scaffolded flow files carry, relative to\n * `.zitadel/flows/` — resolves to `{@link META_SCHEMA_DIR}/flow-definition.json`.\n */\nexport const FLOW_FILE_SCHEMA_REF = \"../meta/flow-definition.json\";\n\n/**\n * The `$schema` value scaffolded branding files carry, relative to\n * `.zitadel/branding/` — resolves to `{@link META_SCHEMA_DIR}/branding.json`.\n */\nexport const BRANDING_FILE_SCHEMA_REF = \"../meta/branding.json\";\n\nexport type MetaSchemaFile = { name: string; body: object };\n\n/**\n * The dialect files to materialize, in write order. `auth-methods.json`,\n * `auth-method.json`, and `sso-auth-method.json` are pulled in by\n * `user-schema.json`'s relative `$ref`s — without them the copied dialect\n * cannot resolve offline.\n */\nexport function metaSchemaFiles(): ReadonlyArray<MetaSchemaFile> {\n return [\n { name: \"flow-definition.json\", body: flowDefinitionMetaSchema as object },\n { name: \"user-schema.json\", body: userSchemaMetaSchema as object },\n { name: \"user-property.json\", body: userPropertyMetaSchema as object },\n { name: \"property-name.json\", body: propertyNameMetaSchema as object },\n { name: \"auth-methods.json\", body: authMethodsMetaSchema as object },\n { name: \"auth-method.json\", body: authMethodMetaSchema as object },\n { name: \"sso-auth-method.json\", body: ssoAuthMethodMetaSchema as object },\n { name: \"idp-connection.json\", body: idpConnectionMetaSchema as object },\n { name: \"branding.json\", body: brandingMetaSchema as object },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AS0CA,MAAa,kBAAkB;;;;;AAM/B,MAAa,uBAAuB;;;;;AAMpC,MAAa,2BAA2B;;;;;;;AAUxC,SAAgB,kBAAiD;AAC/D,QAAO;EACL;GAAE,MAAM;GAAwB,MAAMA;GAAoC;EAC1E;GAAE,MAAM;GAAoB,MAAMC;GAAgC;EAClE;GAAE,MAAM;GAAsB,MAAMC;GAAkC;EACtE;GAAE,MAAM;GAAsB,MAAMC;GAAkC;EACtE;GAAE,MAAM;GAAqB,MAAMC;GAAiC;EACpE;GAAE,MAAM;GAAoB,MAAMC;GAAgC;EAClE;GAAE,MAAM;GAAwB,MAAMC;GAAmC;EACzE;GAAE,MAAM;GAAuB,MAAMC;GAAmC;EACxE;GAAE,MAAM;GAAiB,MAAMC;GAA8B;EAC9D"}
|
package/dist/meta-schemas.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as metaSchemaFiles, n as FLOW_FILE_SCHEMA_REF, r as META_SCHEMA_DIR, t as BRANDING_FILE_SCHEMA_REF } from "./meta-schemas-
|
|
1
|
+
import { i as metaSchemaFiles, n as FLOW_FILE_SCHEMA_REF, r as META_SCHEMA_DIR, t as BRANDING_FILE_SCHEMA_REF } from "./meta-schemas-K8f7YZYz.mjs";
|
|
2
2
|
export { BRANDING_FILE_SCHEMA_REF, FLOW_FILE_SCHEMA_REF, META_SCHEMA_DIR, metaSchemaFiles };
|
package/dist/template.mjs
CHANGED
|
@@ -126,7 +126,7 @@ function validateLoginTemplate(template) {
|
|
|
126
126
|
if (!MANDATORY_GATES_PATTERN.test(template)) issues.push({
|
|
127
127
|
severity: "error",
|
|
128
128
|
rule: "mandatory-gates",
|
|
129
|
-
message: "Template must contain a trailing {% mandatory_gates %} tag — the runtime safety net that appends required
|
|
129
|
+
message: "Template must contain a trailing {% mandatory_gates %} tag — the runtime safety net that appends any required field and the primary action a template left out."
|
|
130
130
|
});
|
|
131
131
|
try {
|
|
132
132
|
getParseEngine().parse(template);
|
package/dist/template.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.mjs","names":[],"sources":["../src/template.ts"],"sourcesContent":["/**\n * The authoritative login-template validator (ADR 040).\n *\n * Runs where authoring happens — `zitadel plan`/`apply`, and later editors —\n * against the same LiquidJS dialect `@zitadel/components` renders. The Go\n * server cannot parse this dialect faithfully, so it only mirrors the\n * lexical subset below (`internal/domain/branding_validator.go`, see the\n * `goRef` entries in {@link TEMPLATE_VALIDATION_RULES}); keep the two\n * pattern lists in sync.\n *\n * Render-time safety does not depend on this validator: the component\n * escapes all output, neuters `| raw`, sanitises via DOMPurify, and patches\n * missing required UI with `{% mandatory_gates %}`\n * (`docs/design/flowengine/template-security.md`).\n */\nimport { Liquid } from \"liquidjs\";\n\n/** The `{% mandatory_gates %}` runtime safety-net tag every template must emit. */\nexport const MANDATORY_GATES_TAG = \"mandatory_gates\";\n\n/**\n * Custom Liquid tags the login orchestrator registers beyond the standard\n * set. Validators must register them (as no-ops) or parsing valid templates\n * fails; `@zitadel/components` registers the real implementations.\n */\nexport const TEMPLATE_TAGS = [MANDATORY_GATES_TAG] as const;\n\n/**\n * Byte cap for stored templates. Mirrors `maxLength` on\n * `branding.liquid_template` in the OpenAPI contract and\n * `MaxBrandingTemplateBytes` in the Go gate.\n */\nexport const MAX_TEMPLATE_BYTES = 131072;\n\nexport type TemplateValidationSeverity = \"error\" | \"warning\";\n\n/**\n * Rule catalog with pointers into the Go lexical gate\n * (`internal/domain/branding_validator.go`) where a server-side mirror\n * exists. `parse` and `mandatory-gates` are authoring-only: the server\n * cannot check them (wrong dialect) and render-time behavior degrades\n * safely without them.\n */\nexport const TEMPLATE_VALIDATION_RULES = {\n parse: { goRef: null },\n size: { goRef: \"MaxBrandingTemplateBytes\" },\n \"no-script-tag\": { goRef: \"brandingScriptTag\" },\n \"no-style-tag\": { goRef: \"brandingStyleTag\" },\n \"no-inline-handlers\": { goRef: \"brandingInlineHandler\" },\n \"no-javascript-urls\": { goRef: \"brandingJavascriptURL\" },\n \"no-raw-filter\": { goRef: \"brandingRawFilter\" },\n \"mandatory-gates\": { goRef: null },\n} as const;\n\nexport type TemplateValidationRuleId = keyof typeof TEMPLATE_VALIDATION_RULES;\n\nexport type TemplateValidationIssue = {\n severity: TemplateValidationSeverity;\n rule: TemplateValidationRuleId;\n message: string;\n};\n\ntype BannedPattern = {\n rule: TemplateValidationRuleId;\n pattern: RegExp;\n message: string;\n};\n\n/**\n * Lexical bans, identical to the Go gate. The inline-handler pattern only\n * matches inside an open tag so Liquid like `{% assign online = true %}`\n * doesn't false-positive; its separator class includes `/` because HTML\n * treats a slash between attributes as whitespace (`<img/onerror=…>`).\n */\nexport const BANNED_TEMPLATE_PATTERNS: readonly BannedPattern[] = [\n {\n rule: \"no-script-tag\",\n pattern: /<\\s*script\\b/i,\n message: \"Templates must not contain <script> tags.\",\n },\n {\n rule: \"no-style-tag\",\n pattern: /<\\s*style\\b/i,\n message: \"Templates must not contain <style> tags — theming is orchestrator-owned via tokens.\",\n },\n {\n rule: \"no-inline-handlers\",\n pattern: /<[a-z][^>]*[\\s/]on[a-z]+\\s*=/i,\n message: \"Templates must not contain inline event handlers (on*= attributes).\",\n },\n {\n rule: \"no-javascript-urls\",\n pattern: /javascript\\s*:/i,\n message: \"Templates must not contain javascript: URLs.\",\n },\n {\n rule: \"no-raw-filter\",\n pattern: /\\|\\s*raw\\b/,\n message:\n \"Templates must not use the | raw filter. The renderer neuters it anyway; remove it from the template.\",\n },\n] as const;\n\n// Derived from the exported tag name so a rename cannot silently diverge\n// the validator from the renderer contract.\nconst MANDATORY_GATES_PATTERN = new RegExp(`\\\\{%-?\\\\s*${MANDATORY_GATES_TAG}\\\\s*-?%\\\\}`);\n\nlet parseEngine: Liquid | undefined;\n\n/**\n * A parse-only engine mirroring the orchestrator's dialect options\n * (`packages/components/src/orchestrator/liquid.ts`); custom tags are\n * registered as no-ops because only parseability matters here.\n */\nfunction getParseEngine(): Liquid {\n if (!parseEngine) {\n const engine = new Liquid({\n cache: false,\n jsTruthy: true,\n relativeReference: false,\n strictFilters: false,\n strictVariables: false,\n });\n for (const tag of TEMPLATE_TAGS) {\n engine.registerTag(tag, {\n parse() {\n // no-op: the tag takes no arguments and no body\n },\n render() {\n return \"\";\n },\n });\n }\n parseEngine = engine;\n }\n return parseEngine;\n}\n\n/**\n * Validates a login template. Pure and non-throwing, like\n * `validateFlowDefinition`: returns an issue list, empty when the template\n * is valid.\n */\nexport function validateLoginTemplate(template: string): TemplateValidationIssue[] {\n const issues: TemplateValidationIssue[] = [];\n\n const bytes = new TextEncoder().encode(template).length;\n if (bytes > MAX_TEMPLATE_BYTES) {\n issues.push({\n severity: \"error\",\n rule: \"size\",\n message: `Template is ${bytes} bytes; the platform caps liquid_template at ${MAX_TEMPLATE_BYTES} bytes.`,\n });\n }\n\n for (const banned of BANNED_TEMPLATE_PATTERNS) {\n if (banned.pattern.test(template)) {\n issues.push({ severity: \"error\", rule: banned.rule, message: banned.message });\n }\n }\n\n if (!MANDATORY_GATES_PATTERN.test(template)) {\n issues.push({\n severity: \"error\",\n rule: \"mandatory-gates\",\n message:\n \"Template must contain a trailing {% mandatory_gates %} tag — the runtime safety net that appends required
|
|
1
|
+
{"version":3,"file":"template.mjs","names":[],"sources":["../src/template.ts"],"sourcesContent":["/**\n * The authoritative login-template validator (ADR 040).\n *\n * Runs where authoring happens — `zitadel plan`/`apply`, and later editors —\n * against the same LiquidJS dialect `@zitadel/components` renders. The Go\n * server cannot parse this dialect faithfully, so it only mirrors the\n * lexical subset below (`internal/domain/branding_validator.go`, see the\n * `goRef` entries in {@link TEMPLATE_VALIDATION_RULES}); keep the two\n * pattern lists in sync.\n *\n * Render-time safety does not depend on this validator: the component\n * escapes all output, neuters `| raw`, sanitises via DOMPurify, and patches\n * missing required UI with `{% mandatory_gates %}`\n * (`docs/design/flowengine/template-security.md`).\n */\nimport { Liquid } from \"liquidjs\";\n\n/** The `{% mandatory_gates %}` runtime safety-net tag every template must emit. */\nexport const MANDATORY_GATES_TAG = \"mandatory_gates\";\n\n/**\n * Custom Liquid tags the login orchestrator registers beyond the standard\n * set. Validators must register them (as no-ops) or parsing valid templates\n * fails; `@zitadel/components` registers the real implementations.\n */\nexport const TEMPLATE_TAGS = [MANDATORY_GATES_TAG] as const;\n\n/**\n * Byte cap for stored templates. Mirrors `maxLength` on\n * `branding.liquid_template` in the OpenAPI contract and\n * `MaxBrandingTemplateBytes` in the Go gate.\n */\nexport const MAX_TEMPLATE_BYTES = 131072;\n\nexport type TemplateValidationSeverity = \"error\" | \"warning\";\n\n/**\n * Rule catalog with pointers into the Go lexical gate\n * (`internal/domain/branding_validator.go`) where a server-side mirror\n * exists. `parse` and `mandatory-gates` are authoring-only: the server\n * cannot check them (wrong dialect) and render-time behavior degrades\n * safely without them.\n */\nexport const TEMPLATE_VALIDATION_RULES = {\n parse: { goRef: null },\n size: { goRef: \"MaxBrandingTemplateBytes\" },\n \"no-script-tag\": { goRef: \"brandingScriptTag\" },\n \"no-style-tag\": { goRef: \"brandingStyleTag\" },\n \"no-inline-handlers\": { goRef: \"brandingInlineHandler\" },\n \"no-javascript-urls\": { goRef: \"brandingJavascriptURL\" },\n \"no-raw-filter\": { goRef: \"brandingRawFilter\" },\n \"mandatory-gates\": { goRef: null },\n} as const;\n\nexport type TemplateValidationRuleId = keyof typeof TEMPLATE_VALIDATION_RULES;\n\nexport type TemplateValidationIssue = {\n severity: TemplateValidationSeverity;\n rule: TemplateValidationRuleId;\n message: string;\n};\n\ntype BannedPattern = {\n rule: TemplateValidationRuleId;\n pattern: RegExp;\n message: string;\n};\n\n/**\n * Lexical bans, identical to the Go gate. The inline-handler pattern only\n * matches inside an open tag so Liquid like `{% assign online = true %}`\n * doesn't false-positive; its separator class includes `/` because HTML\n * treats a slash between attributes as whitespace (`<img/onerror=…>`).\n */\nexport const BANNED_TEMPLATE_PATTERNS: readonly BannedPattern[] = [\n {\n rule: \"no-script-tag\",\n pattern: /<\\s*script\\b/i,\n message: \"Templates must not contain <script> tags.\",\n },\n {\n rule: \"no-style-tag\",\n pattern: /<\\s*style\\b/i,\n message: \"Templates must not contain <style> tags — theming is orchestrator-owned via tokens.\",\n },\n {\n rule: \"no-inline-handlers\",\n pattern: /<[a-z][^>]*[\\s/]on[a-z]+\\s*=/i,\n message: \"Templates must not contain inline event handlers (on*= attributes).\",\n },\n {\n rule: \"no-javascript-urls\",\n pattern: /javascript\\s*:/i,\n message: \"Templates must not contain javascript: URLs.\",\n },\n {\n rule: \"no-raw-filter\",\n pattern: /\\|\\s*raw\\b/,\n message:\n \"Templates must not use the | raw filter. The renderer neuters it anyway; remove it from the template.\",\n },\n] as const;\n\n// Derived from the exported tag name so a rename cannot silently diverge\n// the validator from the renderer contract.\nconst MANDATORY_GATES_PATTERN = new RegExp(`\\\\{%-?\\\\s*${MANDATORY_GATES_TAG}\\\\s*-?%\\\\}`);\n\nlet parseEngine: Liquid | undefined;\n\n/**\n * A parse-only engine mirroring the orchestrator's dialect options\n * (`packages/components/src/orchestrator/liquid.ts`); custom tags are\n * registered as no-ops because only parseability matters here.\n */\nfunction getParseEngine(): Liquid {\n if (!parseEngine) {\n const engine = new Liquid({\n cache: false,\n jsTruthy: true,\n relativeReference: false,\n strictFilters: false,\n strictVariables: false,\n });\n for (const tag of TEMPLATE_TAGS) {\n engine.registerTag(tag, {\n parse() {\n // no-op: the tag takes no arguments and no body\n },\n render() {\n return \"\";\n },\n });\n }\n parseEngine = engine;\n }\n return parseEngine;\n}\n\n/**\n * Validates a login template. Pure and non-throwing, like\n * `validateFlowDefinition`: returns an issue list, empty when the template\n * is valid.\n */\nexport function validateLoginTemplate(template: string): TemplateValidationIssue[] {\n const issues: TemplateValidationIssue[] = [];\n\n const bytes = new TextEncoder().encode(template).length;\n if (bytes > MAX_TEMPLATE_BYTES) {\n issues.push({\n severity: \"error\",\n rule: \"size\",\n message: `Template is ${bytes} bytes; the platform caps liquid_template at ${MAX_TEMPLATE_BYTES} bytes.`,\n });\n }\n\n for (const banned of BANNED_TEMPLATE_PATTERNS) {\n if (banned.pattern.test(template)) {\n issues.push({ severity: \"error\", rule: banned.rule, message: banned.message });\n }\n }\n\n if (!MANDATORY_GATES_PATTERN.test(template)) {\n issues.push({\n severity: \"error\",\n rule: \"mandatory-gates\",\n message:\n \"Template must contain a trailing {% mandatory_gates %} tag — the runtime safety net that appends any required field and the primary action a template left out.\",\n });\n }\n\n try {\n getParseEngine().parse(template);\n } catch (error) {\n issues.push({\n severity: \"error\",\n rule: \"parse\",\n message: `Template is not valid LiquidJS: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n return issues;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAa,sBAAsB;;;;;;AAOnC,MAAa,gBAAgB,CAAC,oBAAoB;;;;;;AAOlD,MAAa,qBAAqB;;;;;;;;AAWlC,MAAa,4BAA4B;CACvC,OAAO,EAAE,OAAO,MAAM;CACtB,MAAM,EAAE,OAAO,4BAA4B;CAC3C,iBAAiB,EAAE,OAAO,qBAAqB;CAC/C,gBAAgB,EAAE,OAAO,oBAAoB;CAC7C,sBAAsB,EAAE,OAAO,yBAAyB;CACxD,sBAAsB,EAAE,OAAO,yBAAyB;CACxD,iBAAiB,EAAE,OAAO,qBAAqB;CAC/C,mBAAmB,EAAE,OAAO,MAAM;CACnC;;;;;;;AAsBD,MAAa,2BAAqD;CAChE;EACE,MAAM;EACN,SAAS;EACT,SAAS;EACV;CACD;EACE,MAAM;EACN,SAAS;EACT,SAAS;EACV;CACD;EACE,MAAM;EACN,SAAS;EACT,SAAS;EACV;CACD;EACE,MAAM;EACN,SAAS;EACT,SAAS;EACV;CACD;EACE,MAAM;EACN,SAAS;EACT,SACE;EACH;CACF;AAID,MAAM,0BAA0B,IAAI,OAAO,aAAa,oBAAoB,YAAY;AAExF,IAAI;;;;;;AAOJ,SAAS,iBAAyB;AAChC,KAAI,CAAC,aAAa;EAChB,MAAM,SAAS,IAAI,OAAO;GACxB,OAAO;GACP,UAAU;GACV,mBAAmB;GACnB,eAAe;GACf,iBAAiB;GAClB,CAAC;AACF,OAAK,MAAM,OAAO,cAChB,QAAO,YAAY,KAAK;GACtB,QAAQ;GAGR,SAAS;AACP,WAAO;;GAEV,CAAC;AAEJ,gBAAc;;AAEhB,QAAO;;;;;;;AAQT,SAAgB,sBAAsB,UAA6C;CACjF,MAAM,SAAoC,EAAE;CAE5C,MAAM,QAAQ,IAAI,aAAa,CAAC,OAAO,SAAS,CAAC;AACjD,KAAI,QAAA,OACF,QAAO,KAAK;EACV,UAAU;EACV,MAAM;EACN,SAAS,eAAe,MAAM,+CAA+C,mBAAmB;EACjG,CAAC;AAGJ,MAAK,MAAM,UAAU,yBACnB,KAAI,OAAO,QAAQ,KAAK,SAAS,CAC/B,QAAO,KAAK;EAAE,UAAU;EAAS,MAAM,OAAO;EAAM,SAAS,OAAO;EAAS,CAAC;AAIlF,KAAI,CAAC,wBAAwB,KAAK,SAAS,CACzC,QAAO,KAAK;EACV,UAAU;EACV,MAAM;EACN,SACE;EACH,CAAC;AAGJ,KAAI;AACF,kBAAgB,CAAC,MAAM,SAAS;UACzB,OAAO;AACd,SAAO,KAAK;GACV,UAAU;GACV,MAAM;GACN,SAAS,mCAAmC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GACnG,CAAC;;AAGJ,QAAO"}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"centered",
|
|
17
17
|
"split"
|
|
18
18
|
],
|
|
19
|
-
"description": "
|
|
19
|
+
"description": "Layout preset the revision declares, `centered` or `split`. It is\nvalidated on publish and handed to the template as `branding.layout`.\nThe bundled default template does not read it, so changing it alone\nchanges nothing on screen, and it is not an appearance control.\nNot a design catalog: the ejectable designs (`centered`, `minimal`)\nship as templates and both carry `centered`; switch designs with\n`zitadel branding eject --design <name>` instead of editing this field.\n`split` is carried by revisions published from the earlier split\ndesigns.\n",
|
|
20
20
|
"default": "centered"
|
|
21
21
|
},
|
|
22
22
|
"liquid_template": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"type": "string",
|
|
44
44
|
"format": "uri",
|
|
45
45
|
"maxLength": 2048,
|
|
46
|
-
"description": "Hero
|
|
46
|
+
"description": "Hero image URL, for templates that reference `branding.hero_url`. The\nbundled default template does not use it.\n",
|
|
47
47
|
"pattern": "^([hH][tT][tT][pP][sS]://(?:\\[[0-9A-Fa-f:.]+\\]|[^/:?#@]+)(?::0*(?:[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?(?:[/?#]|$)|[hH][tT][tT][pP]://([lL][oO][cC][aA][lL][hH][oO][sS][tT]|127\\.(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\\.){2}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])|\\[::1\\])(?::0*(?:[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?([/?#]|$))"
|
|
48
48
|
},
|
|
49
49
|
"theme": {
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"BrandingTheme": {
|
|
75
75
|
"type": "object",
|
|
76
76
|
"additionalProperties": false,
|
|
77
|
-
"description": "The theme sides this project publishes, and which of them may run.\n\nPublishing one side makes it the only theme that resolves, even under `auto`\nor an operating-system preference for the other. Publishing both lets `auto`\nfollow the operating system, each side using its own logo and palette.\n\nThe element-level `theme` property overrides `mode
|
|
77
|
+
"description": "The theme sides this project publishes, and which of them may run.\n\nPublishing one side makes it the only theme that resolves, even under `auto`\nor an operating-system preference for the other. Publishing both lets `auto`\nfollow the operating system, each side using its own logo and palette.\n\nThe element-level `theme` property overrides `mode`. When one side is\npublished it cannot select the other. Publishing neither side leaves\n`mode`, the `theme` property and the operating system to pick one, painted\nwith the maintained defaults.\n",
|
|
78
78
|
"properties": {
|
|
79
79
|
"mode": {
|
|
80
80
|
"type": "string",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"BrandingThemeSide": {
|
|
100
100
|
"type": "object",
|
|
101
101
|
"additionalProperties": false,
|
|
102
|
-
"description": "One complete theme side. Light and dark are independent surfaces — neither\ninherits from the other
|
|
102
|
+
"description": "One complete theme side. Light and dark are independent surfaces — neither\ninherits from the other. When a revision publishes one side, the absent\nside is never used, whatever `mode` or the operating system asks for. When\nit publishes neither, either side can resolve and paints the maintained\ndefaults.\n",
|
|
103
103
|
"properties": {
|
|
104
104
|
"logo_url": {
|
|
105
105
|
"type": "string",
|
|
@@ -214,7 +214,7 @@
|
|
|
214
214
|
"type": "string",
|
|
215
215
|
"format": "uri",
|
|
216
216
|
"maxLength": 2048,
|
|
217
|
-
"description": "Stylesheet that loads `font_family` — a Google Fonts CSS URL, for\nexample. Must be https; unlike the logo and hero assets there is no\nloopback carve-out. Rejected without `font_family`, which would name\nnothing to paint with.\n\
|
|
217
|
+
"description": "Stylesheet that loads `font_family` — a Google Fonts CSS URL, for\nexample. Must be https; unlike the logo and hero assets there is no\nloopback carve-out. Rejected without `font_family`, which would name\nnothing to paint with.\n\nLoaded only when the login component runs as `variant=\"page\"`, the\nfull-page login. In the default `variant=\"widget\"` it is stored but not\nloaded: the component injects no stylesheet, and the embedding page\nloads the face.\n",
|
|
218
218
|
"pattern": "^[hH][tT][tT][pP][sS]://(?:\\[[0-9A-Fa-f:.]+\\]|[^/:?#@]+)(?::0*(?:[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?(?:[/?#]|$)",
|
|
219
219
|
"examples": [
|
|
220
220
|
"https://fonts.googleapis.com/css2?family=Inter&display=swap"
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"oidc": {
|
|
110
110
|
"type": "object",
|
|
111
111
|
"additionalProperties": false,
|
|
112
|
-
"description": "OIDC connection details. Endpoints come from discovery
|
|
112
|
+
"description": "OIDC connection details. Endpoints come from the issuer's discovery document. A provider that exposes no .well-known document names them all instead: authorization_endpoint, token_endpoint, jwks_uri, and userinfo_endpoint unless id_token_mapping is set. Setting some but not all is rejected.",
|
|
113
113
|
"required": [
|
|
114
114
|
"issuer",
|
|
115
115
|
"client_id",
|
|
@@ -121,13 +121,13 @@
|
|
|
121
121
|
"type": "string",
|
|
122
122
|
"format": "uri",
|
|
123
123
|
"pattern": "^(https://[^\\s/?#]+|http://(localhost|127\\.0\\.0\\.1)(:[0-9]+)?)(/[^\\s?#]*)?$",
|
|
124
|
-
"description": "Discovery base. Endpoints are resolved from its .well-known document unless
|
|
124
|
+
"description": "Discovery base. Endpoints are resolved from its .well-known document unless the connection names them all."
|
|
125
125
|
},
|
|
126
126
|
"jwks_uri": {
|
|
127
127
|
"type": "string",
|
|
128
128
|
"format": "uri",
|
|
129
129
|
"pattern": "^(https://[^\\s/?#]+|http://(localhost|127\\.0\\.0\\.1)(:[0-9]+)?)([/?][^\\s#]*)?$",
|
|
130
|
-
"description": "
|
|
130
|
+
"description": "JWKS endpoint, for a provider that exposes no .well-known document. Set together with the other endpoints."
|
|
131
131
|
},
|
|
132
132
|
"id_token_mapping": {
|
|
133
133
|
"type": "boolean",
|
|
@@ -138,19 +138,19 @@
|
|
|
138
138
|
"type": "string",
|
|
139
139
|
"format": "uri",
|
|
140
140
|
"pattern": "^(https://[^\\s/?#]+|http://(localhost|127\\.0\\.0\\.1)(:[0-9]+)?)([/?][^\\s#]*)?$",
|
|
141
|
-
"description": "
|
|
141
|
+
"description": "For a provider that exposes no .well-known document. Set together with the other endpoints."
|
|
142
142
|
},
|
|
143
143
|
"token_endpoint": {
|
|
144
144
|
"type": "string",
|
|
145
145
|
"format": "uri",
|
|
146
146
|
"pattern": "^(https://[^\\s/?#]+|http://(localhost|127\\.0\\.0\\.1)(:[0-9]+)?)([/?][^\\s#]*)?$",
|
|
147
|
-
"description": "
|
|
147
|
+
"description": "For a provider that exposes no .well-known document. Set together with the other endpoints."
|
|
148
148
|
},
|
|
149
149
|
"userinfo_endpoint": {
|
|
150
150
|
"type": "string",
|
|
151
151
|
"format": "uri",
|
|
152
152
|
"pattern": "^(https://[^\\s/?#]+|http://(localhost|127\\.0\\.0\\.1)(:[0-9]+)?)([/?][^\\s#]*)?$",
|
|
153
|
-
"description": "
|
|
153
|
+
"description": "For a provider that exposes no .well-known document. Set together with the other endpoints; not needed when id_token_mapping is set."
|
|
154
154
|
},
|
|
155
155
|
"client_id": {
|
|
156
156
|
"type": "string",
|