@actuate-media/cms-core 0.51.2 → 0.52.0

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.
@@ -26,6 +26,7 @@ export declare const blockNodeSchema: z.ZodObject<{
26
26
  data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
27
27
  }, "strip", z.ZodTypeAny, {
28
28
  type: "block";
29
+ data: Record<string, unknown>;
29
30
  id: string;
30
31
  settings: {
31
32
  blockType: string;
@@ -33,13 +34,13 @@ export declare const blockNodeSchema: z.ZodObject<{
33
34
  } & {
34
35
  [k: string]: unknown;
35
36
  };
36
- data: Record<string, unknown>;
37
37
  responsive?: {
38
38
  mobile?: Record<string, unknown> | undefined;
39
39
  tablet?: Record<string, unknown> | undefined;
40
40
  } | undefined;
41
41
  }, {
42
42
  type: "block";
43
+ data: Record<string, unknown>;
43
44
  id: string;
44
45
  settings: {
45
46
  blockType: string;
@@ -47,7 +48,6 @@ export declare const blockNodeSchema: z.ZodObject<{
47
48
  } & {
48
49
  [k: string]: unknown;
49
50
  };
50
- data: Record<string, unknown>;
51
51
  responsive?: {
52
52
  mobile?: Record<string, unknown> | undefined;
53
53
  tablet?: Record<string, unknown> | undefined;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Sanitize a page-builder layout node tree in place-immutably, cleaning the
3
+ * rich-text block bodies. Containers/rows/columns recurse through `children`.
4
+ */
5
+ export declare function sanitizeLayoutTree(layout: unknown): unknown;
6
+ /**
7
+ * Sanitize every author-HTML sink reachable in a document's `data` payload for
8
+ * the given collection: top-level + nested rich-text fields and the canonical
9
+ * `sections[]` rich-text slots. The page-builder `_layout` tree is sanitized
10
+ * separately via {@link sanitizeLayoutTree} because it is split into an envelope
11
+ * before this runs.
12
+ */
13
+ export declare function sanitizeDocumentHtml(data: Record<string, unknown>, collection: string): Record<string, unknown>;
14
+ //# sourceMappingURL=sanitize-document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize-document.d.ts","sourceRoot":"","sources":["../../src/security/sanitize-document.ts"],"names":[],"mappings":"AAwGA;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAE3D;AAkBD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,UAAU,EAAE,MAAM,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWzB"}
@@ -0,0 +1,117 @@
1
+ import { getActuateConfig } from '../config/runtime.js';
2
+ import { getSectionType } from '../sections/registry.js';
3
+ import { sanitizeHtml } from './sanitize.js';
4
+ /** Bound recursion against pathological / adversarial nesting depth. */
5
+ const MAX_DEPTH = 10;
6
+ function isPlainObject(value) {
7
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
8
+ }
9
+ /** Block-data keys that conventionally carry author HTML in the page-builder tree. */
10
+ const HTML_BLOCK_KEYS = ['content', 'html'];
11
+ /** Resolve a `blocks` field item to its block definition via the conventional discriminator. */
12
+ function pickBlockSlug(item) {
13
+ for (const key of ['blockType', 'blockName', 'blockSlug', '_type']) {
14
+ const v = item[key];
15
+ if (typeof v === 'string')
16
+ return v;
17
+ }
18
+ return undefined;
19
+ }
20
+ function sanitizeObjectByFields(obj, fields, depth) {
21
+ const out = { ...obj };
22
+ for (const [key, def] of Object.entries(fields)) {
23
+ if (key in out)
24
+ out[key] = sanitizeByFieldDef(out[key], def, depth);
25
+ }
26
+ return out;
27
+ }
28
+ function sanitizeByFieldDef(value, def, depth) {
29
+ if (depth > MAX_DEPTH)
30
+ return value;
31
+ switch (def.type) {
32
+ case 'richText':
33
+ return typeof value === 'string' ? sanitizeHtml(value) : value;
34
+ case 'array': {
35
+ if (!Array.isArray(value))
36
+ return value;
37
+ const arrayDef = def;
38
+ return value.map((item) => isPlainObject(item) ? sanitizeObjectByFields(item, arrayDef.fields, depth + 1) : item);
39
+ }
40
+ case 'blocks': {
41
+ if (!Array.isArray(value))
42
+ return value;
43
+ const blocksDef = def;
44
+ return value.map((item) => {
45
+ if (!isPlainObject(item))
46
+ return item;
47
+ const slug = pickBlockSlug(item);
48
+ const blockDef = slug ? blocksDef.blocks.find((b) => b.slug === slug) : undefined;
49
+ if (!blockDef)
50
+ return item;
51
+ return sanitizeObjectByFields(item, blockDef.fields, depth + 1);
52
+ });
53
+ }
54
+ default:
55
+ return value;
56
+ }
57
+ }
58
+ /** Sanitize the rich-text slots of a single canonical `PageSection`. */
59
+ function sanitizeSection(section) {
60
+ if (!isPlainObject(section))
61
+ return section;
62
+ const typeId = typeof section.sectionType === 'string' ? section.sectionType : undefined;
63
+ const def = typeId ? getSectionType(typeId) : undefined;
64
+ if (!def || !isPlainObject(section.content))
65
+ return section;
66
+ let mutated = false;
67
+ const content = { ...section.content };
68
+ for (const field of def.fields) {
69
+ if (field.type === 'richText' && typeof content[field.key] === 'string') {
70
+ content[field.key] = sanitizeHtml(content[field.key]);
71
+ mutated = true;
72
+ }
73
+ }
74
+ return mutated ? { ...section, content } : section;
75
+ }
76
+ /**
77
+ * Sanitize a page-builder layout node tree in place-immutably, cleaning the
78
+ * rich-text block bodies. Containers/rows/columns recurse through `children`.
79
+ */
80
+ export function sanitizeLayoutTree(layout) {
81
+ return sanitizeNode(layout, 0);
82
+ }
83
+ function sanitizeNode(node, depth) {
84
+ if (depth > MAX_DEPTH || !isPlainObject(node))
85
+ return node;
86
+ const out = { ...node };
87
+ if (out.type === 'block' && isPlainObject(out.data)) {
88
+ const data = { ...out.data };
89
+ for (const key of HTML_BLOCK_KEYS) {
90
+ if (typeof data[key] === 'string')
91
+ data[key] = sanitizeHtml(data[key]);
92
+ }
93
+ out.data = data;
94
+ }
95
+ if (Array.isArray(out.children)) {
96
+ out.children = out.children.map((child) => sanitizeNode(child, depth + 1));
97
+ }
98
+ return out;
99
+ }
100
+ /**
101
+ * Sanitize every author-HTML sink reachable in a document's `data` payload for
102
+ * the given collection: top-level + nested rich-text fields and the canonical
103
+ * `sections[]` rich-text slots. The page-builder `_layout` tree is sanitized
104
+ * separately via {@link sanitizeLayoutTree} because it is split into an envelope
105
+ * before this runs.
106
+ */
107
+ export function sanitizeDocumentHtml(data, collection) {
108
+ const fields = getActuateConfig()?.collections?.[collection]?.fields ?? {};
109
+ let result = sanitizeObjectByFields(data, fields, 0);
110
+ // `sections` is the canonical page-content array; it is NOT part of the
111
+ // collection field config, so resolve its HTML slots from the registry.
112
+ if (Array.isArray(result.sections)) {
113
+ result = { ...result, sections: result.sections.map(sanitizeSection) };
114
+ }
115
+ return result;
116
+ }
117
+ //# sourceMappingURL=sanitize-document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize-document.js","sourceRoot":"","sources":["../../src/security/sanitize-document.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actuate-media/cms-core",
3
- "version": "0.51.2",
3
+ "version": "0.52.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/actuate-media/actuatecms.git",