@adobe/helix-html-pipeline 5.4.4 → 6.0.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 +19 -0
- package/package.json +3 -3
- package/src/PipelineState.d.ts +25 -17
- package/src/PipelineState.js +11 -5
- package/src/html-pipe.js +6 -13
- package/src/index.js +0 -1
- package/src/json-pipe.js +5 -20
- package/src/options-pipe.js +10 -20
- package/src/{project-config.d.ts → site-config.d.ts} +33 -53
- package/src/steps/authenticate.js +18 -36
- package/src/steps/folder-mapping.js +1 -1
- package/src/steps/init-config.js +48 -0
- package/src/steps/render.js +1 -1
- package/src/steps/utils.js +1 -1
- package/src/utils/auth.js +6 -6
- package/src/forms-pipe.js +0 -189
- package/src/steps/fetch-config-all.js +0 -95
- package/src/steps/fetch-config.js +0 -79
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## [6.0.1](https://github.com/adobe/helix-html-pipeline/compare/v6.0.0...v6.0.1) (2024-01-11)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* enforce rso ([#488](https://github.com/adobe/helix-html-pipeline/issues/488)) ([4b38e25](https://github.com/adobe/helix-html-pipeline/commit/4b38e2524514a3d64a041d456bf14fe8b2e416cb))
|
|
7
|
+
|
|
8
|
+
# [6.0.0](https://github.com/adobe/helix-html-pipeline/compare/v5.4.4...v6.0.0) (2024-01-10)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* use config service config ([#487](https://github.com/adobe/helix-html-pipeline/issues/487)) ([f2d9770](https://github.com/adobe/helix-html-pipeline/commit/f2d9770577072691b645c6d30c45358d6606e0a1))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### BREAKING CHANGES
|
|
17
|
+
|
|
18
|
+
* state needs config
|
|
19
|
+
|
|
1
20
|
## [5.4.4](https://github.com/adobe/helix-html-pipeline/compare/v5.4.3...v5.4.4) (2023-12-30)
|
|
2
21
|
|
|
3
22
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/helix-html-pipeline",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"description": "Helix HTML Pipeline",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"@semantic-release/changelog": "6.0.3",
|
|
76
76
|
"@semantic-release/git": "10.0.1",
|
|
77
77
|
"@semantic-release/npm": "11.0.2",
|
|
78
|
-
"c8": "
|
|
78
|
+
"c8": "9.0.0",
|
|
79
79
|
"eslint": "8.56.0",
|
|
80
80
|
"eslint-import-resolver-exports": "1.0.0-beta.5",
|
|
81
81
|
"eslint-plugin-header": "3.1.1",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"esmock": "2.6.0",
|
|
84
84
|
"husky": "8.0.3",
|
|
85
85
|
"js-yaml": "4.1.0",
|
|
86
|
-
"jsdom": "23.0
|
|
86
|
+
"jsdom": "23.1.0",
|
|
87
87
|
"junit-report-builder": "3.1.0",
|
|
88
88
|
"lint-staged": "15.2.0",
|
|
89
89
|
"mocha": "10.2.0",
|
package/src/PipelineState.d.ts
CHANGED
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import {PathInfo, S3Loader, FormsMessageDispatcher, PipelineTimer, AuthEnvLoader } from "./index";
|
|
13
13
|
import {PipelineContent} from "./PipelineContent";
|
|
14
|
-
import {
|
|
15
|
-
import {ProjectConfig} from "./project-config";
|
|
14
|
+
import {PipelineSiteConfig} from "./site-config";
|
|
16
15
|
|
|
17
16
|
declare enum PipelineType {
|
|
18
17
|
html = 'html',
|
|
@@ -37,14 +36,15 @@ declare interface PipelineOptions {
|
|
|
37
36
|
s3Loader: S3Loader;
|
|
38
37
|
messageDispatcher: FormsMessageDispatcher;
|
|
39
38
|
authEnvLoader: AuthEnvLoader;
|
|
39
|
+
config: PipelineSiteConfig;
|
|
40
40
|
fetch: Fetch;
|
|
41
|
-
owner: string;
|
|
42
|
-
repo: string;
|
|
43
41
|
ref: string;
|
|
44
42
|
partition: string;
|
|
45
43
|
path: string;
|
|
46
44
|
timer: PipelineTimer;
|
|
47
45
|
env: object;
|
|
46
|
+
site: string;
|
|
47
|
+
org: string;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
declare class PipelineState {
|
|
@@ -72,6 +72,16 @@ declare class PipelineState {
|
|
|
72
72
|
*/
|
|
73
73
|
partition: string;
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* project site
|
|
77
|
+
*/
|
|
78
|
+
site: string;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* project org
|
|
82
|
+
*/
|
|
83
|
+
org: string;
|
|
84
|
+
|
|
75
85
|
/**
|
|
76
86
|
* Repository owner
|
|
77
87
|
*/
|
|
@@ -88,24 +98,19 @@ declare class PipelineState {
|
|
|
88
98
|
ref: string;
|
|
89
99
|
|
|
90
100
|
/**
|
|
91
|
-
*
|
|
92
|
-
*/
|
|
93
|
-
helixConfig?: object;
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* the /.helix/config.json in object form
|
|
101
|
+
* the site config loaded from config-service
|
|
97
102
|
*/
|
|
98
|
-
config
|
|
103
|
+
config: PipelineSiteConfig;
|
|
99
104
|
|
|
100
105
|
/**
|
|
101
106
|
* the metadata.json in modifier form.
|
|
102
107
|
*/
|
|
103
|
-
metadata
|
|
108
|
+
metadata: Modifiers;
|
|
104
109
|
|
|
105
110
|
/**
|
|
106
111
|
* the headers.json in modifier form.
|
|
107
112
|
*/
|
|
108
|
-
headers
|
|
113
|
+
headers: Modifiers;
|
|
109
114
|
|
|
110
115
|
/**
|
|
111
116
|
* optional timer that is used to measure the timing
|
|
@@ -122,16 +127,19 @@ declare class PipelineState {
|
|
|
122
127
|
*/
|
|
123
128
|
authInfo?: AuthInfo;
|
|
124
129
|
|
|
130
|
+
/**
|
|
131
|
+
* the production host
|
|
132
|
+
*/
|
|
133
|
+
prodHost: string;
|
|
134
|
+
|
|
125
135
|
/**
|
|
126
136
|
* the custom preview host if configured via config.cdn.preview.host
|
|
127
|
-
* this is initialized after the config is loaded.
|
|
128
137
|
*/
|
|
129
|
-
previewHost
|
|
138
|
+
previewHost: string;
|
|
130
139
|
|
|
131
140
|
/**
|
|
132
141
|
* the custom live host if configured via config.cdn.live.host
|
|
133
|
-
* this is initialized after the config is loaded.
|
|
134
142
|
*/
|
|
135
|
-
liveHost
|
|
143
|
+
liveHost: string;
|
|
136
144
|
}
|
|
137
145
|
|
package/src/PipelineState.js
CHANGED
|
@@ -28,23 +28,29 @@ export class PipelineState {
|
|
|
28
28
|
log: opts.log ?? console,
|
|
29
29
|
env: opts.env,
|
|
30
30
|
info: getPathInfo(opts.path),
|
|
31
|
+
config: opts.config,
|
|
31
32
|
content: new PipelineContent(),
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
contentBusId: opts.config.contentBusId,
|
|
34
|
+
site: opts.site,
|
|
35
|
+
org: opts.org,
|
|
36
|
+
owner: opts.config.owner,
|
|
37
|
+
repo: opts.config.repo,
|
|
34
38
|
ref: opts.ref,
|
|
35
39
|
partition: opts.partition,
|
|
36
|
-
helixConfig: undefined,
|
|
37
40
|
metadata: Modifiers.EMPTY,
|
|
38
41
|
headers: Modifiers.EMPTY,
|
|
39
|
-
config: {},
|
|
40
42
|
s3Loader: opts.s3Loader,
|
|
41
43
|
messageDispatcher: opts.messageDispatcher,
|
|
42
44
|
authEnvLoader: opts.authEnvLoader ?? { load: () => {} },
|
|
43
45
|
fetch: opts.fetch,
|
|
44
46
|
timer: opts.timer,
|
|
45
47
|
type: 'html',
|
|
46
|
-
authInfo: undefined,
|
|
47
48
|
});
|
|
49
|
+
for (const prop of ['org', 'site', 'contentBusId', 'repo', 'owner', 'ref', 'partition']) {
|
|
50
|
+
if (!this[prop]) {
|
|
51
|
+
throw new Error(`${prop} required`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
// eslint-disable-next-line class-methods-use-this
|
package/src/html-pipe.js
CHANGED
|
@@ -10,15 +10,14 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
import { cleanupHeaderValue } from '@adobe/helix-shared-utils';
|
|
13
|
-
import { authenticate
|
|
13
|
+
import { authenticate } from './steps/authenticate.js';
|
|
14
14
|
import addHeadingIds from './steps/add-heading-ids.js';
|
|
15
15
|
import createPageBlocks from './steps/create-page-blocks.js';
|
|
16
16
|
import createPictures from './steps/create-pictures.js';
|
|
17
17
|
import extractMetaData from './steps/extract-metadata.js';
|
|
18
|
-
import fetchConfig from './steps/fetch-config.js';
|
|
19
18
|
import fetchContent from './steps/fetch-content.js';
|
|
20
19
|
import fetch404 from './steps/fetch-404.js';
|
|
21
|
-
import
|
|
20
|
+
import initConfig from './steps/init-config.js';
|
|
22
21
|
import fixSections from './steps/fix-sections.js';
|
|
23
22
|
import folderMapping from './steps/folder-mapping.js';
|
|
24
23
|
import getMetadata from './steps/get-metadata.js';
|
|
@@ -112,14 +111,8 @@ export async function htmlPipe(state, req) {
|
|
|
112
111
|
}
|
|
113
112
|
}
|
|
114
113
|
|
|
115
|
-
try {
|
|
116
|
-
state
|
|
117
|
-
await fetchConfig(state, req, res);
|
|
118
|
-
if (!state.contentBusId) {
|
|
119
|
-
res.status = 400;
|
|
120
|
-
res.headers.set('x-error', 'contentBusId missing');
|
|
121
|
-
return res;
|
|
122
|
-
}
|
|
114
|
+
try {
|
|
115
|
+
await initConfig(state, req, res);
|
|
123
116
|
|
|
124
117
|
// force code-bus for .html files
|
|
125
118
|
if (state.info.originalExtension === '.html' && state.info.selector !== 'plain') {
|
|
@@ -147,12 +140,11 @@ export async function htmlPipe(state, req) {
|
|
|
147
140
|
// load metadata and content in parallel
|
|
148
141
|
state.timer?.update('metadata-fetch');
|
|
149
142
|
await Promise.all([
|
|
150
|
-
fetchConfigAll(state, req, res),
|
|
151
143
|
contentPromise,
|
|
152
144
|
fetchMappedMetadata(state),
|
|
153
145
|
]);
|
|
154
146
|
|
|
155
|
-
await requireProject(state, req, res);
|
|
147
|
+
// await requireProject(state, req, res);
|
|
156
148
|
if (res.error !== 401) {
|
|
157
149
|
await authenticate(state, req, res);
|
|
158
150
|
}
|
|
@@ -202,6 +194,7 @@ export async function htmlPipe(state, req) {
|
|
|
202
194
|
res.status = 500;
|
|
203
195
|
}
|
|
204
196
|
|
|
197
|
+
/* c8 ignore next */
|
|
205
198
|
const level = res.status >= 500 ? 'error' : 'info';
|
|
206
199
|
log[level](`pipeline status: ${res.status} ${res.error}`, e);
|
|
207
200
|
res.headers.set('x-error', cleanupHeaderValue(res.error));
|
package/src/index.js
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
export * from './html-pipe.js';
|
|
13
13
|
export * from './json-pipe.js';
|
|
14
14
|
export * from './options-pipe.js';
|
|
15
|
-
export * from './forms-pipe.js';
|
|
16
15
|
export * from './PipelineContent.js';
|
|
17
16
|
export * from './PipelineRequest.js';
|
|
18
17
|
export * from './PipelineResponse.js';
|
package/src/json-pipe.js
CHANGED
|
@@ -10,13 +10,12 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
import { cleanupHeaderValue, computeSurrogateKey } from '@adobe/helix-shared-utils';
|
|
13
|
-
import
|
|
13
|
+
import initConfig from './steps/init-config.js';
|
|
14
14
|
import setCustomResponseHeaders from './steps/set-custom-response-headers.js';
|
|
15
15
|
import { PipelineResponse } from './PipelineResponse.js';
|
|
16
16
|
import jsonFilter from './utils/json-filter.js';
|
|
17
17
|
import { extractLastModified, updateLastModified } from './utils/last-modified.js';
|
|
18
18
|
import { authenticate } from './steps/authenticate.js';
|
|
19
|
-
import fetchConfig from './steps/fetch-config.js';
|
|
20
19
|
import { getPathInfo } from './utils/path.js';
|
|
21
20
|
import { PipelineStatusError } from './PipelineStatusError.js';
|
|
22
21
|
|
|
@@ -28,7 +27,7 @@ import { PipelineStatusError } from './PipelineStatusError.js';
|
|
|
28
27
|
* @param {PipelineState} state
|
|
29
28
|
*/
|
|
30
29
|
export default function folderMapping(state) {
|
|
31
|
-
const folders = state.
|
|
30
|
+
const { folders } = state.config;
|
|
32
31
|
if (!folders) {
|
|
33
32
|
return;
|
|
34
33
|
}
|
|
@@ -121,39 +120,25 @@ export async function jsonPipe(state, req) {
|
|
|
121
120
|
}
|
|
122
121
|
|
|
123
122
|
try {
|
|
124
|
-
// fetch config and apply the folder mapping
|
|
125
|
-
await fetchConfig(state, req);
|
|
126
|
-
if (!state.contentBusId) {
|
|
127
|
-
return new PipelineResponse('', {
|
|
128
|
-
status: 400,
|
|
129
|
-
headers: {
|
|
130
|
-
'x-error': 'contentBusId missing',
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
|
|
135
123
|
/** @type PipelineResponse */
|
|
136
124
|
const res = new PipelineResponse('', {
|
|
137
125
|
headers: {
|
|
138
126
|
'content-type': 'application/json',
|
|
139
127
|
},
|
|
140
128
|
});
|
|
129
|
+
await initConfig(state, req, res);
|
|
141
130
|
|
|
142
131
|
// apply the folder mapping if the current resource doesn't exist
|
|
143
132
|
state.timer?.update('json-fetch');
|
|
144
|
-
|
|
133
|
+
await fetchJsonContent(state, req, res);
|
|
145
134
|
if (res.status === 404) {
|
|
146
135
|
folderMapping(state);
|
|
147
136
|
if (state.info.unmappedPath) {
|
|
148
|
-
|
|
137
|
+
await fetchJsonContent(state, req, res);
|
|
149
138
|
}
|
|
150
139
|
}
|
|
151
140
|
|
|
152
141
|
state.timer?.update('json-metadata-fetch');
|
|
153
|
-
await Promise.all([
|
|
154
|
-
fetchConfigAll(state, req, res),
|
|
155
|
-
contentPromise,
|
|
156
|
-
]);
|
|
157
142
|
|
|
158
143
|
await authenticate(state, req, res);
|
|
159
144
|
|
package/src/options-pipe.js
CHANGED
|
@@ -13,9 +13,8 @@ import { cleanupHeaderValue } from '@adobe/helix-shared-utils';
|
|
|
13
13
|
// eslint-disable-next-line import/no-unresolved
|
|
14
14
|
import cryptoImpl from '#crypto';
|
|
15
15
|
import { PipelineResponse } from './PipelineResponse.js';
|
|
16
|
-
import
|
|
16
|
+
import initConfig from './steps/init-config.js';
|
|
17
17
|
import setCustomResponseHeaders from './steps/set-custom-response-headers.js';
|
|
18
|
-
import fetchConfig from './steps/fetch-config.js';
|
|
19
18
|
import { PipelineStatusError } from './PipelineStatusError.js';
|
|
20
19
|
import { getOriginalHost } from './steps/utils.js';
|
|
21
20
|
|
|
@@ -41,8 +40,8 @@ function hashMe(domain, domainkeys) {
|
|
|
41
40
|
* the x-forwarded-host and the domainkey. If no domainkey has been set in .helix/config
|
|
42
41
|
* then the `slack` channel will be used instead.
|
|
43
42
|
* @param {object} state current pipeline state
|
|
44
|
-
* @param {
|
|
45
|
-
* @param {
|
|
43
|
+
* @param {PipelineRequest} request HTTP request
|
|
44
|
+
* @param {PipelineResponse} response HTTP response
|
|
46
45
|
* @returns {void}
|
|
47
46
|
*/
|
|
48
47
|
function setDomainkeyHeader(state, request, response) {
|
|
@@ -53,11 +52,11 @@ function setDomainkeyHeader(state, request, response) {
|
|
|
53
52
|
// get x-forwarded-host
|
|
54
53
|
const originalHost = getOriginalHost(request.headers);
|
|
55
54
|
// get liveHost
|
|
56
|
-
const {
|
|
55
|
+
const { prodHost } = state;
|
|
57
56
|
|
|
58
|
-
if (originalHost !==
|
|
57
|
+
if (originalHost !== prodHost) {
|
|
59
58
|
// these have to match
|
|
60
|
-
state.log.debug(`x-forwarded-host: ${originalHost} does not match prod host: ${
|
|
59
|
+
state.log.debug(`x-forwarded-host: ${originalHost} does not match prod host: ${prodHost}`);
|
|
61
60
|
return;
|
|
62
61
|
}
|
|
63
62
|
// get domainkey from config
|
|
@@ -83,16 +82,6 @@ function setDomainkeyHeader(state, request, response) {
|
|
|
83
82
|
*/
|
|
84
83
|
export async function optionsPipe(state, request) {
|
|
85
84
|
try {
|
|
86
|
-
await fetchConfig(state, request);
|
|
87
|
-
if (!state.contentBusId) {
|
|
88
|
-
return new PipelineResponse('', {
|
|
89
|
-
status: 400,
|
|
90
|
-
headers: {
|
|
91
|
-
'x-error': 'contentBusId missing',
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
85
|
// todo: improve
|
|
97
86
|
const res = new PipelineResponse('', {
|
|
98
87
|
status: 204,
|
|
@@ -103,15 +92,16 @@ export async function optionsPipe(state, request) {
|
|
|
103
92
|
'access-control-allow-headers': 'content-type',
|
|
104
93
|
},
|
|
105
94
|
});
|
|
106
|
-
|
|
107
|
-
|
|
95
|
+
initConfig(state, request, res);
|
|
96
|
+
setCustomResponseHeaders(state, request, res);
|
|
108
97
|
setDomainkeyHeader(state, request, res);
|
|
109
|
-
|
|
110
98
|
return res;
|
|
111
99
|
} catch (e) {
|
|
112
100
|
const res = new PipelineResponse('', {
|
|
101
|
+
/* c8 ignore next */
|
|
113
102
|
status: e instanceof PipelineStatusError ? e.code : 500,
|
|
114
103
|
});
|
|
104
|
+
/* c8 ignore next */
|
|
115
105
|
const level = res.status >= 500 ? 'error' : 'info';
|
|
116
106
|
state.log[level](`pipeline status: ${res.status} ${e.message}`);
|
|
117
107
|
res.headers.set('x-error', cleanupHeaderValue(e.message));
|
|
@@ -9,38 +9,34 @@
|
|
|
9
9
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
+
import { Modifiers } from './utils/modifiers';
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* configuration blueprint repository in the owner/repo format.
|
|
34
|
-
*/
|
|
35
|
-
blueprint?: string;
|
|
36
|
-
/**
|
|
37
|
-
* the slack teamId/channelID(s) where the slack bot is used.
|
|
38
|
-
*/
|
|
39
|
-
slack?: string | string[];
|
|
14
|
+
/**
|
|
15
|
+
* Resolved site config from config service. Passed via pipeline state
|
|
16
|
+
* @todo: generate from schema in config service
|
|
17
|
+
*/
|
|
18
|
+
export interface PipelineSiteConfig {
|
|
19
|
+
contentBusId: string;
|
|
20
|
+
owner: string;
|
|
21
|
+
repo: string;
|
|
22
|
+
ref: string;
|
|
23
|
+
org: string;
|
|
24
|
+
site: string;
|
|
25
|
+
headers: ModifiersSheet;
|
|
26
|
+
metadata: {
|
|
27
|
+
preview: ModifiersSheet;
|
|
28
|
+
live: ModifiersSheet;
|
|
29
|
+
}
|
|
30
|
+
head: {
|
|
31
|
+
html: string;
|
|
32
|
+
}
|
|
40
33
|
cdn?: ProjectCDNConfig;
|
|
41
34
|
access?: SiteAccessConfig;
|
|
42
|
-
admin?: AdminConfig;
|
|
43
35
|
}
|
|
36
|
+
export interface ModifiersSheet {
|
|
37
|
+
data: Modifiers;
|
|
38
|
+
}
|
|
39
|
+
|
|
44
40
|
/**
|
|
45
41
|
* The CDN config
|
|
46
42
|
*/
|
|
@@ -123,35 +119,19 @@ export interface ManagedConfig {
|
|
|
123
119
|
route: string | string[];
|
|
124
120
|
}
|
|
125
121
|
export interface SiteAccessConfig {
|
|
122
|
+
preview?: AccessConfig;
|
|
123
|
+
live?: AccessConfig;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Access config specific to preview content
|
|
127
|
+
*/
|
|
128
|
+
export interface AccessConfig {
|
|
126
129
|
/**
|
|
127
130
|
* The email glob of the users that are allowed.
|
|
128
131
|
*/
|
|
129
|
-
allow: string
|
|
130
|
-
/**
|
|
131
|
-
* the id of the API key(s). this is used to validate the API KEYS and allows to invalidate them.
|
|
132
|
-
*/
|
|
133
|
-
apiKeyId?: string | string[];
|
|
134
|
-
require?: {
|
|
135
|
-
/**
|
|
136
|
-
* The list of owner/repo pointers to projects that are allowed to use this content.
|
|
137
|
-
*/
|
|
138
|
-
repository: string | string[];
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
export interface AdminConfig {
|
|
142
|
-
role?: Role;
|
|
132
|
+
allow: string[];
|
|
143
133
|
/**
|
|
144
134
|
* the id of the API key(s). this is used to validate the API KEYS and allows to invalidate them.
|
|
145
135
|
*/
|
|
146
|
-
apiKeyId
|
|
147
|
-
}
|
|
148
|
-
export interface Role {
|
|
149
|
-
/**
|
|
150
|
-
* The email glob of the users with author role.
|
|
151
|
-
*/
|
|
152
|
-
author?: string | string[];
|
|
153
|
-
/**
|
|
154
|
-
* The email glob of the users with publish role.
|
|
155
|
-
*/
|
|
156
|
-
publish?: string | string[];
|
|
136
|
+
apiKeyId: string[];
|
|
157
137
|
}
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
import { getAuthInfo, makeAuthError } from '../utils/auth.js';
|
|
13
|
-
import { toArray } from './utils.js';
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Checks if the given email is allowed.
|
|
@@ -28,26 +27,6 @@ export function isAllowed(email = '', allows = []) {
|
|
|
28
27
|
return allows.findIndex((a) => a === email || a === wild) >= 0;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
/**
|
|
32
|
-
* Returns the normalized access configuration for the current partition.
|
|
33
|
-
* @param state
|
|
34
|
-
* @return {{}}
|
|
35
|
-
*/
|
|
36
|
-
export function getAccessConfig(state) {
|
|
37
|
-
const { access } = state.config;
|
|
38
|
-
if (!access) {
|
|
39
|
-
return {
|
|
40
|
-
allow: [],
|
|
41
|
-
apiKeyId: [],
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
const { partition } = state;
|
|
45
|
-
return {
|
|
46
|
-
allow: toArray(access[partition]?.allow ?? access.allow),
|
|
47
|
-
apiKeyId: toArray(access[partition]?.apiKeyId ?? access.apiKeyId),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
30
|
/**
|
|
52
31
|
* Handles authentication
|
|
53
32
|
* @type PipelineStep
|
|
@@ -65,7 +44,10 @@ export async function authenticate(state, req, res) {
|
|
|
65
44
|
}
|
|
66
45
|
|
|
67
46
|
// get partition relative auth info
|
|
68
|
-
const access =
|
|
47
|
+
const access = state.config.access?.[state.partition] || {
|
|
48
|
+
allow: [],
|
|
49
|
+
apiKeyId: [],
|
|
50
|
+
};
|
|
69
51
|
|
|
70
52
|
// if not protected, do nothing
|
|
71
53
|
if (!access.allow.length) {
|
|
@@ -138,17 +120,17 @@ export function isOwnerRepoAllowed(owner, repo, allows = []) {
|
|
|
138
120
|
* @param {PipelineResponse} res
|
|
139
121
|
* @returns {Promise<void>}
|
|
140
122
|
*/
|
|
141
|
-
export async function requireProject(state, req, res) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
123
|
+
// export async function requireProject(state, req, res) {
|
|
124
|
+
// // if not restricted, do nothing
|
|
125
|
+
// const ownerRepo = state.config?.access?.require?.repository;
|
|
126
|
+
// if (!ownerRepo) {
|
|
127
|
+
// return;
|
|
128
|
+
// }
|
|
129
|
+
// const ownerRepos = Array.isArray(ownerRepo) ? ownerRepo : [ownerRepo];
|
|
130
|
+
// const { log, owner, repo } = state;
|
|
131
|
+
// if (!isOwnerRepoAllowed(owner, repo, ownerRepos)) {
|
|
132
|
+
// log.warn(`${owner}/${repo} not allowed for ${ownerRepos}`);
|
|
133
|
+
// res.status = 403;
|
|
134
|
+
// res.error = 'forbidden.';
|
|
135
|
+
// }
|
|
136
|
+
// }
|
|
@@ -40,7 +40,7 @@ export function mapPath(folders, path) {
|
|
|
40
40
|
* @param {PipelineState} state
|
|
41
41
|
*/
|
|
42
42
|
export default function folderMapping(state) {
|
|
43
|
-
const folders = state.
|
|
43
|
+
const { folders } = state.config;
|
|
44
44
|
if (!folders) {
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2021 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 { Modifiers } from '../utils/modifiers.js';
|
|
13
|
+
import { getOriginalHost } from './utils.js';
|
|
14
|
+
import { updateLastModified } from '../utils/last-modified.js';
|
|
15
|
+
|
|
16
|
+
function replaceParams(str, info) {
|
|
17
|
+
if (!str) {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
return str
|
|
21
|
+
.replaceAll('$owner', info.owner)
|
|
22
|
+
.replaceAll('$org', info.org)
|
|
23
|
+
.replaceAll('$site', info.site)
|
|
24
|
+
.replaceAll('$repo', info.repo)
|
|
25
|
+
.replaceAll('$ref', info.ref);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initializes the pipeline state with the config from the config service
|
|
30
|
+
* (passed via the `config` parameter during state construction).
|
|
31
|
+
*
|
|
32
|
+
* @type PipelineStep
|
|
33
|
+
* @param {PipelineState} state
|
|
34
|
+
* @param {PipelineRequest} req
|
|
35
|
+
* @param {PipelineResponse} res
|
|
36
|
+
* @returns {Promise<void>}
|
|
37
|
+
*/
|
|
38
|
+
export default function initConfig(state, req, res) {
|
|
39
|
+
const { config, partition } = state;
|
|
40
|
+
state.metadata = new Modifiers(config.metadata?.[partition]?.data || {});
|
|
41
|
+
state.headers = new Modifiers(config.headers || {});
|
|
42
|
+
|
|
43
|
+
// set custom preview and live hosts
|
|
44
|
+
state.previewHost = replaceParams(config.cdn?.preview?.host, state);
|
|
45
|
+
state.liveHost = replaceParams(config.cdn?.live?.host, state);
|
|
46
|
+
state.prodHost = config.cdn?.prod?.host || getOriginalHost(req.headers);
|
|
47
|
+
updateLastModified(state, res, state.config.lastModified);
|
|
48
|
+
}
|
package/src/steps/render.js
CHANGED
|
@@ -72,7 +72,7 @@ export default async function render(state, req, res) {
|
|
|
72
72
|
appendElement($head, createElement('link', 'rel', 'alternate', 'type', 'application/xml+atom', 'href', meta.feed, 'title', `${meta.title} feed`));
|
|
73
73
|
|
|
74
74
|
// inject head.html
|
|
75
|
-
const headHtml = state.
|
|
75
|
+
const headHtml = state.config?.head?.html;
|
|
76
76
|
if (headHtml) {
|
|
77
77
|
const $headHtml = await unified()
|
|
78
78
|
.use(rehypeParse, { fragment: true })
|
package/src/steps/utils.js
CHANGED
package/src/utils/auth.js
CHANGED
|
@@ -99,7 +99,7 @@ export async function decodeIdToken(state, idToken, lenient = false) {
|
|
|
99
99
|
|
|
100
100
|
/**
|
|
101
101
|
* Returns the host of the request; falls back to the configured `host`.
|
|
102
|
-
* Note that this is different from the `
|
|
102
|
+
* Note that this is different from the `state.prodHost` calculation in `init-config`,
|
|
103
103
|
* as this prefers the xfh over the config.
|
|
104
104
|
*
|
|
105
105
|
* @param {PipelineState} state
|
|
@@ -115,7 +115,7 @@ function getRequestHostAndProto(state, req) {
|
|
|
115
115
|
host = host.split(',')[0].trim();
|
|
116
116
|
}
|
|
117
117
|
if (!host) {
|
|
118
|
-
host = state.
|
|
118
|
+
host = state.prodHost;
|
|
119
119
|
}
|
|
120
120
|
// fastly overrides the x-forwarded-proto, so we use x-forwarded-scheme
|
|
121
121
|
const proto = req.headers.get('x-forwarded-scheme') || req.headers.get('x-forwarded-proto') || 'https';
|
|
@@ -239,8 +239,8 @@ export class AuthInfo {
|
|
|
239
239
|
const url = new URL(idp.discovery.authorization_endpoint);
|
|
240
240
|
|
|
241
241
|
const tokenState = await signJWT(state, new SignJWT({
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
org: state.org,
|
|
243
|
+
site: state.site,
|
|
244
244
|
// this is our own login redirect, i.e. the current document
|
|
245
245
|
requestPath: state.info.path,
|
|
246
246
|
requestHost: host,
|
|
@@ -400,8 +400,8 @@ export async function initAuthRoute(state, req, res) {
|
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
// fixup pipeline state
|
|
403
|
-
state.
|
|
404
|
-
state.
|
|
403
|
+
state.org = req.params.state.org;
|
|
404
|
+
state.site = req.params.state.site;
|
|
405
405
|
state.ref = 'main';
|
|
406
406
|
state.partition = 'preview';
|
|
407
407
|
state.info.path = '/.auth';
|
package/src/forms-pipe.js
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2022 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 { cleanupHeaderValue } from '@adobe/helix-shared-utils';
|
|
13
|
-
import { PipelineResponse } from './PipelineResponse.js';
|
|
14
|
-
import fetchConfigAll from './steps/fetch-config-all.js';
|
|
15
|
-
import setCustomResponseHeaders from './steps/set-custom-response-headers.js';
|
|
16
|
-
import { authenticate } from './steps/authenticate.js';
|
|
17
|
-
import fetchConfig from './steps/fetch-config.js';
|
|
18
|
-
import validateCaptcha from './steps/validate-captcha.js';
|
|
19
|
-
|
|
20
|
-
function error(log, msg, status, response) {
|
|
21
|
-
log.error(msg);
|
|
22
|
-
response.status = status;
|
|
23
|
-
response.headers.set('x-error', cleanupHeaderValue(msg));
|
|
24
|
-
return response;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Converts URLSearchParams to an object
|
|
29
|
-
* @param {URLSearchParams} searchParams the search params object
|
|
30
|
-
* @returns {Object} The converted object
|
|
31
|
-
*/
|
|
32
|
-
function searchParamsToObject(searchParams) {
|
|
33
|
-
const result = Object.create(null);
|
|
34
|
-
|
|
35
|
-
for (const key of searchParams.keys()) {
|
|
36
|
-
// get all values association with the key
|
|
37
|
-
const values = searchParams.getAll(key);
|
|
38
|
-
|
|
39
|
-
// if multiple values, convert to array
|
|
40
|
-
result[key] = (values.length === 1) ? values[0] : values;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return result;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Extracts and parses the body data from the request
|
|
48
|
-
* @param {PipelineRequest} request the request object (see fetch api)
|
|
49
|
-
* @returns {Object} The body data
|
|
50
|
-
* @throws {Error} If an error occurs parsing the body
|
|
51
|
-
*/
|
|
52
|
-
export async function extractBodyData(request) {
|
|
53
|
-
let { body } = request;
|
|
54
|
-
if (!body) {
|
|
55
|
-
throw Error('missing body');
|
|
56
|
-
}
|
|
57
|
-
const type = request.headers.get('content-type');
|
|
58
|
-
|
|
59
|
-
// if content is form-urlencoded we need place the object in the body
|
|
60
|
-
// in a "data" property in the body as this is what forms-service expects.
|
|
61
|
-
if (/^application\/x-www-form-urlencoded/.test(type)) {
|
|
62
|
-
// did they pass an object in the body when a form-urlencoded body was expected?
|
|
63
|
-
if (body === '[object Object]') {
|
|
64
|
-
throw Error('invalid form-urlencoded body');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
body = {
|
|
68
|
-
data: searchParamsToObject(new URLSearchParams(body)),
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// else treat the body as json
|
|
72
|
-
} else if (/^application\/json/.test(type)) {
|
|
73
|
-
body = JSON.parse(body);
|
|
74
|
-
// verify the body data is as expected
|
|
75
|
-
if (!body.data) {
|
|
76
|
-
throw Error('missing body.data');
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
throw Error(`post body content-type not supported: ${type}`);
|
|
80
|
-
}
|
|
81
|
-
return body;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Handle a pipeline POST request.
|
|
86
|
-
* At this point POST's only apply to json files that are backed by a workbook.
|
|
87
|
-
* @param {PipelineState} state pipeline options
|
|
88
|
-
* @param {PipelineRequest} req
|
|
89
|
-
* @returns {Promise<PipelineResponse>} a response
|
|
90
|
-
*/
|
|
91
|
-
export async function formsPipe(state, req) {
|
|
92
|
-
const { log } = state;
|
|
93
|
-
state.type = 'form';
|
|
94
|
-
|
|
95
|
-
// todo: improve
|
|
96
|
-
const res = new PipelineResponse('', {
|
|
97
|
-
headers: {
|
|
98
|
-
'content-type': 'text/plain; charset=utf-8',
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
try {
|
|
102
|
-
await fetchConfig(state, req, res);
|
|
103
|
-
} catch (e) {
|
|
104
|
-
// ignore
|
|
105
|
-
}
|
|
106
|
-
if (!state.contentBusId) {
|
|
107
|
-
res.status = 400;
|
|
108
|
-
res.headers.set('x-error', 'contentBusId missing');
|
|
109
|
-
return res;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
await fetchConfigAll(state, req, res);
|
|
113
|
-
await authenticate(state, req, res);
|
|
114
|
-
if (res.error) {
|
|
115
|
-
return res;
|
|
116
|
-
}
|
|
117
|
-
await setCustomResponseHeaders(state, req, res);
|
|
118
|
-
|
|
119
|
-
const {
|
|
120
|
-
owner, repo, ref, contentBusId, partition, s3Loader,
|
|
121
|
-
} = state;
|
|
122
|
-
const { path } = state.info;
|
|
123
|
-
const resourcePath = `${path}.json`;
|
|
124
|
-
|
|
125
|
-
// block all POSTs to resources with extensions
|
|
126
|
-
if (state.info.originalExtension !== '') {
|
|
127
|
-
return error(log, 'POST to URL with extension not allowed', 405, res);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
let body;
|
|
131
|
-
try {
|
|
132
|
-
body = await extractBodyData(req);
|
|
133
|
-
} catch (err) {
|
|
134
|
-
return error(log, err.message, 400, res);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// verify captcha if needed
|
|
138
|
-
try {
|
|
139
|
-
await validateCaptcha(state, body);
|
|
140
|
-
} catch (e) {
|
|
141
|
-
return error(log, e.message, e.code, res);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// head workbook in content bus
|
|
145
|
-
const resourceFetchResponse = await s3Loader.headObject('helix-content-bus', `${contentBusId}/${partition}${resourcePath}`);
|
|
146
|
-
if (resourceFetchResponse.status !== 200) {
|
|
147
|
-
return resourceFetchResponse;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const sheets = resourceFetchResponse.headers.get('x-amz-meta-x-sheet-names');
|
|
151
|
-
if (!sheets) {
|
|
152
|
-
return error(log, `Target workbook at ${resourcePath} missing x-sheet-names header.`, 403, res);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const sourceLocation = resourceFetchResponse.headers.get('x-amz-meta-x-source-location');
|
|
156
|
-
const referer = req.headers.get('referer') || 'unknown';
|
|
157
|
-
const sheetNames = sheets.split(',').map((s) => s.trim());
|
|
158
|
-
|
|
159
|
-
if (!sourceLocation || !sheetNames.includes('incoming')) {
|
|
160
|
-
return error(log, `Target workbook at ${resourcePath} is not setup to intake data.`, 403, res);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Send message to SQS if workbook contains and incoming
|
|
164
|
-
// sheet and the source location is not null
|
|
165
|
-
const { host } = state.config;
|
|
166
|
-
|
|
167
|
-
// Forms service expect owner and repo in the message body
|
|
168
|
-
body.owner = owner;
|
|
169
|
-
body.repo = repo;
|
|
170
|
-
|
|
171
|
-
const message = {
|
|
172
|
-
url: `https://${ref}--${repo}--${owner}.hlx.live${resourcePath}`,
|
|
173
|
-
body,
|
|
174
|
-
host,
|
|
175
|
-
sourceLocation,
|
|
176
|
-
referer,
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
try {
|
|
180
|
-
// Send message to forms queue
|
|
181
|
-
const { requestId, messageId } = await state.messageDispatcher.dispatch(message);
|
|
182
|
-
res.status = 201;
|
|
183
|
-
res.headers.set('x-request-id', requestId);
|
|
184
|
-
res.headers.set('x-message-id', messageId);
|
|
185
|
-
return res;
|
|
186
|
-
} catch (err) {
|
|
187
|
-
return error(log, `Failed to send message to forms queue: ${err}`, 500, res);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2021 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
|
-
import { PipelineStatusError } from '../PipelineStatusError.js';
|
|
14
|
-
import { extractLastModified, updateLastModified } from '../utils/last-modified.js';
|
|
15
|
-
import { globToRegExp, Modifiers } from '../utils/modifiers.js';
|
|
16
|
-
import { getOriginalHost } from './utils.js';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Computes the routes from the given config value.
|
|
20
|
-
* @param {string|string[]|undefined} value
|
|
21
|
-
* @return {RegExp[]} and array of regexps for route matching
|
|
22
|
-
*/
|
|
23
|
-
export function computeRoutes(value) {
|
|
24
|
-
if (!value) {
|
|
25
|
-
return [/.*/];
|
|
26
|
-
}
|
|
27
|
-
// eslint-disable-next-line no-param-reassign
|
|
28
|
-
return (Array.isArray(value) ? value : [value]).map((route) => {
|
|
29
|
-
if (route.indexOf('*') >= 0) {
|
|
30
|
-
return globToRegExp(route);
|
|
31
|
-
}
|
|
32
|
-
if (route.endsWith('/')) {
|
|
33
|
-
return new RegExp(`^${route}.*$`);
|
|
34
|
-
}
|
|
35
|
-
return new RegExp(`^${route}(/.*)?$`);
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function replaceParams(str, info) {
|
|
40
|
-
if (!str) {
|
|
41
|
-
return '';
|
|
42
|
-
}
|
|
43
|
-
return str
|
|
44
|
-
.replaceAll('$owner', info.owner)
|
|
45
|
-
.replaceAll('$repo', info.repo)
|
|
46
|
-
.replaceAll('$ref', info.ref);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Loads the /.helix/config-all.json from the content-bus and stores it in the state. if no
|
|
51
|
-
* such config exists, it will load the metadata.json as fallback and separate out the
|
|
52
|
-
* `state.headers` and `state.metadata`.
|
|
53
|
-
*
|
|
54
|
-
* @type PipelineStep
|
|
55
|
-
* @param {PipelineState} state
|
|
56
|
-
* @param {PipelineRequest} req
|
|
57
|
-
* @param {PipelineResponse} res
|
|
58
|
-
* @returns {Promise<void>}
|
|
59
|
-
*/
|
|
60
|
-
export default async function fetchConfigAll(state, req, res) {
|
|
61
|
-
const { contentBusId, partition } = state;
|
|
62
|
-
const key = `${contentBusId}/${partition}/.helix/config-all.json`;
|
|
63
|
-
const ret = await state.s3Loader.getObject('helix-content-bus', key);
|
|
64
|
-
if (ret.status === 200) {
|
|
65
|
-
let json;
|
|
66
|
-
try {
|
|
67
|
-
json = JSON.parse(ret.body);
|
|
68
|
-
} catch (e) {
|
|
69
|
-
throw new PipelineStatusError(400, `failed parsing of /.helix/config-all.json: ${e.message}`);
|
|
70
|
-
}
|
|
71
|
-
state.config = json.config?.data || {};
|
|
72
|
-
state.metadata = new Modifiers(json.metadata?.data || {});
|
|
73
|
-
state.headers = new Modifiers(json.headers?.data || {});
|
|
74
|
-
|
|
75
|
-
if (state.type === 'html' && state.info.selector !== 'plain') {
|
|
76
|
-
// also update last-modified (only for extensionless html pipeline)
|
|
77
|
-
updateLastModified(state, res, extractLastModified(ret.headers));
|
|
78
|
-
}
|
|
79
|
-
// set custom preview and live hosts
|
|
80
|
-
state.previewHost = replaceParams(state.config.cdn?.preview?.host, state);
|
|
81
|
-
state.liveHost = replaceParams(state.config.cdn?.live?.host, state);
|
|
82
|
-
} else if (ret.status === 404) {
|
|
83
|
-
state.config = {};
|
|
84
|
-
state.metadata = new Modifiers({});
|
|
85
|
-
state.headers = new Modifiers({});
|
|
86
|
-
} else {
|
|
87
|
-
throw new PipelineStatusError(502, `failed to load /.helix/config-all.json: ${ret.status}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// compute host and routes
|
|
91
|
-
if (!state.config.host) {
|
|
92
|
-
state.config.host = state.config.cdn?.prod?.host || getOriginalHost(req.headers);
|
|
93
|
-
}
|
|
94
|
-
state.config.routes = computeRoutes(state.config.cdn?.prod?.route);
|
|
95
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2021 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 { extractLastModified, updateLastModified } from '../utils/last-modified.js';
|
|
13
|
-
import { PipelineStatusError } from '../PipelineStatusError.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Fetches the helix-config.json from the code-bus and stores it in `state.helixConfig`
|
|
17
|
-
* @type PipelineStep
|
|
18
|
-
* @param {PipelineState} state
|
|
19
|
-
* @param {PipelineRequest} req
|
|
20
|
-
* @param {PipelineResponse} [res]
|
|
21
|
-
* @returns {Promise<void>}
|
|
22
|
-
*/
|
|
23
|
-
export default async function fetchConfig(state, req, res) {
|
|
24
|
-
const {
|
|
25
|
-
log, owner, repo, ref,
|
|
26
|
-
} = state;
|
|
27
|
-
|
|
28
|
-
const key = `${owner}/${repo}/${ref}/helix-config.json`;
|
|
29
|
-
const ret = await state.s3Loader.getObject('helix-code-bus', key);
|
|
30
|
-
if (ret.status !== 200) {
|
|
31
|
-
throw new PipelineStatusError(ret.status === 404 ? 404 : 502, `unable to load /helix-config.json: ${ret.status}`);
|
|
32
|
-
}
|
|
33
|
-
let config;
|
|
34
|
-
try {
|
|
35
|
-
config = JSON.parse(ret.body);
|
|
36
|
-
} catch (e) {
|
|
37
|
-
log.info('failed to parse helix-config.json', e);
|
|
38
|
-
throw new PipelineStatusError(400, `Failed parsing of /helix-config.json: ${e.message}`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// upgrade to version 2 if needed
|
|
42
|
-
if (config.version !== 2) {
|
|
43
|
-
Object.keys(config).forEach((name) => {
|
|
44
|
-
config[name] = {
|
|
45
|
-
data: config[name],
|
|
46
|
-
};
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// set contentbusid from header if missing in config
|
|
51
|
-
const cbid = ret.headers.get('x-amz-meta-x-contentbus-id');
|
|
52
|
-
if (!config.content && cbid) {
|
|
53
|
-
config.content = {
|
|
54
|
-
data: {
|
|
55
|
-
'/': {
|
|
56
|
-
contentBusId: cbid.substring(2),
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
if (!state.contentBusId) {
|
|
62
|
-
state.contentBusId = config.content?.data?.['/']?.contentBusId;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (res) {
|
|
66
|
-
// also update last-modified
|
|
67
|
-
const configLastModified = extractLastModified(ret.headers);
|
|
68
|
-
|
|
69
|
-
// update last modified of fstab
|
|
70
|
-
updateLastModified(state, res, config.fstab?.lastModified || configLastModified);
|
|
71
|
-
|
|
72
|
-
// for html requests, also consider the HEAD config
|
|
73
|
-
if (state.type === 'html' && state.info.selector !== 'plain' && config.head?.lastModified) {
|
|
74
|
-
updateLastModified(state, res, config.head.lastModified);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
state.helixConfig = config;
|
|
79
|
-
}
|