@adobe/helix-config-storage 1.15.3 → 2.0.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.
- package/CHANGELOG.md +24 -0
- package/package.json +2 -2
- package/src/config-store.js +81 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
# [2.0.0](https://github.com/adobe/helix-config-storage/compare/v1.15.4...v2.0.0) (2025-03-03)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* tetss ([2d6c782](https://github.com/adobe/helix-config-storage/commit/2d6c782e7685416fe329608254a763b48b2c2102))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add access control around features, limits and modifying admin roles ([c3d4a20](https://github.com/adobe/helix-config-storage/commit/c3d4a2005fdc9f580e0149198bdc02ac0af7f17a))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### BREAKING CHANGES
|
|
15
|
+
|
|
16
|
+
* modifying access control needs 'isAdmin' flag to be set
|
|
17
|
+
|
|
18
|
+
## [1.15.4](https://github.com/adobe/helix-config-storage/compare/v1.15.3...v1.15.4) (2025-02-25)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
* **deps:** update dependency jose to v6 ([#95](https://github.com/adobe/helix-config-storage/issues/95)) ([663608d](https://github.com/adobe/helix-config-storage/commit/663608def22b6c92a0a2d213fd0d48e195e4137e))
|
|
24
|
+
|
|
1
25
|
## [1.15.3](https://github.com/adobe/helix-config-storage/compare/v1.15.2...v1.15.3) (2025-02-22)
|
|
2
26
|
|
|
3
27
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/helix-config-storage",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Helix Config Storage",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
@@ -66,6 +66,6 @@
|
|
|
66
66
|
"@adobe/helix-shared-utils": "3.0.2",
|
|
67
67
|
"ajv": "8.17.1",
|
|
68
68
|
"ajv-formats": "3.0.1",
|
|
69
|
-
"jose": "
|
|
69
|
+
"jose": "6.0.6"
|
|
70
70
|
}
|
|
71
71
|
}
|
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);
|