@adobe/helix-config-storage 1.1.1 → 1.2.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 +7 -0
- package/package.json +1 -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,10 @@
|
|
|
1
|
+
# [1.2.0](https://github.com/adobe/helix-config-storage/compare/v1.1.1...v1.2.0) (2024-08-22)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* 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))
|
|
7
|
+
|
|
1
8
|
## [1.1.1](https://github.com/adobe/helix-config-storage/compare/v1.1.0...v1.1.1) (2024-08-19)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
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')
|