@adobe/helix-config-storage 1.1.0 → 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 CHANGED
@@ -1,3 +1,17 @@
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
+
8
+ ## [1.1.1](https://github.com/adobe/helix-config-storage/compare/v1.1.0...v1.1.1) (2024-08-19)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **deps:** update dependency @adobe/helix-shared-config to v10.6.6 ([#4](https://github.com/adobe/helix-config-storage/issues/4)) ([bace1eb](https://github.com/adobe/helix-config-storage/commit/bace1ebfcbd2e8487acb0c577e78cd5d15add81f))
14
+
1
15
  # [1.1.0](https://github.com/adobe/helix-config-storage/compare/v1.0.0...v1.1.0) (2024-08-16)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-config-storage",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Helix Config Storage",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -60,7 +60,7 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "@adobe/fetch": "4.1.8",
63
- "@adobe/helix-shared-config": "10.6.5",
63
+ "@adobe/helix-shared-config": "10.6.6",
64
64
  "@adobe/helix-shared-git": "3.0.13",
65
65
  "@adobe/helix-shared-storage": "1.0.5",
66
66
  "@adobe/helix-shared-utils": "3.0.2",
package/src/index.js CHANGED
@@ -13,3 +13,4 @@ export { ConfigStore } from './config-store.js';
13
13
  export { SCHEMAS } from './config-validator.js';
14
14
  export * from './ValidationError.js';
15
15
  export * from './config-merge.js';
16
+ export * from './transient-token-store.js';
@@ -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 = `hlx_${secret}`;
103
+ const value = `${pfx}_${secret}`;
103
104
  const hash = crypto
104
105
  .createHmac('sha512', key)
105
106
  .update(value, 'utf-8')