@zitadel/config 0.1.0-alpha.18 → 1.0.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/defaults/README-branding.md +36 -2
- package/defaults/README-flows.md +21 -4
- package/defaults/README-schemas.md +9 -2
- package/defaults/branding/centered/login.liquid +30 -14
- package/defaults/branding/hero/login.liquid +33 -17
- package/defaults/branding/minimal/login.liquid +25 -14
- package/defaults/branding/split/login.liquid +34 -15
- package/defaults/branding/split-right/login.liquid +34 -15
- package/defaults/default-human-user.json +3 -4
- package/defaults/default-login.json +20 -0
- package/defaults/presets/passkey-first/human-user.json +3 -4
- package/defaults/presets/passkey-first/login.json +30 -0
- package/dist/branding-url.d.mts +14 -0
- package/dist/branding-url.d.mts.map +1 -0
- package/dist/branding-url.mjs +23 -0
- package/dist/branding-url.mjs.map +1 -0
- package/dist/{defaults-CXTnFMHj.mjs → defaults-DPq0o3iU.mjs} +132 -77
- package/dist/{defaults-CXTnFMHj.mjs.map → defaults-DPq0o3iU.mjs.map} +1 -1
- package/dist/defaults.mjs +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +4 -3
- package/dist/{meta-schemas-B4YiHAX_.mjs → meta-schemas-z4IlqbLd.mjs} +41 -34
- package/dist/{meta-schemas-B4YiHAX_.mjs.map → meta-schemas-z4IlqbLd.mjs.map} +1 -1
- package/dist/meta-schemas.mjs +1 -1
- package/dist/normalize.d.mts +11 -9
- package/dist/normalize.d.mts.map +1 -1
- package/dist/normalize.mjs +25 -19
- package/dist/normalize.mjs.map +1 -1
- package/dist/schemas.d.mts.map +1 -1
- package/dist/schemas.mjs +8 -6
- package/dist/schemas.mjs.map +1 -1
- package/dist/validate.mjs +72 -13
- package/dist/validate.mjs.map +1 -1
- package/meta-schemas/auth-method.json +2 -7
- package/meta-schemas/branding.json +8 -16
- package/meta-schemas/flow-definition.json +20 -1
- package/meta-schemas/user-property.json +2 -23
- package/meta-schemas/user-schema.json +14 -0
- package/package.json +7 -2
|
@@ -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"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
@@ -49,7 +43,7 @@ var branding_default = {
|
|
|
49
43
|
"type": "string",
|
|
50
44
|
"enum": ["centered", "split"],
|
|
51
45
|
"default": "centered",
|
|
52
|
-
"description": "
|
|
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."
|
|
53
47
|
},
|
|
54
48
|
"liquid_template_file": {
|
|
55
49
|
"type": "string",
|
|
@@ -64,14 +58,14 @@ var branding_default = {
|
|
|
64
58
|
"logo_url": {
|
|
65
59
|
"type": "string",
|
|
66
60
|
"format": "uri",
|
|
67
|
-
"pattern": "^[hH][tT][tT][pP][sS]://",
|
|
68
|
-
"description": "Logo URL (https required)."
|
|
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)."
|
|
69
63
|
},
|
|
70
64
|
"hero_url": {
|
|
71
65
|
"type": "string",
|
|
72
66
|
"format": "uri",
|
|
73
|
-
"pattern": "^[hH][tT][tT][pP][sS]://",
|
|
74
|
-
"description": "Hero/background image URL (https required), used by split designs."
|
|
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."
|
|
75
69
|
}
|
|
76
70
|
},
|
|
77
71
|
not: { "required": ["liquid_template", "liquid_template_file"] }
|
|
@@ -341,6 +335,19 @@ var flow_definition_default = {
|
|
|
341
335
|
null
|
|
342
336
|
],
|
|
343
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\" }`)."
|
|
344
351
|
}
|
|
345
352
|
}
|
|
346
353
|
}
|
|
@@ -369,10 +376,6 @@ var user_property_default = {
|
|
|
369
376
|
"const": "https://json-schema.org/draft/2020-12/schema",
|
|
370
377
|
"description": "The JSON Schema version used for this schema."
|
|
371
378
|
},
|
|
372
|
-
"x-verify": {
|
|
373
|
-
"oneOf": [{ "type": "string" }, { "type": "null" }],
|
|
374
|
-
"description": "The verification method for this property, if applicable"
|
|
375
|
-
},
|
|
376
379
|
"x-unique": {
|
|
377
380
|
"oneOf": [{
|
|
378
381
|
"type": "string",
|
|
@@ -388,20 +391,10 @@ var user_property_default = {
|
|
|
388
391
|
"oneOf": [{ "type": "string" }, { "type": "null" }],
|
|
389
392
|
"description": "The claim name for this property, if applicable"
|
|
390
393
|
},
|
|
391
|
-
"x-
|
|
392
|
-
"type": "boolean",
|
|
393
|
-
"default": true,
|
|
394
|
-
"description": "Whether this property is editable by the user or not"
|
|
395
|
-
},
|
|
396
|
-
"x-sensitive": {
|
|
394
|
+
"x-audit": {
|
|
397
395
|
"type": "boolean",
|
|
398
396
|
"default": false,
|
|
399
|
-
"description": "Whether this property
|
|
400
|
-
},
|
|
401
|
-
"x-mfa": {
|
|
402
|
-
"type": "boolean",
|
|
403
|
-
"default": false,
|
|
404
|
-
"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."
|
|
405
398
|
},
|
|
406
399
|
"properties": {
|
|
407
400
|
"type": "object",
|
|
@@ -448,6 +441,20 @@ var user_schema_default = {
|
|
|
448
441
|
"description": "The user Schema version used for this schema."
|
|
449
442
|
},
|
|
450
443
|
"x-auth-methods": { "$ref": "auth-methods.json" },
|
|
444
|
+
"x-identifier": {
|
|
445
|
+
"type": "string",
|
|
446
|
+
"minLength": 1,
|
|
447
|
+
"description": "Path of the leaf property whose value identifies a user (nested leaves are addressed by their dot-joined attribute path). The property must carry x-unique \"project\". Required when an auth method needing identifier-first dispatch (password) is enabled. Passkey is exempt: discoverable credentials identify the user through the assertion itself, so passkey-only and API-managed schemas may designate nothing; flows using identifier-first passkey are validated at the flow level instead."
|
|
448
|
+
},
|
|
449
|
+
"x-display": {
|
|
450
|
+
"type": "array",
|
|
451
|
+
"minItems": 1,
|
|
452
|
+
"items": {
|
|
453
|
+
"type": "string",
|
|
454
|
+
"minLength": 1
|
|
455
|
+
},
|
|
456
|
+
"description": "Ordered leaf property paths whose values, joined with a space, render the user's display name."
|
|
457
|
+
},
|
|
451
458
|
"properties": {
|
|
452
459
|
"type": "object",
|
|
453
460
|
"description": "A map of additional properties for the user definition, where the key is the property name and the value is the property schema",
|
|
@@ -521,4 +528,4 @@ function metaSchemaFiles() {
|
|
|
521
528
|
//#endregion
|
|
522
529
|
export { metaSchemaFiles as i, FLOW_FILE_SCHEMA_REF as n, META_SCHEMA_DIR as r, BRANDING_FILE_SCHEMA_REF as t };
|
|
523
530
|
|
|
524
|
-
//# sourceMappingURL=meta-schemas-
|
|
531
|
+
//# sourceMappingURL=meta-schemas-z4IlqbLd.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta-schemas-
|
|
1
|
+
{"version":3,"file":"meta-schemas-z4IlqbLd.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"}
|
package/dist/meta-schemas.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as metaSchemaFiles, n as FLOW_FILE_SCHEMA_REF, r as META_SCHEMA_DIR, t as BRANDING_FILE_SCHEMA_REF } from "./meta-schemas-
|
|
1
|
+
import { i as metaSchemaFiles, n as FLOW_FILE_SCHEMA_REF, r as META_SCHEMA_DIR, t as BRANDING_FILE_SCHEMA_REF } from "./meta-schemas-z4IlqbLd.mjs";
|
|
2
2
|
export { BRANDING_FILE_SCHEMA_REF, FLOW_FILE_SCHEMA_REF, META_SCHEMA_DIR, metaSchemaFiles };
|
package/dist/normalize.d.mts
CHANGED
|
@@ -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-
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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-
|
|
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
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
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
|
package/dist/normalize.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize.d.mts","names":[],"sources":["../src/normalize.ts"],"mappings":";;
|
|
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"}
|
package/dist/normalize.mjs
CHANGED
|
@@ -5,36 +5,32 @@
|
|
|
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-
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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-
|
|
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
|
-
* Keys of the flow-definition
|
|
27
|
+
* Keys of the flow-definition response envelope. `fetch` in the flow syncer
|
|
31
28
|
* already unwraps the envelope; stripping them here as well makes the
|
|
32
29
|
* normalizer safe on raw detail responses.
|
|
33
30
|
*/
|
|
34
31
|
const FLOW_ENVELOPE_KEYS = [
|
|
35
32
|
"id",
|
|
36
33
|
"project_id",
|
|
37
|
-
"schema_uri",
|
|
38
34
|
"created_at",
|
|
39
35
|
"updated_at"
|
|
40
36
|
];
|
|
@@ -68,19 +64,29 @@ function normalizeFlowBody(body) {
|
|
|
68
64
|
}
|
|
69
65
|
/**
|
|
70
66
|
* Return a deep copy of a user-schema body with meta-schema property
|
|
71
|
-
* defaults stripped: a property carrying
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
67
|
+
* defaults stripped: a property carrying one of the
|
|
68
|
+
* {@link USER_PROPERTY_DEFAULTS} normalizes to one omitting it. Only
|
|
69
|
+
* exact default values are removed; `$id` and every other field pass
|
|
70
|
+
* through untouched. Nested `properties` are descended into, so a leaf
|
|
71
|
+
* of an object property normalizes the same way a top-level one does.
|
|
75
72
|
*/
|
|
76
73
|
function normalizeSchemaBody(body) {
|
|
77
74
|
const result = structuredClone(body);
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
stripPropertyDefaults(result);
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Strip {@link USER_PROPERTY_DEFAULTS} from every property of an object
|
|
80
|
+
* schema, descending into nested `properties` so a leaf of an object
|
|
81
|
+
* property normalizes the same way a top-level one does.
|
|
82
|
+
*/
|
|
83
|
+
function stripPropertyDefaults(schema) {
|
|
84
|
+
if (!isPlainObject(schema.properties)) return;
|
|
85
|
+
for (const property of Object.values(schema.properties)) {
|
|
80
86
|
if (!isPlainObject(property)) continue;
|
|
81
87
|
for (const [key, defaultValue] of Object.entries(USER_PROPERTY_DEFAULTS)) if (property[key] === defaultValue) delete property[key];
|
|
88
|
+
stripPropertyDefaults(property);
|
|
82
89
|
}
|
|
83
|
-
return result;
|
|
84
90
|
}
|
|
85
91
|
//#endregion
|
|
86
92
|
export { USER_PROPERTY_DEFAULTS, normalizeFlowBody, normalizeSchemaBody };
|
package/dist/normalize.mjs.map
CHANGED
|
@@ -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-
|
|
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 response 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\", \"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;CAAa;AAE3E,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"}
|
package/dist/schemas.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.d.mts","names":[],"sources":["../src/schemas.ts"],"mappings":";;;
|
|
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,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isCanonicalLoopbackHttpUrl } from "./branding-url.mjs";
|
|
2
2
|
import { CreateBrandingBody, CreateFlowDefinitionBody, CreateSchemaBody } from "@zitadel/api/generated/endpoints/zitadelNextGen.zod";
|
|
3
|
+
import { z } from "zod";
|
|
3
4
|
//#region src/schemas.ts
|
|
4
5
|
const schemaConfigSchema = CreateSchemaBody;
|
|
5
6
|
const flowConfigSchema = CreateFlowDefinitionBody.shape.flow_definition;
|
|
@@ -27,10 +28,10 @@ const brandingConfigSchema = z.strictObject({
|
|
|
27
28
|
code: "custom",
|
|
28
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."
|
|
29
30
|
});
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
validateBrandingAssetUrl(value.logo_url, "logo_url", ctx);
|
|
32
|
+
validateBrandingAssetUrl(value.hero_url, "hero_url", ctx);
|
|
32
33
|
});
|
|
33
|
-
function
|
|
34
|
+
function validateBrandingAssetUrl(value, field, ctx) {
|
|
34
35
|
if (value === void 0 || value === "") return;
|
|
35
36
|
if (/[\s\\]/.test(value)) {
|
|
36
37
|
ctx.addIssue({
|
|
@@ -39,7 +40,7 @@ function requireHttpsUrl(value, field, ctx) {
|
|
|
39
40
|
});
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
|
-
if (!/^https
|
|
43
|
+
if (!/^https?:\/\//i.test(value)) {
|
|
43
44
|
ctx.addIssue({
|
|
44
45
|
code: "custom",
|
|
45
46
|
message: `${field} must be an absolute https URL.`
|
|
@@ -56,9 +57,10 @@ function requireHttpsUrl(value, field, ctx) {
|
|
|
56
57
|
});
|
|
57
58
|
return;
|
|
58
59
|
}
|
|
60
|
+
if (parsed.protocol === "http:" && isCanonicalLoopbackHttpUrl(value)) return;
|
|
59
61
|
if (parsed.protocol !== "https:" || parsed.host === "") ctx.addIssue({
|
|
60
62
|
code: "custom",
|
|
61
|
-
message: `${field} must be an absolute https URL.`
|
|
63
|
+
message: `${field} must be an absolute https URL (http is allowed for canonical loopback development hosts only).`
|
|
62
64
|
});
|
|
63
65
|
}
|
|
64
66
|
//#endregion
|
package/dist/schemas.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.mjs","names":[],"sources":["../src/schemas.ts"],"sourcesContent":["import {
|
|
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"}
|
package/dist/validate.mjs
CHANGED
|
@@ -114,7 +114,8 @@ function readFlow(flow) {
|
|
|
114
114
|
const entry = isPlainObject(t) ? t : {};
|
|
115
115
|
transitions.set(key, {
|
|
116
116
|
target: str(entry.target),
|
|
117
|
-
action: typeof entry.action === "string" ? entry.action : null
|
|
117
|
+
action: typeof entry.action === "string" ? entry.action : null,
|
|
118
|
+
purpose: typeof entry.purpose === "string" ? entry.purpose : null
|
|
118
119
|
});
|
|
119
120
|
}
|
|
120
121
|
steps.push({
|
|
@@ -226,9 +227,18 @@ function bfs(start, adj, visited) {
|
|
|
226
227
|
}
|
|
227
228
|
function validateGraph(def, stepNames) {
|
|
228
229
|
const issues = [];
|
|
229
|
-
for (const step of def.steps) for (const [key, t] of step.transitions)
|
|
230
|
-
if (
|
|
231
|
-
|
|
230
|
+
for (const step of def.steps) for (const [key, t] of step.transitions) {
|
|
231
|
+
if (t.purpose !== null) if (t.action !== null) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} declares both purpose and action; a transition either re-purposes locally or targets another flow`, step.name));
|
|
232
|
+
else if (!FLOW_PURPOSES.includes(t.purpose)) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} has invalid purpose`, step.name));
|
|
233
|
+
else {
|
|
234
|
+
const entry = def.purposes.get(t.purpose);
|
|
235
|
+
if (entry === void 0) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} re-purposes to ${q(t.purpose)}, which this definition does not serve`, step.name));
|
|
236
|
+
else if (t.target !== entry) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} re-purposes to ${q(t.purpose)} but targets ${q(t.target)}; it must target that purpose's entry step ${q(entry)}`, step.name));
|
|
237
|
+
}
|
|
238
|
+
if (isCurrentFlow(t)) {
|
|
239
|
+
if (!stepNames.has(t.target)) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} targets unknown step ${q(t.target)}`, step.name));
|
|
240
|
+
} else if (t.action !== "switch" && t.action !== "pivot") issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} has invalid action ${q(t.action ?? "")}`, step.name));
|
|
241
|
+
}
|
|
232
242
|
for (const step of def.steps) if (!step.terminal && step.transitions.size === 0) issues.push(error("graph", `step ${q(step.name)} is non-terminal but has no outgoing transitions`, step.name));
|
|
233
243
|
const reachable = /* @__PURE__ */ new Set();
|
|
234
244
|
const adj = localAdjacency(def);
|
|
@@ -276,6 +286,38 @@ function schemaProperties(schema) {
|
|
|
276
286
|
function schemaRequired(schema) {
|
|
277
287
|
return Array.isArray(schema.required) ? schema.required.map(str).filter((n) => n !== "") : [];
|
|
278
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Mirrors `schemaReader.RequiredPaths`: every name in `required`,
|
|
291
|
+
* recursed through the nested `required` of each required object. A
|
|
292
|
+
* required object declaring no `required` of its own ends the descent at
|
|
293
|
+
* the object itself.
|
|
294
|
+
*
|
|
295
|
+
* An optional object contributes its own `required` too once it appears
|
|
296
|
+
* in `materialized`. It only exists in the document because something
|
|
297
|
+
* beneath it was collected, and from that point document validation
|
|
298
|
+
* enforces the rest of its `required` list.
|
|
299
|
+
*/
|
|
300
|
+
function requiredPaths(schema, materialized, prefix = "") {
|
|
301
|
+
const properties = schemaProperties(schema);
|
|
302
|
+
const required = schemaRequired(schema);
|
|
303
|
+
const out = /* @__PURE__ */ new Set();
|
|
304
|
+
for (const name of required) {
|
|
305
|
+
const path = prefix === "" ? name : `${prefix}.${name}`;
|
|
306
|
+
const property = properties !== void 0 && Object.hasOwn(properties, name) ? properties[name] : void 0;
|
|
307
|
+
if (!isPlainObject(property) || schemaRequired(property).length === 0) {
|
|
308
|
+
out.add(path);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
for (const nested of requiredPaths(property, materialized, path)) out.add(nested);
|
|
312
|
+
}
|
|
313
|
+
for (const [name, property] of Object.entries(properties ?? {})) {
|
|
314
|
+
if (required.includes(name) || !isPlainObject(property)) continue;
|
|
315
|
+
const path = prefix === "" ? name : `${prefix}.${name}`;
|
|
316
|
+
if (!materialized.has(path)) continue;
|
|
317
|
+
for (const nested of requiredPaths(property, materialized, path)) out.add(nested);
|
|
318
|
+
}
|
|
319
|
+
return [...out];
|
|
320
|
+
}
|
|
279
321
|
/** Mirrors `xAuthMethodsReader.IsEnabled` in flow_field_resolver_schema.go. */
|
|
280
322
|
function authMethodEnabled(schema, method) {
|
|
281
323
|
if (!isPlainObject(schema["x-auth-methods"])) return false;
|
|
@@ -294,15 +336,26 @@ function resolveFieldChallenge(field, properties, schema) {
|
|
|
294
336
|
if (!authMethodEnabled(schema, method)) return { message: `${q(method)} is not an enabled authentication method` };
|
|
295
337
|
return { challenge: "password" };
|
|
296
338
|
}
|
|
297
|
-
const
|
|
298
|
-
|
|
339
|
+
const segments = field.split(".");
|
|
340
|
+
let property = void 0;
|
|
341
|
+
let level = properties;
|
|
342
|
+
for (const [i, segment] of segments.entries()) {
|
|
343
|
+
property = level !== void 0 && Object.hasOwn(level, segment) ? level[segment] : void 0;
|
|
344
|
+
if (property === void 0) return { message: `flow field: not a property in the user schema: ${q(field)}` };
|
|
345
|
+
const nested = isPlainObject(property) ? schemaProperties(property) : void 0;
|
|
346
|
+
if (i < segments.length - 1 && nested === void 0) return { message: `flow field: not a property in the user schema: ${q(field)}` };
|
|
347
|
+
level = nested;
|
|
348
|
+
}
|
|
299
349
|
if (!isPlainObject(property)) return { challenge: null };
|
|
300
|
-
|
|
350
|
+
let jsonType = property.type;
|
|
301
351
|
if (Array.isArray(jsonType)) {
|
|
302
|
-
|
|
352
|
+
const nonNull = jsonType.filter((t) => t !== "null");
|
|
353
|
+
if (nonNull.length > 1) return { message: `flow field: unsupported JSON type: [${jsonType.map(str).join(" ")}]` };
|
|
354
|
+
jsonType = nonNull[0];
|
|
303
355
|
}
|
|
304
|
-
|
|
305
|
-
|
|
356
|
+
if (jsonType === "object" || jsonType === "array" || schemaProperties(property) !== void 0 || "items" in property) return { message: `flow field: not a scalar property, name a nested leaf instead: ${q(field)}` };
|
|
357
|
+
const identifier = schema["x-identifier"];
|
|
358
|
+
if (typeof identifier === "string" && identifier !== "" && field === identifier) return { challenge: "identifier" };
|
|
306
359
|
return { challenge: null };
|
|
307
360
|
}
|
|
308
361
|
function validateAgainstSchema(def, schema) {
|
|
@@ -313,9 +366,15 @@ function validateAgainstSchema(def, schema) {
|
|
|
313
366
|
issues.push(error("schema/fields-resolve", "user schema has no properties"));
|
|
314
367
|
return issues;
|
|
315
368
|
}
|
|
316
|
-
const
|
|
317
|
-
for (const step of def.steps) for (const field of step.fields)
|
|
318
|
-
|
|
369
|
+
const covered = /* @__PURE__ */ new Set();
|
|
370
|
+
for (const step of def.steps) for (const field of step.fields) {
|
|
371
|
+
let path = "";
|
|
372
|
+
for (const node of field.split(".")) {
|
|
373
|
+
path = path === "" ? node : `${path}.${node}`;
|
|
374
|
+
covered.add(path);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const missing = requiredPaths(raw, covered).filter((field) => !covered.has(field)).sort();
|
|
319
378
|
if (missing.length > 0) issues.push(error("schema/required-fields", `required fields [${missing.join(" ")}] in user schema are missing in the flow definition steps`));
|
|
320
379
|
const challengesByStep = /* @__PURE__ */ new Map();
|
|
321
380
|
let resolutionFailed = false;
|