@adobe/helix-config 2.9.0 → 2.10.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.
@@ -0,0 +1 @@
1
+ npx lint-staged
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [2.10.1](https://github.com/adobe/helix-config/compare/v2.10.0...v2.10.1) (2024-04-26)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * consistent return from update ([bce73a1](https://github.com/adobe/helix-config/commit/bce73a1b423397afa751c15bd67ecad164bc05e1))
7
+
8
+ # [2.10.0](https://github.com/adobe/helix-config/compare/v2.9.0...v2.10.0) (2024-04-26)
9
+
10
+
11
+ ### Features
12
+
13
+ * add support to migrate jwt api keys ([#59](https://github.com/adobe/helix-config/issues/59)) ([04adf04](https://github.com/adobe/helix-config/commit/04adf04c975ee38840a56f81aac8a1609959298a))
14
+
1
15
  # [2.9.0](https://github.com/adobe/helix-config/compare/v2.8.0...v2.9.0) (2024-04-25)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-config",
3
- "version": "2.9.0",
3
+ "version": "2.10.1",
4
4
  "description": "Helix Config",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -15,7 +15,7 @@
15
15
  "docs:types": "node ./test/dev/generate-types.js",
16
16
  "semantic-release": "semantic-release",
17
17
  "semantic-release-dry": "semantic-release --dry-run --branches $CI_BRANCH",
18
- "prepare": "husky install"
18
+ "prepare": "husky"
19
19
  },
20
20
  "repository": {
21
21
  "type": "git",
@@ -68,6 +68,7 @@
68
68
  "@smithy/node-http-handler": "2.5.0",
69
69
  "ajv": "8.12.0",
70
70
  "ajv-formats": "3.0.1",
71
+ "jose": "5.2.4",
71
72
  "mime": "4.0.1"
72
73
  }
73
74
  }
@@ -78,7 +78,11 @@ export function getAccessConfig(config, partition) {
78
78
  const cfg = {
79
79
  allow: toArray(access[partition]?.allow ?? access.allow),
80
80
  apiKeyId,
81
- tokenHash: apiKeyId.map((id) => tokens[id]?.hash).filter((hash) => !!hash),
81
+ tokenHash: apiKeyId
82
+ // token ids are always stored in base64url format, but legacy apiKeyIds are not
83
+ .map((jti) => jti.replaceAll('/', '_').replaceAll('+', '-'))
84
+ .map((id) => tokens[id]?.hash)
85
+ .filter((hash) => !!hash),
82
86
  clientCertDN: toArray(access[partition]?.clientCertDN ?? access.clientCertDN),
83
87
  };
84
88
  // if an allow is defined but no apiKeyId, create a fake one so that auth is still
@@ -10,12 +10,14 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
  /* eslint-disable no-param-reassign */
13
+ import { isDeepStrictEqual } from 'util';
13
14
  import { HelixStorage } from './storage.js';
14
15
  import { StatusCodeError } from './status-code-error.js';
15
16
  import {
16
17
  createToken,
17
18
  jsonGet,
18
19
  jsonPut,
20
+ migrateToken,
19
21
  updateCodeSource,
20
22
  updateContentSource,
21
23
  } from './utils.js';
@@ -185,7 +187,15 @@ export class ConfigStore {
185
187
  const old = buf ? JSON.parse(buf) : null;
186
188
  const frag = getFragmentInfo(this.type, relPath);
187
189
  let config = data;
190
+ // set config to null if empty object
191
+ if (isDeepStrictEqual(config, {})) {
192
+ config = null;
193
+ }
194
+
188
195
  let ret = null;
196
+ if (!config && frag?.type !== 'tokens') {
197
+ throw new StatusCodeError(400, 'no config in body.');
198
+ }
189
199
  if (frag) {
190
200
  if (!old) {
191
201
  throw new StatusCodeError(404, 'config not found.');
@@ -198,14 +208,21 @@ export class ConfigStore {
198
208
  // don't allow to update individual token
199
209
  throw new StatusCodeError(400, 'invalid object path.');
200
210
  } else if (frag.type === 'tokens') {
201
- // create new token
202
- const token = createToken(this.org);
211
+ // TODO: remove support after all helix4 projects are migrated
212
+ let token;
213
+ if (data.jwt) {
214
+ token = await migrateToken(this.org, data.jwt);
215
+ } else {
216
+ // create new token
217
+ token = createToken(this.org);
218
+ }
219
+
203
220
  data = {
204
221
  ...token,
205
222
  };
206
223
  // don't store token value in the config
207
224
  delete data.value;
208
- relPath = `tokens/${token.id}`;
225
+ relPath = ['tokens', token.id];
209
226
  frag.type = 'token';
210
227
  ret = token;
211
228
  // don't expose hash in return value
@@ -12,13 +12,15 @@
12
12
  /* eslint-disable no-param-reassign */
13
13
  import crypto from 'crypto';
14
14
  import { GitUrl } from '@adobe/helix-shared-git';
15
+ import { decodeJwt } from 'jose';
16
+ import { StatusCodeError } from './status-code-error.js';
15
17
 
16
18
  export function jsonGet(obj, path) {
17
19
  return path.split('/').reduce((o, p) => o[p], obj);
18
20
  }
19
21
 
20
22
  export function jsonPut(obj, path, value) {
21
- const parts = path.split('/');
23
+ const parts = Array.isArray(path) ? path : path.split('/');
22
24
  const last = parts.pop();
23
25
  const parent = parts.reduce((o, p) => {
24
26
  if (!(p in o)) {
@@ -134,3 +136,37 @@ export function createToken(key) {
134
136
  created,
135
137
  };
136
138
  }
139
+
140
+ /**
141
+ * migrates an existing jwt token
142
+ * @param key
143
+ * @param jwt
144
+ * @returns {Promise<object>}
145
+ */
146
+ export async function migrateToken(key, jwt) {
147
+ let payload;
148
+ try {
149
+ payload = await decodeJwt(jwt);
150
+ } catch (e) {
151
+ throw new StatusCodeError(400, `unable to migrate jwt: ${e.message}`);
152
+ }
153
+ const { jti } = payload;
154
+ if (!jti) {
155
+ throw new StatusCodeError(400, 'unable to migrate jwt: missing jti claim.');
156
+ }
157
+ const id = jti
158
+ .replaceAll('/', '_')
159
+ .replaceAll('+', '-');
160
+ const hash = crypto
161
+ .createHmac('sha512', key)
162
+ .update(jwt, 'utf-8')
163
+ .digest()
164
+ .toString('base64url');
165
+ const created = new Date().toISOString();
166
+ return {
167
+ id,
168
+ value: jwt,
169
+ hash,
170
+ created,
171
+ };
172
+ }