@adobe/helix-config-storage 1.15.4 → 2.0.1
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 +25 -0
- package/package.json +1 -1
- package/src/config-store.js +81 -0
- package/src/schemas/sidekick.schema.json +37 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
## [2.0.1](https://github.com/adobe/helix-config-storage/compare/v2.0.0...v2.0.1) (2025-03-03)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add review environment, popover and badges ([8a85edd](https://github.com/adobe/helix-config-storage/commit/8a85eddd7af9162b533eedefa479b744a792e072))
|
|
7
|
+
* dependentRequired ([88d54ce](https://github.com/adobe/helix-config-storage/commit/88d54ce7c52c2a4ff3c49fd3b3870066f7360ec1))
|
|
8
|
+
|
|
9
|
+
# [2.0.0](https://github.com/adobe/helix-config-storage/compare/v1.15.4...v2.0.0) (2025-03-03)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* tetss ([2d6c782](https://github.com/adobe/helix-config-storage/commit/2d6c782e7685416fe329608254a763b48b2c2102))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* add access control around features, limits and modifying admin roles ([c3d4a20](https://github.com/adobe/helix-config-storage/commit/c3d4a2005fdc9f580e0149198bdc02ac0af7f17a))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### BREAKING CHANGES
|
|
23
|
+
|
|
24
|
+
* modifying access control needs 'isAdmin' flag to be set
|
|
25
|
+
|
|
1
26
|
## [1.15.4](https://github.com/adobe/helix-config-storage/compare/v1.15.3...v1.15.4) (2025-02-25)
|
|
2
27
|
|
|
3
28
|
|
package/package.json
CHANGED
package/src/config-store.js
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from './utils.js';
|
|
24
24
|
import { validate as validateSchema } from './config-validator.js';
|
|
25
25
|
import { getMergedConfig } from './config-merge.js';
|
|
26
|
+
import { ValidationError } from './ValidationError.js';
|
|
26
27
|
|
|
27
28
|
const FRAGMENTS_COMMON = {
|
|
28
29
|
content: 'object',
|
|
@@ -300,6 +301,28 @@ export class ConfigStore {
|
|
|
300
301
|
? `/orgs/${org}/${name || 'config'}.json`
|
|
301
302
|
: `/orgs/${org}/${type}/${name}.json`;
|
|
302
303
|
this.now = new Date();
|
|
304
|
+
this.isAdmin = false;
|
|
305
|
+
this.isOps = false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Controls if setting the admin role is allowed.
|
|
310
|
+
* @param {boolean} v
|
|
311
|
+
* @returns {ConfigStore} this
|
|
312
|
+
*/
|
|
313
|
+
withAllowAdmin(v) {
|
|
314
|
+
this.isAdmin = v;
|
|
315
|
+
return this;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Controls if setting ops related properties, like features and limits are allowed.
|
|
320
|
+
* @param {boolean} v
|
|
321
|
+
* @returns {ConfigStore} this
|
|
322
|
+
*/
|
|
323
|
+
withAllowOps(v) {
|
|
324
|
+
this.isOps = v;
|
|
325
|
+
return this;
|
|
303
326
|
}
|
|
304
327
|
|
|
305
328
|
/**
|
|
@@ -370,6 +393,62 @@ export class ConfigStore {
|
|
|
370
393
|
return validateSchema(data, this.type);
|
|
371
394
|
}
|
|
372
395
|
|
|
396
|
+
/**
|
|
397
|
+
* Checks if the permissions allow to update the config.
|
|
398
|
+
* @param {AdminConfig} ctx
|
|
399
|
+
* @param {object} oldConfig
|
|
400
|
+
* @param {object} newConfig
|
|
401
|
+
* @returns {Promise<void>}
|
|
402
|
+
*/
|
|
403
|
+
async validatePermissions(ctx, oldConfig, newConfig) {
|
|
404
|
+
// prevent setting the ops role
|
|
405
|
+
if (newConfig.access?.admin?.role?.ops) {
|
|
406
|
+
throw new ValidationError('invalid role: ops');
|
|
407
|
+
}
|
|
408
|
+
// ops can do everything
|
|
409
|
+
if (this.isOps) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
// required admin to set admin role
|
|
413
|
+
if (!this.isAdmin) {
|
|
414
|
+
if (this.type === 'org') {
|
|
415
|
+
const getAdmins = (users) => users
|
|
416
|
+
.filter((user) => user.roles.includes('admin'))
|
|
417
|
+
.map((user) => user.email)
|
|
418
|
+
.sort((a, b) => a.localeCompare(b));
|
|
419
|
+
// get the users with the admin role
|
|
420
|
+
const oldAdmins = getAdmins(oldConfig?.users ?? []);
|
|
421
|
+
const newAdmins = getAdmins(newConfig?.users ?? []);
|
|
422
|
+
if (oldAdmins.join() !== newAdmins.join()) {
|
|
423
|
+
throw new StatusCodeError(403, 'not allowed to modify admin role');
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
// check for changed admin roles
|
|
427
|
+
const oldAdmins = (oldConfig?.access?.admin?.role?.admin || [])
|
|
428
|
+
.sort((a, b) => a.localeCompare(b));
|
|
429
|
+
const newAdmins = (newConfig?.access?.admin?.role?.admin || [])
|
|
430
|
+
.sort((a, b) => a.localeCompare(b));
|
|
431
|
+
if (oldAdmins.join() !== newAdmins.join()) {
|
|
432
|
+
throw new StatusCodeError(403, 'not allowed to modify admin role');
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// check for changed features or limits
|
|
438
|
+
if (this.type !== 'org') {
|
|
439
|
+
const oldFeatures = oldConfig?.features ?? {};
|
|
440
|
+
const newFeatures = newConfig?.features ?? {};
|
|
441
|
+
if (!isDeepStrictEqual(oldFeatures, newFeatures)) {
|
|
442
|
+
throw new StatusCodeError(403, 'not allowed to modify features');
|
|
443
|
+
}
|
|
444
|
+
const oldLimits = oldConfig?.limits ?? {};
|
|
445
|
+
const newLimits = newConfig?.limits ?? {};
|
|
446
|
+
if (!isDeepStrictEqual(oldLimits, newLimits)) {
|
|
447
|
+
throw new StatusCodeError(403, 'not allowed to modify limits');
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
373
452
|
async create(ctx, data, relPath = '') {
|
|
374
453
|
if (relPath) {
|
|
375
454
|
throw new StatusCodeError(409, 'create not supported on substructures.');
|
|
@@ -396,6 +475,7 @@ export class ConfigStore {
|
|
|
396
475
|
this.#updateTimeStamps(data);
|
|
397
476
|
const config = await this.getAggregatedConfig(ctx, data);
|
|
398
477
|
await this.validate(ctx, config);
|
|
478
|
+
await this.validatePermissions(ctx, {}, config);
|
|
399
479
|
await storage.put(this.key, JSON.stringify(data), 'application/json');
|
|
400
480
|
await this.purge(ctx, null, config);
|
|
401
481
|
}
|
|
@@ -576,6 +656,7 @@ export class ConfigStore {
|
|
|
576
656
|
}
|
|
577
657
|
|
|
578
658
|
await this.validate(ctx, newConfig);
|
|
659
|
+
await this.validatePermissions(ctx, oldConfig, newConfig);
|
|
579
660
|
await storage.put(this.key, JSON.stringify(config), 'application/json');
|
|
580
661
|
await this.purge(ctx, oldConfig, newConfig);
|
|
581
662
|
return ret ?? redact(data, frag);
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"type": "array",
|
|
27
27
|
"items": {
|
|
28
28
|
"type": "string",
|
|
29
|
-
"enum": ["any", "dev", "admin", "edit", "preview", "live", "prod"]
|
|
29
|
+
"enum": ["any", "dev", "admin", "edit", "preview", "live", "prod", "review"]
|
|
30
30
|
},
|
|
31
31
|
"description": "The environments to display this plugin in",
|
|
32
32
|
"default": "any"
|
|
@@ -69,6 +69,14 @@
|
|
|
69
69
|
"type": "string",
|
|
70
70
|
"description": "he dimensions and position of a palette box"
|
|
71
71
|
},
|
|
72
|
+
"isPopover": {
|
|
73
|
+
"type": "boolean",
|
|
74
|
+
"description": "Opens the URL in a popover instead of a new tab"
|
|
75
|
+
},
|
|
76
|
+
"popoverRect": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"description": "The dimensions of a popover, delimited by a semicolon (width, height)"
|
|
79
|
+
},
|
|
72
80
|
"titleI18n": {
|
|
73
81
|
"type": "object",
|
|
74
82
|
"description": "The button text in other supported languages",
|
|
@@ -88,6 +96,30 @@
|
|
|
88
96
|
"passReferrer": {
|
|
89
97
|
"type": "boolean",
|
|
90
98
|
"description": "Append the referrer URL as a query param on new URL button click"
|
|
99
|
+
},
|
|
100
|
+
"isBadge": {
|
|
101
|
+
"type": "boolean",
|
|
102
|
+
"description": "Opens the URL in a palette instead of a new tab"
|
|
103
|
+
},
|
|
104
|
+
"badgeVariant": {
|
|
105
|
+
"type": "string",
|
|
106
|
+
"description": "The variant of the badge following the Adobe Spectrum badge variants",
|
|
107
|
+
"enum": [
|
|
108
|
+
"gray",
|
|
109
|
+
"red",
|
|
110
|
+
"orange",
|
|
111
|
+
"yellow",
|
|
112
|
+
"chartreuse",
|
|
113
|
+
"celery",
|
|
114
|
+
"green",
|
|
115
|
+
"seafoam",
|
|
116
|
+
"cyan",
|
|
117
|
+
"blue",
|
|
118
|
+
"indigo",
|
|
119
|
+
"purple",
|
|
120
|
+
"fuchsia",
|
|
121
|
+
"magenta"
|
|
122
|
+
]
|
|
91
123
|
}
|
|
92
124
|
},
|
|
93
125
|
"required": [
|
|
@@ -95,7 +127,10 @@
|
|
|
95
127
|
],
|
|
96
128
|
"dependentRequired": {
|
|
97
129
|
"isPalette": ["url"],
|
|
98
|
-
"paletteRect": ["isPalette"]
|
|
130
|
+
"paletteRect": ["isPalette"],
|
|
131
|
+
"isPopover": ["url"],
|
|
132
|
+
"popoverRect": ["isPopover"],
|
|
133
|
+
"badgeVariant": ["isBadge"]
|
|
99
134
|
}
|
|
100
135
|
}
|
|
101
136
|
},
|