@adobe/helix-config-storage 1.1.1 → 1.2.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 +14 -0
- package/package.json +1 -1
- package/src/config-merge.js +22 -1
- package/src/index.js +1 -0
- package/src/transient-token-store.js +167 -0
- package/src/utils.js +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.2.1](https://github.com/adobe/helix-config-storage/compare/v1.2.0...v1.2.1) (2024-08-22)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* keep some required properties ([#6](https://github.com/adobe/helix-config-storage/issues/6)) ([4ef6d96](https://github.com/adobe/helix-config-storage/commit/4ef6d9678901bfa1be53380efe0582e4b30e03eb))
|
|
7
|
+
|
|
8
|
+
# [1.2.0](https://github.com/adobe/helix-config-storage/compare/v1.1.1...v1.2.0) (2024-08-22)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* add support for transient site tokens ([#5](https://github.com/adobe/helix-config-storage/issues/5)) ([0ce5664](https://github.com/adobe/helix-config-storage/commit/0ce56644ab97d0a2ab22e32cd2c5ce6b423aa138))
|
|
14
|
+
|
|
1
15
|
## [1.1.1](https://github.com/adobe/helix-config-storage/compare/v1.1.0...v1.1.1) (2024-08-19)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
package/src/config-merge.js
CHANGED
|
@@ -40,6 +40,26 @@ const FORCED_TYPES = {
|
|
|
40
40
|
'.cdn.prod.route': 'array',
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* properties that are required by the schema and must not be removed for empty values
|
|
45
|
+
*/
|
|
46
|
+
const REQUIRED_PROPERTIES = {
|
|
47
|
+
'.cdn.prod.host': true,
|
|
48
|
+
'.cdn.prod.type': true,
|
|
49
|
+
'.cdn.prod.endpoint': true,
|
|
50
|
+
'.cdn.prod.clientSecret': true,
|
|
51
|
+
'.cdn.prod.clientToken': true,
|
|
52
|
+
'.cdn.prod.accessToken': true,
|
|
53
|
+
'.cdn.prod.plan': true,
|
|
54
|
+
'.cdn.prod.zoneId': true,
|
|
55
|
+
'.cdn.prod.apiToken': true,
|
|
56
|
+
'.cdn.prod.distributionId': true,
|
|
57
|
+
'.cdn.prod.accessKeyId': true,
|
|
58
|
+
'.cdn.prod.secretAccessKey': true,
|
|
59
|
+
'.cdn.prod.serviceId': true,
|
|
60
|
+
'.cdn.prod.authToken': true,
|
|
61
|
+
};
|
|
62
|
+
|
|
43
63
|
/**
|
|
44
64
|
* Merges the `src` object into the `dst` object.
|
|
45
65
|
* - if the dst property is a scalar, it will be overwritten by the dst property
|
|
@@ -48,13 +68,14 @@ const FORCED_TYPES = {
|
|
|
48
68
|
* - some well known paths are forced to be arrays
|
|
49
69
|
* @param {object} dst
|
|
50
70
|
* @param {object} src
|
|
71
|
+
* @param {string} path the item path
|
|
51
72
|
* @param {boolean} isModifier specifies that the objects are modifiers sheets and need special
|
|
52
73
|
* merging
|
|
53
74
|
*/
|
|
54
75
|
function merge(dst, src, path, isModifier) {
|
|
55
76
|
for (const [key, value] of Object.entries(src)) {
|
|
56
77
|
const itemPath = `${path}.${key}`;
|
|
57
|
-
if (value === '') {
|
|
78
|
+
if (value === '' && !REQUIRED_PROPERTIES[itemPath]) {
|
|
58
79
|
// remove key if value is empty string (keep false, 0)
|
|
59
80
|
delete dst[key];
|
|
60
81
|
} else if (typeof value !== 'object') {
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef Token
|
|
15
|
+
* @property {string} id
|
|
16
|
+
* @property {string} value
|
|
17
|
+
* @property {string} hash
|
|
18
|
+
* @property {Date} created
|
|
19
|
+
* @property {Date} expires
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef SiteTokens
|
|
24
|
+
* @property {Token} preview
|
|
25
|
+
* @property {Token} live
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef SiteTokensData
|
|
30
|
+
* @property {Token[]} preview
|
|
31
|
+
* @property {Token[]} live
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { HelixStorage } from '@adobe/helix-shared-storage';
|
|
35
|
+
import { createToken } from './utils.js';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Default expiry time for transient site tokens (24 hours)
|
|
39
|
+
* @type {number}
|
|
40
|
+
*/
|
|
41
|
+
const DEFAULT_EXPIRY_TIME = 24 * 60 * 60 * 1000;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Store to manage transient site tokens.
|
|
45
|
+
* - the tokens have a fixed expiration time (default 24h) which can be verified on the edge
|
|
46
|
+
* - the transient site tokens are managed and delivered independent of the fixed site tokens
|
|
47
|
+
*/
|
|
48
|
+
export class TransientTokenStore {
|
|
49
|
+
/**
|
|
50
|
+
* Org name
|
|
51
|
+
*/
|
|
52
|
+
org;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Site name
|
|
56
|
+
*/
|
|
57
|
+
site;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* S3/R2 Key for the transient site tokens
|
|
61
|
+
*/
|
|
62
|
+
#key;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* loaded tokens
|
|
66
|
+
*/
|
|
67
|
+
#tokens;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* modified flag
|
|
71
|
+
*/
|
|
72
|
+
#modified;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* consistent "now"
|
|
76
|
+
*/
|
|
77
|
+
#now;
|
|
78
|
+
|
|
79
|
+
constructor(org, site) {
|
|
80
|
+
this.org = org;
|
|
81
|
+
this.site = site;
|
|
82
|
+
this.#key = `orgs/${this.org}/sites/${this.site}/transient-site-tokens.json`;
|
|
83
|
+
this.#now = Date.now();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Returns the current time (mainly used for tests)
|
|
88
|
+
* @returns {number}
|
|
89
|
+
*/
|
|
90
|
+
now() {
|
|
91
|
+
return this.#now;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Loads the transient site tokens from the storage
|
|
96
|
+
* @param ctx
|
|
97
|
+
* @returns {Promise<SiteTokensData>}
|
|
98
|
+
*/
|
|
99
|
+
async #load(ctx) {
|
|
100
|
+
if (!this.#tokens) {
|
|
101
|
+
this.#tokens = {
|
|
102
|
+
preview: [],
|
|
103
|
+
live: [],
|
|
104
|
+
};
|
|
105
|
+
this.#modified = false;
|
|
106
|
+
const storage = HelixStorage.fromContext(ctx).configBus();
|
|
107
|
+
const buf = await storage.get(this.#key);
|
|
108
|
+
if (buf) {
|
|
109
|
+
const data = JSON.parse(buf.toString('utf-8'));
|
|
110
|
+
const isValid = (t) => Date.parse(t.expires) > this.#now;
|
|
111
|
+
this.#tokens.preview = data.tokens.preview.filter(isValid);
|
|
112
|
+
this.#tokens.live = data.tokens.live.filter(isValid);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return this.#tokens;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Saves the transient site tokens to the storage if modified
|
|
120
|
+
* @param ctx
|
|
121
|
+
* @returns {Promise<void>}
|
|
122
|
+
*/
|
|
123
|
+
async #save(ctx) {
|
|
124
|
+
if (this.#modified) {
|
|
125
|
+
const storage = HelixStorage.fromContext(ctx).configBus();
|
|
126
|
+
await storage.put(this.#key, JSON.stringify(this.#tokens));
|
|
127
|
+
this.#modified = false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Returns the transient site token for the given partition. If the token does not exist or is
|
|
133
|
+
* expired, it will be created.
|
|
134
|
+
* @param ctx
|
|
135
|
+
* @param partition
|
|
136
|
+
* @returns {Promise<Token>}
|
|
137
|
+
*/
|
|
138
|
+
async getOrCreateToken(ctx, partition) {
|
|
139
|
+
if (partition !== 'preview' && partition !== 'live') {
|
|
140
|
+
throw new Error(`Invalid partition: ${partition}`);
|
|
141
|
+
}
|
|
142
|
+
const tokens = await this.#load(ctx);
|
|
143
|
+
let token = tokens[partition][0];
|
|
144
|
+
if (!token) {
|
|
145
|
+
token = createToken(this.org, 'hlxtst');
|
|
146
|
+
token.created = new Date(this.#now).toUTCString();
|
|
147
|
+
token.expires = new Date(this.#now + DEFAULT_EXPIRY_TIME).toUTCString();
|
|
148
|
+
tokens[partition].push(token);
|
|
149
|
+
this.#modified = true;
|
|
150
|
+
}
|
|
151
|
+
await this.#save(ctx);
|
|
152
|
+
return token;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Returns the transient site tokens for the given context
|
|
157
|
+
* @param ctx
|
|
158
|
+
* @returns {Promise<SiteTokens>}
|
|
159
|
+
*/
|
|
160
|
+
async getSiteTokens(ctx) {
|
|
161
|
+
const tokens = await this.#load(ctx);
|
|
162
|
+
return {
|
|
163
|
+
preview: tokens.preview[0],
|
|
164
|
+
live: tokens.live[0],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
package/src/utils.js
CHANGED
|
@@ -95,11 +95,12 @@ export function updateCodeSource(ctx, code) {
|
|
|
95
95
|
/**
|
|
96
96
|
* Creates a random token and hashes it with the given key
|
|
97
97
|
* @param {string} key
|
|
98
|
+
* @param {string} [pfx='hlx'] the prefix of the token
|
|
98
99
|
* @returns {object} the token
|
|
99
100
|
*/
|
|
100
|
-
export function createToken(key) {
|
|
101
|
+
export function createToken(key, pfx = 'hlx') {
|
|
101
102
|
const secret = crypto.randomBytes(32).toString('base64url');
|
|
102
|
-
const value =
|
|
103
|
+
const value = `${pfx}_${secret}`;
|
|
103
104
|
const hash = crypto
|
|
104
105
|
.createHmac('sha512', key)
|
|
105
106
|
.update(value, 'utf-8')
|