@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.
@@ -1,12 +1,14 @@
1
1
  # Branding
2
2
 
3
- This directory owns how your login UI looks: `branding.json` (layout preset
4
- and asset URLs) plus `login.liquid`, the LiquidJS template the
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/hero URLs).
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, gate, or submit action your template forgot, so a broken template
26
- still yields a submittable step.
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` 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.
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": "split",
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 each asset URL and warns
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 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`.
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
- - In the split-family designs (`split`, `split-right`, `hero`) the
65
- `.zl-split__brand` pane is yours: structural HTML plus inline `style=""`
66
- attributes are allowed; `button`, `input`, and `form` tags are stripped —
67
- use `<a>` for landing CTAs. The `hero` design ships a full landing-page
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`, `split`, `split-right`, `hero`, `minimal`; add `--force` to
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
- * Login-design starting points `zitadel branding eject --design <name>` (and
352
- * `zitadel setup --design <name>`) scaffold into `.zitadel/branding/`. A
353
- * design is a full Liquid template plus the descriptor `layout` it degrades
354
- * to — the wire `layout` enum stays `centered | split`, richer designs are
355
- * delivered as templates (ADR 040).
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
- /** Descriptor `layout` each design degrades to when its template is rejected. */
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
- * the other designs are authored variants of it.
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-DV6hY2KT.mjs.map
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
- * Login-design starting points `zitadel branding eject --design <name>` (and
41
- * `zitadel setup --design <name>`) scaffold into `.zitadel/branding/`. A
42
- * design is a full Liquid template plus the descriptor `layout` it degrades
43
- * to — the wire `layout` enum stays `centered | split`, richer designs are
44
- * delivered as templates (ADR 040).
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", "split", "split-right", "hero", "minimal"];
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" | "split"; /** A `$file` reference to the sibling template; the CLI inlines it before publishing. */
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
- * the other designs are authored variants of it.
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-BxGCGdqS.d.mts.map
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"}
@@ -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-BxGCGdqS.mjs";
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-DV6hY2KT.mjs";
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-BxGCGdqS.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-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-DV6hY2KT.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-OowgSUfU.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-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": "Degrade preset (`centered` or `split`) the bundled default template\nbranches on, and the fallback when a custom template fails validation.\nNot the complete design catalog: all named designs (`centered`, `split`,\n`split-right`, `hero`, `minimal`) ship as templates and map onto one of\nthese two values; switch designs with\n`zitadel branding eject --design <name>` instead of editing this field.\nEjected templates may ignore it. Selects a template rather than restyling\nthe widget, so it is not an appearance control.\n",
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/background image URL, used by the split layout.",
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`, but cannot select a\nside the project never published.\n",
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, and a side that is absent is never used, whatever\n`mode` or the operating system asks for.\n",
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\nStored but not applied to an embedded widget: the embedding page loads\nthe face, and Zitadel does not inject a stylesheet into a document it\ndoes not own.\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, but any may be supplied to override it, or to serve a provider that exposes no .well-known document.",
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 overridden below."
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": "Overrides the JWKS endpoint from discovery. Required in practice when the provider exposes no .well-known document, since ID tokens cannot otherwise be validated."
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": "Overrides discovery."
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": "Overrides discovery."
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": "Overrides discovery."
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-OowgSUfU.mjs.map
1173
+ //# sourceMappingURL=meta-schemas-K8f7YZYz.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"meta-schemas-OowgSUfU.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"}
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"}
@@ -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-OowgSUfU.mjs";
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 fields, gates, and the submit action a template forgot to render."
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);
@@ -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 fields, gates, and the submit action a template forgot to render.\",\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"}
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": "Degrade preset (`centered` or `split`) the bundled default template\nbranches on, and the fallback when a custom template fails validation.\nNot the complete design catalog: all named designs (`centered`, `split`,\n`split-right`, `hero`, `minimal`) ship as templates and map onto one of\nthese two values; switch designs with\n`zitadel branding eject --design <name>` instead of editing this field.\nEjected templates may ignore it. Selects a template rather than restyling\nthe widget, so it is not an appearance control.\n",
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/background image URL, used by the split layout.",
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`, but cannot select a\nside the project never published.\n",
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, and a side that is absent is never used, whatever\n`mode` or the operating system asks for.\n",
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\nStored but not applied to an embedded widget: the embedding page loads\nthe face, and Zitadel does not inject a stylesheet into a document it\ndoes not own.\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, but any may be supplied to override it, or to serve a provider that exposes no .well-known document.",
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 overridden below."
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": "Overrides the JWKS endpoint from discovery. Required in practice when the provider exposes no .well-known document, since ID tokens cannot otherwise be validated."
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": "Overrides discovery."
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": "Overrides discovery."
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": "Overrides discovery."
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",