@adobe/spacecat-shared-data-access 4.15.1 → 4.15.2

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/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [@adobe/spacecat-shared-data-access-v4.15.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v4.15.1...@adobe/spacecat-shared-data-access-v4.15.2) (2026-07-29)
2
+
3
+ ### Bug Fixes
4
+
5
+ * **site:** guard config writes against the config schema ([#1852](https://github.com/adobe/spacecat-shared/issues/1852)) ([dafbea2](https://github.com/adobe/spacecat-shared/commit/dafbea2619d1ff65f27b0fe5ca47734b2622619d)), closes [#1850](https://github.com/adobe/spacecat-shared/issues/1850)
6
+
1
7
  ## [@adobe/spacecat-shared-data-access-v4.15.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v4.15.0...@adobe/spacecat-shared-data-access-v4.15.1) (2026-07-29)
2
8
 
3
9
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "4.15.1",
3
+ "version": "4.15.2",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "engines": {
@@ -18,6 +18,8 @@ import {
18
18
  AUTHORING_TYPES,
19
19
  } from '@adobe/spacecat-shared-utils';
20
20
  import BaseModel from '../base/base.model.js';
21
+ import { validateConfiguration } from './config.js';
22
+ import { guardConfigValidation } from '../../util/config-validation-guard.js';
21
23
 
22
24
  const HLX_HOST = /\.(?:aem|hlx)\.(?:page|live)$/i;
23
25
  export const AEM_CS_HOST = /^author-p(\d+)-e(\d+)/i;
@@ -84,6 +86,34 @@ class Site extends BaseModel {
84
86
  return this;
85
87
  }
86
88
 
89
+ /**
90
+ * Sets the site config, guarding it against the config schema
91
+ * (`validateConfiguration`). Overrides the auto-generated setter so every
92
+ * writer (e.g. the `PATCH /sites/{id}` config merge) is checked at one
93
+ * chokepoint. Behavior is governed by `CONFIG_VALIDATION_ENFORCEMENT`
94
+ * (default `warn`; `enforce` throws; `off` skips) — see
95
+ * config-validation-guard.js for the rollout rationale.
96
+ *
97
+ * Note: this only guards writes. Reads still go through the lenient
98
+ * `Config()` getter (attribute `get:` transform), which intentionally
99
+ * tolerates legacy invalid config already stored on existing sites so a
100
+ * bad historical record doesn't break every read of that site.
101
+ *
102
+ * @param {object} value - candidate config object
103
+ * @returns {this}
104
+ */
105
+ setConfig(value) {
106
+ guardConfigValidation({
107
+ entityName: Site.ENTITY_NAME,
108
+ entityId: this.getId(),
109
+ value,
110
+ validate: validateConfiguration,
111
+ log: this.log,
112
+ });
113
+ this.patcher.patchValue('config', value, false);
114
+ return this;
115
+ }
116
+
87
117
  /**
88
118
  * Resolves the site's base URL to a final URL by fetching the URL,
89
119
  * following the redirects and returning the final URL.
@@ -0,0 +1,76 @@
1
+ /*
2
+ * Copyright 2026 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import { ValidationError } from '../errors/index.js';
13
+
14
+ export const ENFORCEMENT_MODES = {
15
+ OFF: 'off',
16
+ WARN: 'warn',
17
+ ENFORCE: 'enforce',
18
+ };
19
+
20
+ /**
21
+ * Resolves the config-validation enforcement mode from the environment.
22
+ *
23
+ * `CONFIG_VALIDATION_ENFORCEMENT` ∈ { off, warn, enforce }; defaults to `warn`.
24
+ * Read at call time so deployments (and tests) can flip it without a re-import.
25
+ *
26
+ * Rollout rationale: `Site.setConfig()` previously accepted any value with no
27
+ * schema validation at all on the update path (only entity `create()` runs
28
+ * attribute validation — see base.collection.js `#validateItem`/`#prepareItem`).
29
+ * Some existing sites carry legacy config that already fails today's Joi
30
+ * schema (e.g. an `imports` entry that predates a stricter schema addition).
31
+ * Flipping straight to `enforce` would reject unrelated PATCH requests on
32
+ * those sites. Ship `warn` first to surface violations in logs, then flip to
33
+ * `enforce` once legacy data is cleaned up or confirmed rare.
34
+ *
35
+ * @returns {string} one of ENFORCEMENT_MODES
36
+ */
37
+ export const getConfigEnforcementMode = () => {
38
+ const raw = (process.env.CONFIG_VALIDATION_ENFORCEMENT || '').trim().toLowerCase();
39
+ return Object.values(ENFORCEMENT_MODES).includes(raw) ? raw : ENFORCEMENT_MODES.WARN;
40
+ };
41
+
42
+ /**
43
+ * Guards a config write. Validates `value` against the config schema via
44
+ * `validateConfiguration`. A valid config passes silently. An invalid config
45
+ * is, depending on the enforcement mode, ignored (`off`), logged without
46
+ * blocking (`warn`), or rejected (`enforce`).
47
+ *
48
+ * @param {object} params
49
+ * @param {string} params.entityName - e.g. 'Site' (for the log message)
50
+ * @param {string} [params.entityId] - entity id (for the log message)
51
+ * @param {object} params.value - the candidate config object
52
+ * @param {(value: object) => object} params.validate - validateConfiguration
53
+ * @param {object} [params.log] - logger with a `warn` method
54
+ * @throws {ValidationError} in `enforce` mode when validation fails
55
+ */
56
+ export const guardConfigValidation = ({
57
+ entityName, entityId, value, validate, log,
58
+ }) => {
59
+ const mode = getConfigEnforcementMode();
60
+ if (mode === ENFORCEMENT_MODES.OFF) {
61
+ return;
62
+ }
63
+
64
+ try {
65
+ validate(value);
66
+ } catch (error) {
67
+ const message = `config validation violation: ${entityName} ${entityId ?? '<unknown>'}: ${error.message}`;
68
+ if (log) {
69
+ log.warn(message);
70
+ }
71
+
72
+ if (mode === ENFORCEMENT_MODES.ENFORCE) {
73
+ throw new ValidationError(`Invalid config for ${entityName}: ${error.message}`);
74
+ }
75
+ }
76
+ };