@zitadel/config 0.1.0-alpha.17 → 0.1.0-alpha.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +27 -0
  2. package/defaults/README-branding.md +96 -0
  3. package/defaults/README-flows.md +21 -4
  4. package/defaults/README-schemas.md +2 -2
  5. package/defaults/branding/centered/login.liquid +164 -0
  6. package/defaults/branding/hero/login.liquid +224 -0
  7. package/defaults/branding/minimal/login.liquid +154 -0
  8. package/defaults/branding/split/login.liquid +183 -0
  9. package/defaults/branding/split-right/login.liquid +183 -0
  10. package/defaults/default-human-user.json +2 -19
  11. package/defaults/default-login.json +21 -4
  12. package/defaults/presets/passkey-first/human-user.json +2 -19
  13. package/defaults/presets/passkey-first/login.json +31 -4
  14. package/dist/branding-url.d.mts +14 -0
  15. package/dist/branding-url.d.mts.map +1 -0
  16. package/dist/branding-url.mjs +23 -0
  17. package/dist/branding-url.mjs.map +1 -0
  18. package/dist/defaults-Bh4_xVbh.d.mts +84 -0
  19. package/dist/defaults-Bh4_xVbh.d.mts.map +1 -0
  20. package/dist/defaults-eiGHyI5Y.mjs +569 -0
  21. package/dist/defaults-eiGHyI5Y.mjs.map +1 -0
  22. package/dist/defaults.d.mts +2 -2
  23. package/dist/defaults.mjs +2 -2
  24. package/dist/index.d.mts +6 -4
  25. package/dist/index.mjs +6 -4
  26. package/dist/{meta-schemas-8nlyVL7g.mjs → meta-schemas-CSc18JqO.mjs} +92 -31
  27. package/dist/meta-schemas-CSc18JqO.mjs.map +1 -0
  28. package/dist/meta-schemas.d.mts +6 -1
  29. package/dist/meta-schemas.d.mts.map +1 -1
  30. package/dist/meta-schemas.mjs +2 -2
  31. package/dist/normalize.d.mts +11 -9
  32. package/dist/normalize.d.mts.map +1 -1
  33. package/dist/normalize.mjs +24 -17
  34. package/dist/normalize.mjs.map +1 -1
  35. package/dist/schemas.d.mts +14 -1
  36. package/dist/schemas.d.mts.map +1 -1
  37. package/dist/schemas.mjs +62 -2
  38. package/dist/schemas.mjs.map +1 -1
  39. package/dist/template.d.mts +76 -0
  40. package/dist/template.d.mts.map +1 -0
  41. package/dist/template.mjs +145 -0
  42. package/dist/template.mjs.map +1 -0
  43. package/dist/validate.mjs +70 -11
  44. package/dist/validate.mjs.map +1 -1
  45. package/meta-schemas/auth-method.json +2 -7
  46. package/meta-schemas/branding.json +44 -0
  47. package/meta-schemas/flow-definition.json +20 -1
  48. package/meta-schemas/property-name.json +7 -0
  49. package/meta-schemas/user-property.json +8 -24
  50. package/meta-schemas/user-schema.json +4 -1
  51. package/package.json +17 -4
  52. package/dist/defaults-BQej3KIY.mjs +0 -388
  53. package/dist/defaults-BQej3KIY.mjs.map +0 -1
  54. package/dist/defaults-CSjBjBkQ.d.mts +0 -41
  55. package/dist/defaults-CSjBjBkQ.d.mts.map +0 -1
  56. package/dist/meta-schemas-8nlyVL7g.mjs.map +0 -1
@@ -3,18 +3,12 @@ var auth_method_default = {
3
3
  $schema: "https://json-schema.org/draft/2020-12/schema",
4
4
  title: "AuthMethod",
5
5
  type: "object",
6
- required: ["enabled", "position"],
7
- properties: {
8
- "enabled": {
9
- "type": "boolean",
10
- "description": "Whether the authentication method is enabled or not"
11
- },
12
- "position": {
13
- "type": "integer",
14
- "minimum": 0,
15
- "description": "The position of the authentication method in the list of supported methods"
16
- }
17
- }
6
+ required: ["enabled"],
7
+ additionalProperties: false,
8
+ properties: { "enabled": {
9
+ "type": "boolean",
10
+ "description": "Whether the authentication method is enabled or not"
11
+ } }
18
12
  };
19
13
  //#endregion
20
14
  //#region meta-schemas/auth-methods.json
@@ -33,6 +27,50 @@ var auth_methods_default = {
33
27
  }
34
28
  };
35
29
  //#endregion
30
+ //#region meta-schemas/branding.json
31
+ var branding_default = {
32
+ $schema: "https://json-schema.org/draft/2020-12/schema",
33
+ title: "Branding",
34
+ description: "Local branding descriptor for the login UI. Carries the layout preset, asset URLs, and the LiquidJS template (by file reference or inline). `zitadel apply` publishes this as an immutable branding revision; flow responses resolve the latest revision per project (ADR 040). This schema describes the local configuration shape only.",
35
+ type: "object",
36
+ additionalProperties: false,
37
+ properties: {
38
+ "$schema": {
39
+ "type": "string",
40
+ "description": "Editor affordance: path or URL of this meta-schema so editors validate and autocomplete the file (scaffolded branding files point at `.zitadel/meta/branding.json`). Ignored by the platform."
41
+ },
42
+ "layout": {
43
+ "type": "string",
44
+ "enum": ["centered", "split"],
45
+ "default": "centered",
46
+ "description": "Degrade preset (`centered` or `split`) the bundled default template branches on, not the complete design catalog. All named designs (`centered`, `split`, `split-right`, `hero`, `minimal`) ship as templates and map onto one of these two values; switch designs with the `branding eject --design <name>` command instead of editing this field. Custom templates may ignore it; the preset is also used when a template fails component-side validation."
47
+ },
48
+ "liquid_template_file": {
49
+ "type": "string",
50
+ "examples": ["./login.liquid"],
51
+ "description": "Path to the LiquidJS template file, relative to this descriptor. The CLI inlines the file content into `liquid_template` when publishing, and writes server state back to the file. Mutually exclusive with `liquid_template`."
52
+ },
53
+ "liquid_template": {
54
+ "type": "string",
55
+ "maxLength": 131072,
56
+ "description": "Inline LiquidJS template string. Prefer `liquid_template_file` — a real `.liquid` file edits far better than a JSON-escaped string. Mutually exclusive with `liquid_template_file`."
57
+ },
58
+ "logo_url": {
59
+ "type": "string",
60
+ "format": "uri",
61
+ "pattern": "^([hH][tT][tT][pP][sS]://|[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]))?([/?#]|$))",
62
+ "description": "Logo URL (https required; canonical loopback http is allowed for local development and renders only on loopback pages)."
63
+ },
64
+ "hero_url": {
65
+ "type": "string",
66
+ "format": "uri",
67
+ "pattern": "^([hH][tT][tT][pP][sS]://|[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]))?([/?#]|$))",
68
+ "description": "Hero/background image URL (https required; canonical loopback http is allowed for local development and renders only on loopback pages), used by split designs."
69
+ }
70
+ },
71
+ not: { "required": ["liquid_template", "liquid_template_file"] }
72
+ };
73
+ //#endregion
36
74
  //#region meta-schemas/flow-definition.json
37
75
  var flow_definition_default = {
38
76
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -297,12 +335,34 @@ var flow_definition_default = {
297
335
  null
298
336
  ],
299
337
  "description": "How to interpret `target`. - Omitted: transition to a step in the current definition. - `switch`: replace the current flow with the target flow. No return; the current flow is discarded. Typical use: navigating between peer flows like login ↔ register. - `pivot`: push the target flow onto the stack. The current flow is paused; when the target flow completes the engine auto-pops back. Typical use: login → recovery → back to login."
338
+ },
339
+ "purpose": {
340
+ "type": ["string", "null"],
341
+ "enum": [
342
+ "login",
343
+ "register",
344
+ "recovery",
345
+ "profiling",
346
+ "reauth",
347
+ "link_account",
348
+ null
349
+ ],
350
+ "description": "Local re-purposing. When set, taking this transition changes the flow's current purpose to this value — the dispatch mode a step's challenges run under — while the flow's original purpose stays pinned. Must be a purpose this definition serves, and `target` must be that purpose's entry step. Mutually exclusive with `action`: a transition either re-purposes within this flow or targets another flow, never both. Example: a \"Sign up\" navigation on the login identifier step (`{ \"target\": \"register\", \"purpose\": \"register\" }`)."
300
351
  }
301
352
  }
302
353
  }
303
354
  }
304
355
  };
305
356
  //#endregion
357
+ //#region meta-schemas/property-name.json
358
+ var property_name_default = {
359
+ $schema: "https://json-schema.org/draft/2020-12/schema",
360
+ title: "PropertyName",
361
+ type: "string",
362
+ pattern: "^[^.]+$",
363
+ description: "The name of a single user attribute."
364
+ };
365
+ //#endregion
306
366
  //#region meta-schemas/user-property.json
307
367
  var user_property_default = {
308
368
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -316,10 +376,6 @@ var user_property_default = {
316
376
  "const": "https://json-schema.org/draft/2020-12/schema",
317
377
  "description": "The JSON Schema version used for this schema."
318
378
  },
319
- "x-verify": {
320
- "oneOf": [{ "type": "string" }, { "type": "null" }],
321
- "description": "The verification method for this property, if applicable"
322
- },
323
379
  "x-unique": {
324
380
  "oneOf": [{
325
381
  "type": "string",
@@ -335,25 +391,16 @@ var user_property_default = {
335
391
  "oneOf": [{ "type": "string" }, { "type": "null" }],
336
392
  "description": "The claim name for this property, if applicable"
337
393
  },
338
- "x-editable": {
339
- "type": "boolean",
340
- "default": true,
341
- "description": "Whether this property is editable by the user or not"
342
- },
343
- "x-sensitive": {
344
- "type": "boolean",
345
- "default": false,
346
- "description": "Whether this property contains sensitive information or not"
347
- },
348
- "x-mfa": {
394
+ "x-audit": {
349
395
  "type": "boolean",
350
396
  "default": false,
351
- "description": "Whether this property is used for multi-factor authentication or not"
397
+ "description": "Whether this property's value may appear in audit event payloads. Audit payloads are deny-by-default: a property without this annotation contributes its key but never its value."
352
398
  },
353
399
  "properties": {
354
400
  "type": "object",
355
401
  "description": "A map of additional properties for the user definition, where the key is the property name and the value is the property schema",
356
- "additionalProperties": true
402
+ "propertyNames": { "$ref": "property-name.json" },
403
+ "additionalProperties": { "$ref": "user-property.json" }
357
404
  }
358
405
  }
359
406
  };
@@ -397,6 +444,7 @@ var user_schema_default = {
397
444
  "properties": {
398
445
  "type": "object",
399
446
  "description": "A map of additional properties for the user definition, where the key is the property name and the value is the property schema",
447
+ "propertyNames": { "$ref": "property-name.json" },
400
448
  "additionalProperties": { "$ref": "user-property.json" }
401
449
  }
402
450
  }
@@ -422,6 +470,11 @@ const META_SCHEMA_DIR = ".zitadel/meta";
422
470
  */
423
471
  const FLOW_FILE_SCHEMA_REF = "../meta/flow-definition.json";
424
472
  /**
473
+ * The `$schema` value scaffolded branding files carry, relative to
474
+ * `.zitadel/branding/` — resolves to `{@link META_SCHEMA_DIR}/branding.json`.
475
+ */
476
+ const BRANDING_FILE_SCHEMA_REF = "../meta/branding.json";
477
+ /**
425
478
  * The dialect files to materialize, in write order. `auth-methods.json` and
426
479
  * `auth-method.json` are pulled in by `user-schema.json`'s relative `$ref`s —
427
480
  * without them the copied dialect cannot resolve offline.
@@ -440,6 +493,10 @@ function metaSchemaFiles() {
440
493
  name: "user-property.json",
441
494
  body: user_property_default
442
495
  },
496
+ {
497
+ name: "property-name.json",
498
+ body: property_name_default
499
+ },
443
500
  {
444
501
  name: "auth-methods.json",
445
502
  body: auth_methods_default
@@ -447,10 +504,14 @@ function metaSchemaFiles() {
447
504
  {
448
505
  name: "auth-method.json",
449
506
  body: auth_method_default
507
+ },
508
+ {
509
+ name: "branding.json",
510
+ body: branding_default
450
511
  }
451
512
  ];
452
513
  }
453
514
  //#endregion
454
- export { META_SCHEMA_DIR as n, metaSchemaFiles as r, FLOW_FILE_SCHEMA_REF as t };
515
+ export { metaSchemaFiles as i, FLOW_FILE_SCHEMA_REF as n, META_SCHEMA_DIR as r, BRANDING_FILE_SCHEMA_REF as t };
455
516
 
456
- //# sourceMappingURL=meta-schemas-8nlyVL7g.mjs.map
517
+ //# sourceMappingURL=meta-schemas-CSc18JqO.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta-schemas-CSc18JqO.mjs","names":["flowDefinitionMetaSchema","userSchemaMetaSchema","userPropertyMetaSchema","propertyNameMetaSchema","authMethodsMetaSchema","authMethodMetaSchema","brandingMetaSchema"],"sources":["../meta-schemas/auth-method.json","../meta-schemas/auth-methods.json","../meta-schemas/branding.json","../meta-schemas/flow-definition.json","../meta-schemas/property-name.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 committed copies of\n * `api/openapi/endpoints/schemas/*.json` (the source the server embeds);\n * a drift-audit test keeps them byte-identical in the monorepo.\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 propertyNameMetaSchema from \"../meta-schemas/property-name.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` and\n * `auth-method.json` are pulled in by `user-schema.json`'s relative `$ref`s —\n * without them the copied dialect 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: \"branding.json\", body: brandingMetaSchema as object },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AOkCA,MAAa,kBAAkB;;;;;AAM/B,MAAa,uBAAuB;;;;;AAMpC,MAAa,2BAA2B;;;;;;AASxC,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;GAAiB,MAAMC;GAA8B;EAC9D"}
@@ -6,6 +6,11 @@ declare const META_SCHEMA_DIR = ".zitadel/meta";
6
6
  * `.zitadel/flows/` — resolves to `{@link META_SCHEMA_DIR}/flow-definition.json`.
7
7
  */
8
8
  declare const FLOW_FILE_SCHEMA_REF = "../meta/flow-definition.json";
9
+ /**
10
+ * The `$schema` value scaffolded branding files carry, relative to
11
+ * `.zitadel/branding/` — resolves to `{@link META_SCHEMA_DIR}/branding.json`.
12
+ */
13
+ declare const BRANDING_FILE_SCHEMA_REF = "../meta/branding.json";
9
14
  type MetaSchemaFile = {
10
15
  name: string;
11
16
  body: object;
@@ -17,5 +22,5 @@ type MetaSchemaFile = {
17
22
  */
18
23
  declare function metaSchemaFiles(): ReadonlyArray<MetaSchemaFile>;
19
24
  //#endregion
20
- export { FLOW_FILE_SCHEMA_REF, META_SCHEMA_DIR, MetaSchemaFile, metaSchemaFiles };
25
+ export { BRANDING_FILE_SCHEMA_REF, FLOW_FILE_SCHEMA_REF, META_SCHEMA_DIR, MetaSchemaFile, metaSchemaFiles };
21
26
  //# sourceMappingURL=meta-schemas.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"meta-schemas.d.mts","names":[],"sources":["../src/meta-schemas.ts"],"mappings":";;cA4Ba,eAAA;;;;;cAMA,oBAAA;AAAA,KAED,cAAA;EAAmB,IAAA;EAAc,IAAA;AAAA;AAA7C;;;;;AAAA,iBAOgB,eAAA,CAAA,GAAmB,aAAA,CAAc,cAAA"}
1
+ {"version":3,"file":"meta-schemas.d.mts","names":[],"sources":["../src/meta-schemas.ts"],"mappings":";;cAkCa,eAAA;;;;;cAMA,oBAAA;;;;;cAMA,wBAAA;AAAA,KAED,cAAA;EAAmB,IAAA;EAAc,IAAA;AAAA;AAA7C;;;;;AAAA,iBAOgB,eAAA,CAAA,GAAmB,aAAA,CAAc,cAAA"}
@@ -1,2 +1,2 @@
1
- import { n as META_SCHEMA_DIR, r as metaSchemaFiles, t as FLOW_FILE_SCHEMA_REF } from "./meta-schemas-8nlyVL7g.mjs";
2
- export { FLOW_FILE_SCHEMA_REF, META_SCHEMA_DIR, metaSchemaFiles };
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-CSc18JqO.mjs";
2
+ export { BRANDING_FILE_SCHEMA_REF, FLOW_FILE_SCHEMA_REF, META_SCHEMA_DIR, metaSchemaFiles };
@@ -5,21 +5,22 @@
5
5
  * The sync engine compares `.zitadel/**` files against server responses;
6
6
  * the server echoes fields the author never wrote (an empty `audience`
7
7
  * on flows) and the meta-schema declares defaults an author may or may
8
- * not spell out (`x-editable` et al on schema properties). Normalizing
9
- * both sides before hashing or diffing keeps a one-field edit rendering
10
- * as a one-field diff.
8
+ * not spell out (`x-audit` on schema properties). Normalizing both
9
+ * sides before hashing or diffing keeps a one-field edit rendering as a
10
+ * one-field diff.
11
11
  *
12
12
  * Normalized bodies are for COMPARISON. Never upload them, and never
13
13
  * write a normalized schema body to a file: the server stores schema
14
14
  * bytes verbatim without materializing meta-schema defaults, so a
15
- * stripped `"x-editable": true` would vanish from the next published
15
+ * stripped `"x-audit": false` would vanish from the next published
16
16
  * revision. (Flow write-back may reuse {@link normalizeFlowBody} —
17
17
  * everything it strips is pure transport noise.)
18
18
  */
19
19
  /**
20
20
  * Property-level defaults declared by the user-property meta-schema
21
21
  * (`api/openapi/endpoints/schemas/user-property.json`). A property that
22
- * spells one of these out is semantically identical to one that omits it.
22
+ * spells one of these out is semantically identical to one that omits it:
23
+ * audit payloads deny by default.
23
24
  */
24
25
  declare const USER_PROPERTY_DEFAULTS: Readonly<Record<string, boolean>>;
25
26
  /**
@@ -34,10 +35,11 @@ declare const USER_PROPERTY_DEFAULTS: Readonly<Record<string, boolean>>;
34
35
  declare function normalizeFlowBody(body: object): object;
35
36
  /**
36
37
  * Return a deep copy of a user-schema body with meta-schema property
37
- * defaults stripped: a property carrying `"x-editable": true` (or the
38
- * other {@link USER_PROPERTY_DEFAULTS}) normalizes to one omitting it.
39
- * Only exact default values are removed; `$id` and every other field
40
- * pass through untouched.
38
+ * defaults stripped: a property carrying one of the
39
+ * {@link USER_PROPERTY_DEFAULTS} normalizes to one omitting it. Only
40
+ * exact default values are removed; `$id` and every other field pass
41
+ * through untouched. Nested `properties` are descended into, so a leaf
42
+ * of an object property normalizes the same way a top-level one does.
41
43
  */
42
44
  declare function normalizeSchemaBody(body: object): object;
43
45
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.d.mts","names":[],"sources":["../src/normalize.ts"],"mappings":";;AAuBA;;;;;AA0CA;;;;;AAmBA;;;;;;;;;;;cA7Da,sBAAA,EAAwB,QAAA,CAAS,MAAA;;;;;;;;;;iBA0C9B,iBAAA,CAAkB,IAAA;;;;;;;;iBAmBlB,mBAAA,CAAoB,IAAA"}
1
+ {"version":3,"file":"normalize.d.mts","names":[],"sources":["../src/normalize.ts"],"mappings":";;AAwBA;;;;;AAwCA;;;;;AAoBA;;;;;;;;;;;;cA5Da,sBAAA,EAAwB,QAAA,CAAS,MAAA;;;;;;;;;;iBAwC9B,iBAAA,CAAkB,IAAA;;;;;;;;;iBAoBlB,mBAAA,CAAoB,IAAA"}
@@ -5,27 +5,24 @@
5
5
  * The sync engine compares `.zitadel/**` files against server responses;
6
6
  * the server echoes fields the author never wrote (an empty `audience`
7
7
  * on flows) and the meta-schema declares defaults an author may or may
8
- * not spell out (`x-editable` et al on schema properties). Normalizing
9
- * both sides before hashing or diffing keeps a one-field edit rendering
10
- * as a one-field diff.
8
+ * not spell out (`x-audit` on schema properties). Normalizing both
9
+ * sides before hashing or diffing keeps a one-field edit rendering as a
10
+ * one-field diff.
11
11
  *
12
12
  * Normalized bodies are for COMPARISON. Never upload them, and never
13
13
  * write a normalized schema body to a file: the server stores schema
14
14
  * bytes verbatim without materializing meta-schema defaults, so a
15
- * stripped `"x-editable": true` would vanish from the next published
15
+ * stripped `"x-audit": false` would vanish from the next published
16
16
  * revision. (Flow write-back may reuse {@link normalizeFlowBody} —
17
17
  * everything it strips is pure transport noise.)
18
18
  */
19
19
  /**
20
20
  * Property-level defaults declared by the user-property meta-schema
21
21
  * (`api/openapi/endpoints/schemas/user-property.json`). A property that
22
- * spells one of these out is semantically identical to one that omits it.
22
+ * spells one of these out is semantically identical to one that omits it:
23
+ * audit payloads deny by default.
23
24
  */
24
- const USER_PROPERTY_DEFAULTS = {
25
- "x-editable": true,
26
- "x-sensitive": false,
27
- "x-mfa": false
28
- };
25
+ const USER_PROPERTY_DEFAULTS = { "x-audit": false };
29
26
  /**
30
27
  * Keys of the flow-definition detail envelope. `fetch` in the flow syncer
31
28
  * already unwraps the envelope; stripping them here as well makes the
@@ -68,19 +65,29 @@ function normalizeFlowBody(body) {
68
65
  }
69
66
  /**
70
67
  * Return a deep copy of a user-schema body with meta-schema property
71
- * defaults stripped: a property carrying `"x-editable": true` (or the
72
- * other {@link USER_PROPERTY_DEFAULTS}) normalizes to one omitting it.
73
- * Only exact default values are removed; `$id` and every other field
74
- * pass through untouched.
68
+ * defaults stripped: a property carrying one of the
69
+ * {@link USER_PROPERTY_DEFAULTS} normalizes to one omitting it. Only
70
+ * exact default values are removed; `$id` and every other field pass
71
+ * through untouched. Nested `properties` are descended into, so a leaf
72
+ * of an object property normalizes the same way a top-level one does.
75
73
  */
76
74
  function normalizeSchemaBody(body) {
77
75
  const result = structuredClone(body);
78
- if (!isPlainObject(result.properties)) return result;
79
- for (const property of Object.values(result.properties)) {
76
+ stripPropertyDefaults(result);
77
+ return result;
78
+ }
79
+ /**
80
+ * Strip {@link USER_PROPERTY_DEFAULTS} from every property of an object
81
+ * schema, descending into nested `properties` so a leaf of an object
82
+ * property normalizes the same way a top-level one does.
83
+ */
84
+ function stripPropertyDefaults(schema) {
85
+ if (!isPlainObject(schema.properties)) return;
86
+ for (const property of Object.values(schema.properties)) {
80
87
  if (!isPlainObject(property)) continue;
81
88
  for (const [key, defaultValue] of Object.entries(USER_PROPERTY_DEFAULTS)) if (property[key] === defaultValue) delete property[key];
89
+ stripPropertyDefaults(property);
82
90
  }
83
- return result;
84
91
  }
85
92
  //#endregion
86
93
  export { USER_PROPERTY_DEFAULTS, normalizeFlowBody, normalizeSchemaBody };
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.mjs","names":[],"sources":["../src/normalize.ts"],"sourcesContent":["/**\n * Canonical-form normalizers for the two repo-config resource kinds.\n *\n * The sync engine compares `.zitadel/**` files against server responses;\n * the server echoes fields the author never wrote (an empty `audience`\n * on flows) and the meta-schema declares defaults an author may or may\n * not spell out (`x-editable` et al on schema properties). Normalizing\n * both sides before hashing or diffing keeps a one-field edit rendering\n * as a one-field diff.\n *\n * Normalized bodies are for COMPARISON. Never upload them, and never\n * write a normalized schema body to a file: the server stores schema\n * bytes verbatim without materializing meta-schema defaults, so a\n * stripped `\"x-editable\": true` would vanish from the next published\n * revision. (Flow write-back may reuse {@link normalizeFlowBody} —\n * everything it strips is pure transport noise.)\n */\n\n/**\n * Property-level defaults declared by the user-property meta-schema\n * (`api/openapi/endpoints/schemas/user-property.json`). A property that\n * spells one of these out is semantically identical to one that omits it.\n */\nexport const USER_PROPERTY_DEFAULTS: Readonly<Record<string, boolean>> = {\n \"x-editable\": true,\n \"x-sensitive\": false,\n \"x-mfa\": false,\n};\n\n/**\n * Keys of the flow-definition detail envelope. `fetch` in the flow syncer\n * already unwraps the envelope; stripping them here as well makes the\n * normalizer safe on raw detail responses.\n */\nconst FLOW_ENVELOPE_KEYS = [\"id\", \"project_id\", \"schema_uri\", \"created_at\", \"updated_at\"];\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * An `audience` counts as empty when it scopes nothing: no keys, or only\n * `team_ids`/`app_ids` that are null or empty arrays. Unknown keys keep\n * the audience — better a noisy diff than a silently dropped scope.\n */\nfunction isEmptyAudience(value: unknown): boolean {\n if (!isPlainObject(value)) {\n return false;\n }\n return Object.entries(value).every(\n ([key, entry]) =>\n (key === \"team_ids\" || key === \"app_ids\") &&\n (entry === null || entry === undefined || (Array.isArray(entry) && entry.length === 0)),\n );\n}\n\n/**\n * Return a deep copy of a bare flow-definition body with server-echoed\n * noise removed: an empty `audience` and any detail-envelope keys. A\n * top-level `$schema` is stripped too — it is a local editor affordance\n * (scaffolded flow files point it at `.zitadel/meta/flow-definition.json`),\n * never part of the resource: the server skips it on decode and omits it\n * from responses, so comparing with it would read every scaffolded file\n * as permanently diverged.\n */\nexport function normalizeFlowBody(body: object): object {\n const result = structuredClone(body) as Record<string, unknown>;\n for (const key of FLOW_ENVELOPE_KEYS) {\n delete result[key];\n }\n delete result.$schema;\n if (\"audience\" in result && isEmptyAudience(result.audience)) {\n delete result.audience;\n }\n return result;\n}\n\n/**\n * Return a deep copy of a user-schema body with meta-schema property\n * defaults stripped: a property carrying `\"x-editable\": true` (or the\n * other {@link USER_PROPERTY_DEFAULTS}) normalizes to one omitting it.\n * Only exact default values are removed; `$id` and every other field\n * pass through untouched.\n */\nexport function normalizeSchemaBody(body: object): object {\n const result = structuredClone(body) as Record<string, unknown>;\n if (!isPlainObject(result.properties)) {\n return result;\n }\n for (const property of Object.values(result.properties)) {\n if (!isPlainObject(property)) {\n continue;\n }\n for (const [key, defaultValue] of Object.entries(USER_PROPERTY_DEFAULTS)) {\n if (property[key] === defaultValue) {\n delete property[key];\n }\n }\n }\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,yBAA4D;CACvE,cAAc;CACd,eAAe;CACf,SAAS;CACV;;;;;;AAOD,MAAM,qBAAqB;CAAC;CAAM;CAAc;CAAc;CAAc;CAAa;AAEzF,SAAS,cAAc,OAAkD;AACvE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;;;;;;AAQ7E,SAAS,gBAAgB,OAAyB;AAChD,KAAI,CAAC,cAAc,MAAM,CACvB,QAAO;AAET,QAAO,OAAO,QAAQ,MAAM,CAAC,OAC1B,CAAC,KAAK,YACJ,QAAQ,cAAc,QAAQ,eAC9B,UAAU,QAAQ,UAAU,KAAA,KAAc,MAAM,QAAQ,MAAM,IAAI,MAAM,WAAW,GACvF;;;;;;;;;;;AAYH,SAAgB,kBAAkB,MAAsB;CACtD,MAAM,SAAS,gBAAgB,KAAK;AACpC,MAAK,MAAM,OAAO,mBAChB,QAAO,OAAO;AAEhB,QAAO,OAAO;AACd,KAAI,cAAc,UAAU,gBAAgB,OAAO,SAAS,CAC1D,QAAO,OAAO;AAEhB,QAAO;;;;;;;;;AAUT,SAAgB,oBAAoB,MAAsB;CACxD,MAAM,SAAS,gBAAgB,KAAK;AACpC,KAAI,CAAC,cAAc,OAAO,WAAW,CACnC,QAAO;AAET,MAAK,MAAM,YAAY,OAAO,OAAO,OAAO,WAAW,EAAE;AACvD,MAAI,CAAC,cAAc,SAAS,CAC1B;AAEF,OAAK,MAAM,CAAC,KAAK,iBAAiB,OAAO,QAAQ,uBAAuB,CACtE,KAAI,SAAS,SAAS,aACpB,QAAO,SAAS;;AAItB,QAAO"}
1
+ {"version":3,"file":"normalize.mjs","names":[],"sources":["../src/normalize.ts"],"sourcesContent":["/**\n * Canonical-form normalizers for the two repo-config resource kinds.\n *\n * The sync engine compares `.zitadel/**` files against server responses;\n * the server echoes fields the author never wrote (an empty `audience`\n * on flows) and the meta-schema declares defaults an author may or may\n * not spell out (`x-audit` on schema properties). Normalizing both\n * sides before hashing or diffing keeps a one-field edit rendering as a\n * one-field diff.\n *\n * Normalized bodies are for COMPARISON. Never upload them, and never\n * write a normalized schema body to a file: the server stores schema\n * bytes verbatim without materializing meta-schema defaults, so a\n * stripped `\"x-audit\": false` would vanish from the next published\n * revision. (Flow write-back may reuse {@link normalizeFlowBody} —\n * everything it strips is pure transport noise.)\n */\n\n/**\n * Property-level defaults declared by the user-property meta-schema\n * (`api/openapi/endpoints/schemas/user-property.json`). A property that\n * spells one of these out is semantically identical to one that omits it:\n * audit payloads deny by default.\n */\nexport const USER_PROPERTY_DEFAULTS: Readonly<Record<string, boolean>> = {\n \"x-audit\": false,\n};\n\n/**\n * Keys of the flow-definition detail envelope. `fetch` in the flow syncer\n * already unwraps the envelope; stripping them here as well makes the\n * normalizer safe on raw detail responses.\n */\nconst FLOW_ENVELOPE_KEYS = [\"id\", \"project_id\", \"schema_uri\", \"created_at\", \"updated_at\"];\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * An `audience` counts as empty when it scopes nothing: no keys, or only\n * `team_ids`/`app_ids` that are null or empty arrays. Unknown keys keep\n * the audience — better a noisy diff than a silently dropped scope.\n */\nfunction isEmptyAudience(value: unknown): boolean {\n if (!isPlainObject(value)) {\n return false;\n }\n return Object.entries(value).every(\n ([key, entry]) =>\n (key === \"team_ids\" || key === \"app_ids\") &&\n (entry === null || entry === undefined || (Array.isArray(entry) && entry.length === 0)),\n );\n}\n\n/**\n * Return a deep copy of a bare flow-definition body with server-echoed\n * noise removed: an empty `audience` and any detail-envelope keys. A\n * top-level `$schema` is stripped too — it is a local editor affordance\n * (scaffolded flow files point it at `.zitadel/meta/flow-definition.json`),\n * never part of the resource: the server skips it on decode and omits it\n * from responses, so comparing with it would read every scaffolded file\n * as permanently diverged.\n */\nexport function normalizeFlowBody(body: object): object {\n const result = structuredClone(body) as Record<string, unknown>;\n for (const key of FLOW_ENVELOPE_KEYS) {\n delete result[key];\n }\n delete result.$schema;\n if (\"audience\" in result && isEmptyAudience(result.audience)) {\n delete result.audience;\n }\n return result;\n}\n\n/**\n * Return a deep copy of a user-schema body with meta-schema property\n * defaults stripped: a property carrying one of the\n * {@link USER_PROPERTY_DEFAULTS} normalizes to one omitting it. Only\n * exact default values are removed; `$id` and every other field pass\n * through untouched. Nested `properties` are descended into, so a leaf\n * of an object property normalizes the same way a top-level one does.\n */\nexport function normalizeSchemaBody(body: object): object {\n const result = structuredClone(body) as Record<string, unknown>;\n stripPropertyDefaults(result);\n return result;\n}\n\n/**\n * Strip {@link USER_PROPERTY_DEFAULTS} from every property of an object\n * schema, descending into nested `properties` so a leaf of an object\n * property normalizes the same way a top-level one does.\n */\nfunction stripPropertyDefaults(schema: Record<string, unknown>): void {\n if (!isPlainObject(schema.properties)) {\n return;\n }\n for (const property of Object.values(schema.properties)) {\n if (!isPlainObject(property)) {\n continue;\n }\n for (const [key, defaultValue] of Object.entries(USER_PROPERTY_DEFAULTS)) {\n if (property[key] === defaultValue) {\n delete property[key];\n }\n }\n stripPropertyDefaults(property);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,yBAA4D,EACvE,WAAW,OACZ;;;;;;AAOD,MAAM,qBAAqB;CAAC;CAAM;CAAc;CAAc;CAAc;CAAa;AAEzF,SAAS,cAAc,OAAkD;AACvE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;;;;;;AAQ7E,SAAS,gBAAgB,OAAyB;AAChD,KAAI,CAAC,cAAc,MAAM,CACvB,QAAO;AAET,QAAO,OAAO,QAAQ,MAAM,CAAC,OAC1B,CAAC,KAAK,YACJ,QAAQ,cAAc,QAAQ,eAC9B,UAAU,QAAQ,UAAU,KAAA,KAAc,MAAM,QAAQ,MAAM,IAAI,MAAM,WAAW,GACvF;;;;;;;;;;;AAYH,SAAgB,kBAAkB,MAAsB;CACtD,MAAM,SAAS,gBAAgB,KAAK;AACpC,MAAK,MAAM,OAAO,mBAChB,QAAO,OAAO;AAEhB,QAAO,OAAO;AACd,KAAI,cAAc,UAAU,gBAAgB,OAAO,SAAS,CAC1D,QAAO,OAAO;AAEhB,QAAO;;;;;;;;;;AAWT,SAAgB,oBAAoB,MAAsB;CACxD,MAAM,SAAS,gBAAgB,KAAK;AACpC,uBAAsB,OAAO;AAC7B,QAAO;;;;;;;AAQT,SAAS,sBAAsB,QAAuC;AACpE,KAAI,CAAC,cAAc,OAAO,WAAW,CACnC;AAEF,MAAK,MAAM,YAAY,OAAO,OAAO,OAAO,WAAW,EAAE;AACvD,MAAI,CAAC,cAAc,SAAS,CAC1B;AAEF,OAAK,MAAM,CAAC,KAAK,iBAAiB,OAAO,QAAQ,uBAAuB,CACtE,KAAI,SAAS,SAAS,aACpB,QAAO,SAAS;AAGpB,wBAAsB,SAAS"}
@@ -1,7 +1,20 @@
1
+ import { z } from "zod";
2
+
1
3
  //#region src/schemas.d.ts
2
4
  declare const schemaConfigSchema: any;
3
5
  declare const flowConfigSchema: any;
4
6
  declare const createFlowDefinitionRequestSchema: any;
7
+ /** The wire shape of a branding revision (`POST /branding` request body). */
8
+ declare const brandingWireSchema: any;
9
+ /**
10
+ * The local `.zitadel/branding/*.json` dialect: the wire shape plus
11
+ * `liquid_template_file`, a descriptor-relative path the CLI inlines into
12
+ * `liquid_template` when publishing. Exactly one of the two template carriers
13
+ * may be present (the meta-schema `branding.json` mirrors this).
14
+ */
15
+ declare const brandingConfigSchema: z.ZodObject<{
16
+ [x: string]: any;
17
+ }, z.core.$strict>;
5
18
  //#endregion
6
- export { createFlowDefinitionRequestSchema, flowConfigSchema, schemaConfigSchema };
19
+ export { brandingConfigSchema, brandingWireSchema, createFlowDefinitionRequestSchema, flowConfigSchema, schemaConfigSchema };
7
20
  //# sourceMappingURL=schemas.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.mts","names":[],"sources":["../src/schemas.ts"],"mappings":";cAKa,kBAAA;AAAA,cACA,gBAAA;AAAA,cACA,iCAAA"}
1
+ {"version":3,"file":"schemas.d.mts","names":[],"sources":["../src/schemas.ts"],"mappings":";;;cASa,kBAAA;AAAA,cACA,gBAAA;AAAA,cACA,iCAAA;;cAGA,kBAAA;;;AAJb;;;;cAYa,oBAAA,EAAoB,CAAA,CAAA,SAAA;EAAA;GAuC7B,CAAA,CAAA,IAAA,CAAA,OAAA"}
package/dist/schemas.mjs CHANGED
@@ -1,9 +1,69 @@
1
- import { CreateFlowDefinitionBody, CreateSchemaBody } from "@zitadel/api/generated/endpoints/zitadelNextGen.zod";
1
+ import { isCanonicalLoopbackHttpUrl } from "./branding-url.mjs";
2
+ import { CreateBrandingBody, CreateFlowDefinitionBody, CreateSchemaBody } from "@zitadel/api/generated/endpoints/zitadelNextGen.zod";
3
+ import { z } from "zod";
2
4
  //#region src/schemas.ts
3
5
  const schemaConfigSchema = CreateSchemaBody;
4
6
  const flowConfigSchema = CreateFlowDefinitionBody.shape.flow_definition;
5
7
  const createFlowDefinitionRequestSchema = CreateFlowDefinitionBody;
8
+ /** The wire shape of a branding revision (`POST /branding` request body). */
9
+ const brandingWireSchema = CreateBrandingBody;
10
+ /**
11
+ * The local `.zitadel/branding/*.json` dialect: the wire shape plus
12
+ * `liquid_template_file`, a descriptor-relative path the CLI inlines into
13
+ * `liquid_template` when publishing. Exactly one of the two template carriers
14
+ * may be present (the meta-schema `branding.json` mirrors this).
15
+ */
16
+ const brandingConfigSchema = z.strictObject({
17
+ ...CreateBrandingBody.shape,
18
+ logo_url: z.string().optional(),
19
+ hero_url: z.string().optional(),
20
+ $schema: z.string().optional(),
21
+ liquid_template_file: z.string().min(1).optional()
22
+ }).superRefine((value, ctx) => {
23
+ if (value.liquid_template !== void 0 && value.liquid_template_file !== void 0) ctx.addIssue({
24
+ code: "custom",
25
+ message: "Use either liquid_template_file or an inline liquid_template, not both."
26
+ });
27
+ if (value.font_url !== void 0) ctx.addIssue({
28
+ code: "custom",
29
+ message: "font_url is not writable yet (tenant font delivery needs a safe design, see ADR 040); load fonts from the embedding page instead."
30
+ });
31
+ validateBrandingAssetUrl(value.logo_url, "logo_url", ctx);
32
+ validateBrandingAssetUrl(value.hero_url, "hero_url", ctx);
33
+ });
34
+ function validateBrandingAssetUrl(value, field, ctx) {
35
+ if (value === void 0 || value === "") return;
36
+ if (/[\s\\]/.test(value)) {
37
+ ctx.addIssue({
38
+ code: "custom",
39
+ message: `${field} is not a valid URL.`
40
+ });
41
+ return;
42
+ }
43
+ if (!/^https?:\/\//i.test(value)) {
44
+ ctx.addIssue({
45
+ code: "custom",
46
+ message: `${field} must be an absolute https URL.`
47
+ });
48
+ return;
49
+ }
50
+ let parsed;
51
+ try {
52
+ parsed = new URL(value);
53
+ } catch {
54
+ ctx.addIssue({
55
+ code: "custom",
56
+ message: `${field} is not a valid URL.`
57
+ });
58
+ return;
59
+ }
60
+ if (parsed.protocol === "http:" && isCanonicalLoopbackHttpUrl(value)) return;
61
+ if (parsed.protocol !== "https:" || parsed.host === "") ctx.addIssue({
62
+ code: "custom",
63
+ message: `${field} must be an absolute https URL (http is allowed for canonical loopback development hosts only).`
64
+ });
65
+ }
6
66
  //#endregion
7
- export { createFlowDefinitionRequestSchema, flowConfigSchema, schemaConfigSchema };
67
+ export { brandingConfigSchema, brandingWireSchema, createFlowDefinitionRequestSchema, flowConfigSchema, schemaConfigSchema };
8
68
 
9
69
  //# sourceMappingURL=schemas.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.mjs","names":[],"sources":["../src/schemas.ts"],"sourcesContent":["import {\n CreateFlowDefinitionBody,\n CreateSchemaBody,\n} from \"@zitadel/api/generated/endpoints/zitadelNextGen.zod\";\n\nexport const schemaConfigSchema = CreateSchemaBody;\nexport const flowConfigSchema = CreateFlowDefinitionBody.shape.flow_definition;\nexport const createFlowDefinitionRequestSchema = CreateFlowDefinitionBody;\n"],"mappings":";;AAKA,MAAa,qBAAqB;AAClC,MAAa,mBAAmB,yBAAyB,MAAM;AAC/D,MAAa,oCAAoC"}
1
+ {"version":3,"file":"schemas.mjs","names":[],"sources":["../src/schemas.ts"],"sourcesContent":["import {\n CreateBrandingBody,\n CreateFlowDefinitionBody,\n CreateSchemaBody,\n} from \"@zitadel/api/generated/endpoints/zitadelNextGen.zod\";\nimport { z } from \"zod\";\n\nimport { isCanonicalLoopbackHttpUrl } from \"./branding-url.js\";\n\nexport const schemaConfigSchema = CreateSchemaBody;\nexport const flowConfigSchema = CreateFlowDefinitionBody.shape.flow_definition;\nexport const createFlowDefinitionRequestSchema = CreateFlowDefinitionBody;\n\n/** The wire shape of a branding revision (`POST /branding` request body). */\nexport const brandingWireSchema = CreateBrandingBody;\n\n/**\n * The local `.zitadel/branding/*.json` dialect: the wire shape plus\n * `liquid_template_file`, a descriptor-relative path the CLI inlines into\n * `liquid_template` when publishing. Exactly one of the two template carriers\n * may be present (the meta-schema `branding.json` mirrors this).\n */\nexport const brandingConfigSchema = z\n .strictObject({\n // Strict, unlike the generated wire schema: a typo like `hero_urll`\n // must fail plan, not silently pass, get ignored by the server, and\n // vanish on canonical write-back. The editor meta-schema already\n // rejects unknown keys; the CLI gate has to agree.\n ...CreateBrandingBody.shape,\n // Raw strings instead of the generated uri fields: those trim and\n // normalize before refinement runs, hiding exactly the forms the wire\n // body still carries verbatim and Go's url.Parse rejects.\n logo_url: z.string().optional(),\n hero_url: z.string().optional(),\n $schema: z.string().optional(),\n liquid_template_file: z.string().min(1).optional(),\n })\n .superRefine((value, ctx) => {\n if (value.liquid_template !== undefined && value.liquid_template_file !== undefined) {\n ctx.addIssue({\n code: \"custom\",\n message: \"Use either liquid_template_file or an inline liquid_template, not both.\",\n });\n }\n // font_url is read-only in v1: the login component loads it as a\n // document-level stylesheet (shadow-scoped @font-face never registers\n // faces), which would give branding.write arbitrary CSS over the embedding\n // page. The server rejects it too; safe delivery is an ADR 040 follow-up.\n if (value.font_url !== undefined) {\n ctx.addIssue({\n code: \"custom\",\n message:\n \"font_url is not writable yet (tenant font delivery needs a safe design, see ADR 040); load fonts from the embedding page instead.\",\n });\n }\n // Mirror the server's asset URL gate (validateBrandingAssetURL in\n // internal/domain/branding_validator.go) so plan rejects what apply would.\n // Apply mutates schemas and flows before branding, so a late 400 here would\n // leave a half-applied run.\n validateBrandingAssetUrl(value.logo_url, \"logo_url\", ctx);\n validateBrandingAssetUrl(value.hero_url, \"hero_url\", ctx);\n });\n\nfunction validateBrandingAssetUrl(\n value: string | undefined,\n field: \"logo_url\" | \"hero_url\",\n ctx: z.RefinementCtx,\n): void {\n if (value === undefined || value === \"\") {\n return;\n }\n // Stricter-or-equal than the Go gate (validateBrandingAssetURL): the WHATWG\n // parser forgives what Go's url.Parse rejects — `https:example.com`,\n // backslashes, and whitespace all get silently normalized. plan must never\n // accept what apply would reject (the reverse is harmless), so reject the\n // raw forms before parsing.\n if (/[\\s\\\\]/.test(value)) {\n ctx.addIssue({ code: \"custom\", message: `${field} is not a valid URL.` });\n return;\n }\n if (!/^https?:\\/\\//i.test(value)) {\n ctx.addIssue({ code: \"custom\", message: `${field} must be an absolute https URL.` });\n return;\n }\n let parsed: URL;\n try {\n parsed = new URL(value);\n } catch {\n ctx.addIssue({ code: \"custom\", message: `${field} is not a valid URL.` });\n return;\n }\n // Loopback HTTP is the dev-posture carve-out (assets served from the app's\n // own dev server). Check the raw URL so WHATWG normalisation cannot make\n // plan accept a host spelling that the Go save gate rejects.\n if (parsed.protocol === \"http:\" && isCanonicalLoopbackHttpUrl(value)) {\n return;\n }\n if (parsed.protocol !== \"https:\" || parsed.host === \"\") {\n ctx.addIssue({\n code: \"custom\",\n message: `${field} must be an absolute https URL (http is allowed for canonical loopback development hosts only).`,\n });\n }\n}\n"],"mappings":";;;;AASA,MAAa,qBAAqB;AAClC,MAAa,mBAAmB,yBAAyB,MAAM;AAC/D,MAAa,oCAAoC;;AAGjD,MAAa,qBAAqB;;;;;;;AAQlC,MAAa,uBAAuB,EACjC,aAAa;CAKZ,GAAG,mBAAmB;CAItB,UAAU,EAAE,QAAQ,CAAC,UAAU;CAC/B,UAAU,EAAE,QAAQ,CAAC,UAAU;CAC/B,SAAS,EAAE,QAAQ,CAAC,UAAU;CAC9B,sBAAsB,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,UAAU;CACnD,CAAC,CACD,aAAa,OAAO,QAAQ;AAC3B,KAAI,MAAM,oBAAoB,KAAA,KAAa,MAAM,yBAAyB,KAAA,EACxE,KAAI,SAAS;EACX,MAAM;EACN,SAAS;EACV,CAAC;AAMJ,KAAI,MAAM,aAAa,KAAA,EACrB,KAAI,SAAS;EACX,MAAM;EACN,SACE;EACH,CAAC;AAMJ,0BAAyB,MAAM,UAAU,YAAY,IAAI;AACzD,0BAAyB,MAAM,UAAU,YAAY,IAAI;EACzD;AAEJ,SAAS,yBACP,OACA,OACA,KACM;AACN,KAAI,UAAU,KAAA,KAAa,UAAU,GACnC;AAOF,KAAI,SAAS,KAAK,MAAM,EAAE;AACxB,MAAI,SAAS;GAAE,MAAM;GAAU,SAAS,GAAG,MAAM;GAAuB,CAAC;AACzE;;AAEF,KAAI,CAAC,gBAAgB,KAAK,MAAM,EAAE;AAChC,MAAI,SAAS;GAAE,MAAM;GAAU,SAAS,GAAG,MAAM;GAAkC,CAAC;AACpF;;CAEF,IAAI;AACJ,KAAI;AACF,WAAS,IAAI,IAAI,MAAM;SACjB;AACN,MAAI,SAAS;GAAE,MAAM;GAAU,SAAS,GAAG,MAAM;GAAuB,CAAC;AACzE;;AAKF,KAAI,OAAO,aAAa,WAAW,2BAA2B,MAAM,CAClE;AAEF,KAAI,OAAO,aAAa,YAAY,OAAO,SAAS,GAClD,KAAI,SAAS;EACX,MAAM;EACN,SAAS,GAAG,MAAM;EACnB,CAAC"}
@@ -0,0 +1,76 @@
1
+ //#region src/template.d.ts
2
+ /** The `{% mandatory_gates %}` runtime safety-net tag every template must emit. */
3
+ declare const MANDATORY_GATES_TAG = "mandatory_gates";
4
+ /**
5
+ * Custom Liquid tags the login orchestrator registers beyond the standard
6
+ * set. Validators must register them (as no-ops) or parsing valid templates
7
+ * fails; `@zitadel/components` registers the real implementations.
8
+ */
9
+ declare const TEMPLATE_TAGS: readonly ["mandatory_gates"];
10
+ /**
11
+ * Byte cap for stored templates. Mirrors `maxLength` on
12
+ * `branding.liquid_template` in the OpenAPI contract and
13
+ * `MaxBrandingTemplateBytes` in the Go gate.
14
+ */
15
+ declare const MAX_TEMPLATE_BYTES = 131072;
16
+ type TemplateValidationSeverity = "error" | "warning";
17
+ /**
18
+ * Rule catalog with pointers into the Go lexical gate
19
+ * (`internal/domain/branding_validator.go`) where a server-side mirror
20
+ * exists. `parse` and `mandatory-gates` are authoring-only: the server
21
+ * cannot check them (wrong dialect) and render-time behavior degrades
22
+ * safely without them.
23
+ */
24
+ declare const TEMPLATE_VALIDATION_RULES: {
25
+ readonly parse: {
26
+ readonly goRef: null;
27
+ };
28
+ readonly size: {
29
+ readonly goRef: "MaxBrandingTemplateBytes";
30
+ };
31
+ readonly "no-script-tag": {
32
+ readonly goRef: "brandingScriptTag";
33
+ };
34
+ readonly "no-style-tag": {
35
+ readonly goRef: "brandingStyleTag";
36
+ };
37
+ readonly "no-inline-handlers": {
38
+ readonly goRef: "brandingInlineHandler";
39
+ };
40
+ readonly "no-javascript-urls": {
41
+ readonly goRef: "brandingJavascriptURL";
42
+ };
43
+ readonly "no-raw-filter": {
44
+ readonly goRef: "brandingRawFilter";
45
+ };
46
+ readonly "mandatory-gates": {
47
+ readonly goRef: null;
48
+ };
49
+ };
50
+ type TemplateValidationRuleId = keyof typeof TEMPLATE_VALIDATION_RULES;
51
+ type TemplateValidationIssue = {
52
+ severity: TemplateValidationSeverity;
53
+ rule: TemplateValidationRuleId;
54
+ message: string;
55
+ };
56
+ type BannedPattern = {
57
+ rule: TemplateValidationRuleId;
58
+ pattern: RegExp;
59
+ message: string;
60
+ };
61
+ /**
62
+ * Lexical bans, identical to the Go gate. The inline-handler pattern only
63
+ * matches inside an open tag so Liquid like `{% assign online = true %}`
64
+ * doesn't false-positive; its separator class includes `/` because HTML
65
+ * treats a slash between attributes as whitespace (`<img/onerror=…>`).
66
+ */
67
+ declare const BANNED_TEMPLATE_PATTERNS: readonly BannedPattern[];
68
+ /**
69
+ * Validates a login template. Pure and non-throwing, like
70
+ * `validateFlowDefinition`: returns an issue list, empty when the template
71
+ * is valid.
72
+ */
73
+ declare function validateLoginTemplate(template: string): TemplateValidationIssue[];
74
+ //#endregion
75
+ export { BANNED_TEMPLATE_PATTERNS, MANDATORY_GATES_TAG, MAX_TEMPLATE_BYTES, TEMPLATE_TAGS, TEMPLATE_VALIDATION_RULES, TemplateValidationIssue, TemplateValidationRuleId, TemplateValidationSeverity, validateLoginTemplate };
76
+ //# sourceMappingURL=template.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.d.mts","names":[],"sources":["../src/template.ts"],"mappings":";;cAkBa,mBAAA;;;;;AAOb;cAAa,aAAA;;;;AAOb;;cAAa,kBAAA;AAAA,KAED,0BAAA;;AAAZ;;;;;AASA;cAAa,yBAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;KAWD,wBAAA,gBAAwC,yBAAA;AAAA,KAExC,uBAAA;EACV,QAAA,EAAU,0BAAA;EACV,IAAA,EAAM,wBAAA;EACN,OAAA;AAAA;AAAA,KAGG,aAAA;EACH,IAAA,EAAM,wBAAA;EACN,OAAA,EAAS,MAAA;EACT,OAAA;AAAA;AALA;;;;;;AAAA,cAcW,wBAAA,WAAmC,aAAA;;;;;AAAhD;iBAqEgB,qBAAA,CAAsB,QAAA,WAAmB,uBAAA"}