@adobe/helix-config 3.13.1 → 4.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +5 -15
  3. package/src/utils.js +0 -36
  4. package/src/schemas/access-admin.schema.cjs +0 -12
  5. package/src/schemas/access-admin.schema.json +0 -57
  6. package/src/schemas/access-site.schema.cjs +0 -12
  7. package/src/schemas/access-site.schema.json +0 -24
  8. package/src/schemas/access.schema.cjs +0 -12
  9. package/src/schemas/access.schema.json +0 -22
  10. package/src/schemas/cdn-prod-akamai.schema.cjs +0 -12
  11. package/src/schemas/cdn-prod-akamai.schema.json +0 -45
  12. package/src/schemas/cdn-prod-cloudflare.schema.cjs +0 -12
  13. package/src/schemas/cdn-prod-cloudflare.schema.json +0 -41
  14. package/src/schemas/cdn-prod-cloudfront.schema.cjs +0 -12
  15. package/src/schemas/cdn-prod-cloudfront.schema.json +0 -41
  16. package/src/schemas/cdn-prod-fastly.schema.cjs +0 -12
  17. package/src/schemas/cdn-prod-fastly.schema.json +0 -40
  18. package/src/schemas/cdn-prod-managed.schema.cjs +0 -12
  19. package/src/schemas/cdn-prod-managed.schema.json +0 -29
  20. package/src/schemas/cdn.schema.cjs +0 -12
  21. package/src/schemas/cdn.schema.json +0 -75
  22. package/src/schemas/code.schema.cjs +0 -12
  23. package/src/schemas/code.schema.json +0 -43
  24. package/src/schemas/common.schema.cjs +0 -12
  25. package/src/schemas/common.schema.json +0 -27
  26. package/src/schemas/content-source-google.schema.cjs +0 -12
  27. package/src/schemas/content-source-google.schema.json +0 -26
  28. package/src/schemas/content-source-markup.schema.cjs +0 -12
  29. package/src/schemas/content-source-markup.schema.json +0 -24
  30. package/src/schemas/content-source-onedrive.schema.cjs +0 -12
  31. package/src/schemas/content-source-onedrive.schema.json +0 -28
  32. package/src/schemas/content.schema.cjs +0 -12
  33. package/src/schemas/content.schema.json +0 -30
  34. package/src/schemas/folders.schema.cjs +0 -12
  35. package/src/schemas/folders.schema.json +0 -13
  36. package/src/schemas/groups.schema.cjs +0 -12
  37. package/src/schemas/groups.schema.json +0 -39
  38. package/src/schemas/headers.schema.cjs +0 -12
  39. package/src/schemas/headers.schema.json +0 -16
  40. package/src/schemas/metadata-source.schema.cjs +0 -12
  41. package/src/schemas/metadata-source.schema.json +0 -16
  42. package/src/schemas/org.schema.cjs +0 -12
  43. package/src/schemas/org.schema.json +0 -26
  44. package/src/schemas/profile.schema.cjs +0 -12
  45. package/src/schemas/profile.schema.json +0 -57
  46. package/src/schemas/profiles.schema.cjs +0 -12
  47. package/src/schemas/profiles.schema.json +0 -28
  48. package/src/schemas/public.schema.cjs +0 -12
  49. package/src/schemas/public.schema.json +0 -8
  50. package/src/schemas/robots.schema.cjs +0 -12
  51. package/src/schemas/robots.schema.json +0 -13
  52. package/src/schemas/sidekick.schema.cjs +0 -12
  53. package/src/schemas/sidekick.schema.json +0 -112
  54. package/src/schemas/site.schema.cjs +0 -12
  55. package/src/schemas/site.schema.json +0 -74
  56. package/src/schemas/sites.schema.cjs +0 -12
  57. package/src/schemas/sites.schema.json +0 -28
  58. package/src/schemas/tokens.schema.cjs +0 -12
  59. package/src/schemas/tokens.schema.json +0 -28
  60. package/src/schemas/user.schema.cjs +0 -12
  61. package/src/schemas/user.schema.json +0 -42
  62. package/src/schemas/users.schema.cjs +0 -12
  63. package/src/schemas/users.schema.json +0 -10
  64. package/src/storage/ValidationError.js +0 -95
  65. package/src/storage/config-store.js +0 -359
  66. package/src/storage/config-validator.js +0 -112
  67. package/src/storage/index.js +0 -14
  68. package/src/storage/status-code-error.js +0 -22
  69. package/src/storage/utils.js +0 -157
  70. package/types/org-config.d.ts +0 -51
  71. package/types/profile-config.d.ts +0 -368
  72. package/types/site-config.d.ts +0 -375
@@ -1,359 +0,0 @@
1
- /*
2
- * Copyright 2023 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
- /* eslint-disable no-param-reassign */
13
- import { isDeepStrictEqual } from 'util';
14
- import { HelixStorage } from '@adobe/helix-shared-storage';
15
- import { StatusCodeError } from './status-code-error.js';
16
- import {
17
- createToken, createUser,
18
- migrateToken,
19
- updateCodeSource,
20
- updateContentSource,
21
- } from './utils.js';
22
- import { validate as validateSchema } from './config-validator.js';
23
- import { deepGetOrCreate, deepPut } from '../utils.js';
24
-
25
- const FRAGMENTS_COMMON = {
26
- content: 'object',
27
- code: 'object',
28
- folders: 'object',
29
- headers: 'object',
30
- metadata: 'object',
31
- sidekick: 'object',
32
- cdn: {
33
- '.': 'object',
34
- prod: 'object',
35
- preview: 'object',
36
- live: 'object',
37
- },
38
- access: {
39
- '.': 'object',
40
- admin: 'object',
41
- preview: 'object',
42
- live: 'object',
43
- },
44
- tokens: {
45
- '.': 'tokens',
46
- '.param': {
47
- name: 'id',
48
- '.': 'token',
49
- },
50
- },
51
- groups: {
52
- '.': 'object',
53
- '.param': {
54
- '.': 'object',
55
- members: 'object',
56
- },
57
- },
58
- public: 'object',
59
- robots: 'object',
60
- };
61
-
62
- const FRAGMENTS = {
63
- sites: {
64
- ...FRAGMENTS_COMMON,
65
- extends: 'object',
66
- },
67
- profiles: FRAGMENTS_COMMON,
68
- org: {
69
- users: {
70
- '.': 'users',
71
- '.param': {
72
- name: 'id',
73
- '.': 'user',
74
- },
75
- },
76
- groups: {
77
- '.': 'object',
78
- '.param': {
79
- '.': 'object',
80
- members: 'object',
81
- },
82
- },
83
- },
84
- };
85
-
86
- export function getFragmentInfo(type, relPath) {
87
- if (!relPath) {
88
- return null;
89
- }
90
- const parts = relPath.split('/');
91
- let fragment = FRAGMENTS[type];
92
- const info = {
93
- relPath,
94
- };
95
- for (const part of parts) {
96
- let next = fragment[part];
97
- if (!next) {
98
- next = fragment['.param'];
99
- if (!next) {
100
- throw new StatusCodeError(400, 'invalid object path.');
101
- }
102
- info[next.name] = part;
103
- }
104
- fragment = next;
105
- }
106
- if (typeof fragment === 'string') {
107
- info.type = fragment;
108
- } else {
109
- info.type = fragment['.'];
110
- }
111
- return info;
112
- }
113
-
114
- /**
115
- * Redact / transform the token config
116
- * @param token
117
- */
118
- function redactToken(token) {
119
- return {
120
- id: token.id,
121
- created: token.created,
122
- };
123
- }
124
-
125
- /**
126
- * Redact / transform the tokens config
127
- * @param tokens
128
- */
129
- function redactTokens(tokens) {
130
- return Object.values(tokens).map(redactToken);
131
- }
132
-
133
- /**
134
- * redact information from the config
135
- * @param {object} config
136
- * @param {object} frag
137
- */
138
- function redact(config, frag) {
139
- if (!config) {
140
- return config;
141
- }
142
- let ret = config;
143
- if (frag?.type === 'tokens') {
144
- ret = redactTokens(config);
145
- }
146
- if (frag?.type === 'token') {
147
- ret = redactToken(config);
148
- }
149
- if (ret.tokens) {
150
- // eslint-disable-next-line no-param-reassign
151
- ret.tokens = redactTokens(ret.tokens);
152
- }
153
- return ret;
154
- }
155
-
156
- /**
157
- * General purpose config store.
158
- */
159
- export class ConfigStore {
160
- /**
161
- * @param {string} org the org id
162
- * @param {string} type store type (org, sites, profiles, secrets, users)
163
- * @param {string} name config name
164
- */
165
- constructor(org, type = 'org', name = '') {
166
- if (!org) {
167
- throw new Error('org required');
168
- }
169
- if (org.includes('/') || type.includes('/') || name.includes('/')) {
170
- throw new Error('orgId, type and name must not contain slashes');
171
- }
172
- this.org = org;
173
- this.type = type;
174
- this.name = name;
175
- this.key = type === 'org'
176
- ? `/orgs/${this.org}/${this.name || 'config'}.json`
177
- : `/orgs/${this.org}/${this.type}/${this.name}.json`;
178
- }
179
-
180
- /**
181
- * Validates the config and throws an error if not valid.
182
- * @param {AdminContext} ctx
183
- * @param {HelixSiteConfig|HelixProfileConfig} data the data of the config to be updated
184
- * @returns {Promise<boolean|undefined>}
185
- */
186
- async validate(ctx, data) {
187
- return validateSchema(data, this.type);
188
- }
189
-
190
- async create(ctx, data, relPath = '') {
191
- if (relPath) {
192
- throw new StatusCodeError(409, 'create not supported on substructures.');
193
- }
194
- const storage = HelixStorage.fromContext(ctx).configBus();
195
- if (await storage.head(this.key)) {
196
- throw new StatusCodeError(409, 'config already exists.');
197
- }
198
- if (data.tokens) {
199
- throw new StatusCodeError(400, 'creating config with tokens not supported yet.');
200
- }
201
- if (this.type === 'org' && data.users) {
202
- throw new StatusCodeError(400, 'creating org config with users is not supported yet.');
203
- }
204
- if (this.type !== 'org') {
205
- updateContentSource(ctx, data.content);
206
- updateCodeSource(ctx, data.code);
207
- }
208
- await this.validate(ctx, data);
209
- await storage.put(this.key, JSON.stringify(data), 'application/json');
210
- await this.purge(ctx, null, data);
211
- }
212
-
213
- async #list(ctx) {
214
- const storage = HelixStorage.fromContext(ctx).configBus();
215
- const key = `orgs/${this.org}/${this.type}/`;
216
- const list = await storage.list(key);
217
- return {
218
- [this.type]: list.map((entry) => {
219
- const siteKey = entry.key;
220
- if (siteKey.endsWith('.json')) {
221
- const name = siteKey.split('/').pop();
222
- return {
223
- path: `/config/${this.org}/${this.type}/${name}`,
224
- name: name.substring(0, name.length - 5),
225
- };
226
- }
227
- return null;
228
- }).filter((entry) => entry),
229
- };
230
- }
231
-
232
- async read(ctx, relPath = '') {
233
- if (this.name === '' && (this.type === 'sites' || this.type === 'profiles')) {
234
- return this.#list(ctx);
235
- }
236
-
237
- const storage = HelixStorage.fromContext(ctx).configBus();
238
- const buf = await storage.get(this.key);
239
- if (!buf) {
240
- return null;
241
- }
242
- let obj = JSON.parse(buf);
243
- const frag = getFragmentInfo(this.type, relPath);
244
- if (frag) {
245
- obj = deepGetOrCreate(obj, frag.relPath);
246
- }
247
- return redact(obj, frag) ?? null;
248
- }
249
-
250
- async update(ctx, data, relPath = '') {
251
- const storage = HelixStorage.fromContext(ctx).configBus();
252
- const buf = await storage.get(this.key);
253
- let old = buf ? JSON.parse(buf) : null;
254
- const frag = getFragmentInfo(this.type, relPath);
255
- let config = data;
256
- // set config to null if empty object
257
- if (isDeepStrictEqual(config, {})) {
258
- config = null;
259
- }
260
-
261
- let ret = null;
262
- if (!config && frag?.type !== 'tokens') {
263
- throw new StatusCodeError(400, 'no config in body.');
264
- }
265
- if (frag) {
266
- if (!old) {
267
- if (this.type === 'profiles' && frag.type === 'tokens') {
268
- old = {};
269
- } else {
270
- throw new StatusCodeError(404, 'config not found.');
271
- }
272
- }
273
- if (relPath === 'code') {
274
- updateCodeSource(ctx, data);
275
- } else if (relPath === 'content') {
276
- updateContentSource(ctx, data);
277
- } else if (frag.type === 'token') {
278
- // don't allow to update individual token
279
- throw new StatusCodeError(400, 'invalid object path.');
280
- } else if (frag.type === 'tokens') {
281
- // TODO: remove support after all helix4 projects are migrated
282
- let token;
283
- if (data.jwt) {
284
- token = await migrateToken(this.org, data.jwt);
285
- } else {
286
- // create new token
287
- token = createToken(this.org);
288
- }
289
-
290
- data = {
291
- ...token,
292
- };
293
- // don't store token value in the config
294
- delete data.value;
295
- relPath = ['tokens', token.id];
296
- frag.type = 'token';
297
- ret = token;
298
- // don't expose hash in return value
299
- delete ret.hash;
300
- } else if (frag.type === 'users') {
301
- const user = createUser();
302
- data = {
303
- ...data,
304
- ...user,
305
- };
306
- relPath = ['users', user.id];
307
- frag.type = 'user';
308
- // todo: define via "schema"
309
- if (!old.users) {
310
- old.users = [];
311
- }
312
- } else if (frag.type === 'user') {
313
- const user = deepGetOrCreate(old, relPath);
314
- if (!user) {
315
- throw new StatusCodeError(404, 'object not found.');
316
- }
317
- if (data.id) {
318
- if (data.id !== user.id) {
319
- throw new StatusCodeError(400, 'object id mismatch.');
320
- }
321
- } else {
322
- data.id = user.id;
323
- }
324
- }
325
- config = deepPut(old, relPath, data);
326
- } else if (this.type !== 'org') {
327
- updateContentSource(ctx, config.content);
328
- updateCodeSource(ctx, config.code);
329
- }
330
- await this.validate(ctx, config);
331
- await storage.put(this.key, JSON.stringify(config), 'application/json');
332
- await this.purge(ctx, buf ? JSON.parse(buf) : null, config);
333
- return ret ?? redact(data, frag);
334
- }
335
-
336
- async remove(ctx, relPath) {
337
- const storage = HelixStorage.fromContext(ctx).configBus();
338
- const buf = await storage.get(this.key);
339
- if (!buf) {
340
- throw new StatusCodeError(404, 'config not found.');
341
- }
342
- const frag = getFragmentInfo(this.type, relPath);
343
- if (frag) {
344
- const data = deepPut(JSON.parse(buf), relPath, null);
345
- await this.validate(ctx, data);
346
- await storage.put(this.key, JSON.stringify(data), 'application/json');
347
- await this.purge(ctx, JSON.parse(buf), data);
348
- return;
349
- }
350
-
351
- await storage.remove(this.key);
352
- await this.purge(ctx, JSON.parse(buf), null);
353
- }
354
-
355
- // eslint-disable-next-line class-methods-use-this,no-unused-vars
356
- async purge(ctx, oldConfig, newConfig) {
357
- // override in subclass
358
- }
359
- }
@@ -1,112 +0,0 @@
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
- import Ajv2019 from 'ajv/dist/2019.js';
13
- import ajvFormats from 'ajv-formats';
14
- import { ValidationError } from './ValidationError.js';
15
-
16
- import accessAdminSchema from '../schemas/access-admin.schema.cjs';
17
- import accessSchema from '../schemas/access.schema.cjs';
18
- import accessSiteSchema from '../schemas/access-site.schema.cjs';
19
- import cdnSchema from '../schemas/cdn.schema.cjs';
20
- import cdnProdFastlySchema from '../schemas/cdn-prod-fastly.schema.cjs';
21
- import cdnProdCloudflareSchema from '../schemas/cdn-prod-cloudflare.schema.cjs';
22
- import cdnProdAkamaiSchema from '../schemas/cdn-prod-akamai.schema.cjs';
23
- import cdnProdManagedSchema from '../schemas/cdn-prod-managed.schema.cjs';
24
- import cdnProdCloudfrontSchema from '../schemas/cdn-prod-cloudfront.schema.cjs';
25
- import commonSchema from '../schemas/common.schema.cjs';
26
- import codeSchema from '../schemas/code.schema.cjs';
27
- import contentSchema from '../schemas/content.schema.cjs';
28
- import foldersSchema from '../schemas/folders.schema.cjs';
29
- import groupsSchema from '../schemas/groups.schema.cjs';
30
- import googleSchema from '../schemas/content-source-google.schema.cjs';
31
- import headersSchema from '../schemas/headers.schema.cjs';
32
- import markupSchema from '../schemas/content-source-markup.schema.cjs';
33
- import metadataSchema from '../schemas/metadata-source.schema.cjs';
34
- import orgSchema from '../schemas/org.schema.cjs';
35
- import onedriveSchema from '../schemas/content-source-onedrive.schema.cjs';
36
- import publicSchema from '../schemas/public.schema.cjs';
37
- import profileSchema from '../schemas/profile.schema.cjs';
38
- import profilesSchema from '../schemas/profiles.schema.cjs';
39
- import robotsSchema from '../schemas/robots.schema.cjs';
40
- import sidekickSchema from '../schemas/sidekick.schema.cjs';
41
- import siteSchema from '../schemas/site.schema.cjs';
42
- import sitesSchema from '../schemas/sites.schema.cjs';
43
- import tokensSchema from '../schemas/tokens.schema.cjs';
44
- import userSchema from '../schemas/user.schema.cjs';
45
- import usersSchema from '../schemas/users.schema.cjs';
46
-
47
- export const SCHEMAS = [
48
- accessSchema,
49
- accessAdminSchema,
50
- accessSiteSchema,
51
- cdnSchema,
52
- commonSchema,
53
- contentSchema,
54
- codeSchema,
55
- foldersSchema,
56
- googleSchema,
57
- groupsSchema,
58
- headersSchema,
59
- markupSchema,
60
- metadataSchema,
61
- orgSchema,
62
- onedriveSchema,
63
- publicSchema,
64
- profileSchema,
65
- profilesSchema,
66
- robotsSchema,
67
- sidekickSchema,
68
- siteSchema,
69
- sitesSchema,
70
- tokensSchema,
71
- userSchema,
72
- usersSchema,
73
- cdnProdFastlySchema,
74
- cdnProdCloudflareSchema,
75
- cdnProdAkamaiSchema,
76
- cdnProdManagedSchema,
77
- cdnProdCloudfrontSchema,
78
- ];
79
-
80
- const SCHEMA_TYPES = {
81
- profiles: profileSchema,
82
- sites: siteSchema,
83
- org: orgSchema,
84
- };
85
-
86
- /**
87
- * Validates the loaded configuration and coerces types and sets defaults
88
- * @param {object} config The configuration to validate
89
- * @param {string} type the config type
90
- * @returns {object} The validated configuration
91
- */
92
- export async function validate(config, type) {
93
- const schema = SCHEMA_TYPES[type];
94
- if (!schema) {
95
- throw new Error(`no such type: ${type}`);
96
- }
97
- const ajv = new Ajv2019({
98
- allErrors: true,
99
- verbose: true,
100
- useDefaults: true,
101
- coerceTypes: 'array',
102
- strict: false,
103
- });
104
- ajvFormats(ajv);
105
-
106
- ajv.addSchema(SCHEMAS);
107
- const res = ajv.validate(schema, config);
108
- if (res) {
109
- return res;
110
- }
111
- throw new ValidationError(ajv.errorsText(), ajv.errors);
112
- }
@@ -1,14 +0,0 @@
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
- export { ConfigStore } from './config-store.js';
13
- export { SCHEMAS } from './config-validator.js';
14
- export * from './ValidationError.js';
@@ -1,22 +0,0 @@
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
- export class StatusCodeError extends Error {
13
- /**
14
- * Creates a new StatusCodeError.
15
- * @param {number} statusCode The status code.
16
- * @param {string} message The error message.
17
- */
18
- constructor(statusCode, message) {
19
- super(message);
20
- this.statusCode = statusCode;
21
- }
22
- }
@@ -1,157 +0,0 @@
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
- /* eslint-disable no-param-reassign */
13
- import crypto from 'crypto';
14
- import { GitUrl } from '@adobe/helix-shared-git';
15
- import { decodeJwt } from 'jose';
16
- import { StatusCodeError } from './status-code-error.js';
17
-
18
- /**
19
- * Update the contentBusId field of the content object based on the source URL.
20
- * @param ctx
21
- * @param content
22
- * @returns {boolean}
23
- */
24
- export function updateContentSource(ctx, content) {
25
- if (!content?.source?.url) {
26
- return false;
27
- }
28
- const url = new URL(content.source.url);
29
- let modified = false;
30
- const sha256 = crypto
31
- .createHash('sha256')
32
- .update(url.toString())
33
- .digest('hex');
34
- const contentBusId = `${sha256.substring(0, 59)}`;
35
- if (contentBusId === content.contentBusId) {
36
- ctx.log.info(`contentBusId is already correct for ${url}: ${contentBusId}`);
37
- } else {
38
- ctx.log.info(`Updating contentBusId for ${url}: ${contentBusId}`);
39
- content.contentBusId = contentBusId;
40
- modified = true;
41
- }
42
-
43
- // calculate type based on source URL
44
- if (!content.source.type) {
45
- if (url.hostname === 'drive.google.com') {
46
- content.source.type = 'google';
47
- content.source.id = url.pathname.split('/').pop();
48
- modified = true;
49
- } else if (url.protocol === 'gdrive:') {
50
- content.source.type = 'google';
51
- content.source.id = url.pathname;
52
- modified = true;
53
- } else if (url.hostname.endsWith('.sharepoint.com') || url.hostname === '1drv.ms') {
54
- content.source.type = 'onedrive';
55
- modified = true;
56
- } else if (url.protocol === 'onedrive:') {
57
- content.source.type = 'onedrive';
58
- content.source.itemId = url.pathname;
59
- modified = true;
60
- }
61
- }
62
- return modified;
63
- }
64
-
65
- /**
66
- * updates the code source information
67
- * @param ctx
68
- * @param code
69
- * @return {boolean} true if the code source was updated
70
- */
71
- export function updateCodeSource(ctx, code) {
72
- let modified = false;
73
- if (code?.source?.url) {
74
- // recompute owner, repo and type
75
- code.source.type = 'github';
76
- const url = new GitUrl(code.source.url);
77
- if (url.owner !== code.owner) {
78
- code.owner = url.owner;
79
- modified = true;
80
- }
81
- if (url.repo !== code.repo) {
82
- code.repo = url.repo;
83
- modified ||= true;
84
- }
85
- } else if (code?.owner && code?.repo) {
86
- code.source = {
87
- type: 'github',
88
- url: `https://github.com/${code.owner}/${code.repo}`,
89
- };
90
- modified = true;
91
- }
92
- return modified;
93
- }
94
-
95
- /**
96
- * Creates a random token and hashes it with the given key
97
- * @param {string} key
98
- * @returns {object} the token
99
- */
100
- export function createToken(key) {
101
- const secret = crypto.randomBytes(32).toString('base64url');
102
- const value = `hlx_${secret}`;
103
- const hash = crypto
104
- .createHmac('sha512', key)
105
- .update(value, 'utf-8')
106
- .digest()
107
- .toString('base64url');
108
- const id = crypto.createHash('sha256').update(hash).digest().toString('base64url');
109
- const created = new Date().toISOString();
110
- return {
111
- id,
112
- value,
113
- hash,
114
- created,
115
- };
116
- }
117
-
118
- export function createUser() {
119
- const id = crypto.randomBytes(16).toString('base64url');
120
- return {
121
- id,
122
- };
123
- }
124
-
125
- /**
126
- * migrates an existing jwt token
127
- * @param key
128
- * @param jwt
129
- * @returns {Promise<object>}
130
- */
131
- export async function migrateToken(key, jwt) {
132
- let payload;
133
- try {
134
- payload = await decodeJwt(jwt);
135
- } catch (e) {
136
- throw new StatusCodeError(400, `unable to migrate jwt: ${e.message}`);
137
- }
138
- const { jti } = payload;
139
- if (!jti) {
140
- throw new StatusCodeError(400, 'unable to migrate jwt: missing jti claim.');
141
- }
142
- const id = jti
143
- .replaceAll('/', '_')
144
- .replaceAll('+', '-');
145
- const hash = crypto
146
- .createHmac('sha512', key)
147
- .update(jwt, 'utf-8')
148
- .digest()
149
- .toString('base64url');
150
- const created = new Date().toISOString();
151
- return {
152
- id,
153
- value: jwt,
154
- hash,
155
- created,
156
- };
157
- }