@adobe/helix-html-pipeline 3.0.1 → 3.1.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 +21 -0
- package/package.json +6 -6
- package/src/PipelineState.d.ts +2 -0
- package/src/PipelineState.js +1 -0
- package/src/steps/authenticate.js +1 -1
- package/src/utils/auth.js +56 -19
- package/src/utils/idp-configs/microsoft.js +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## [3.1.1](https://github.com/adobe/helix-html-pipeline/compare/v3.1.0...v3.1.1) (2022-07-09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **deps:** update dependency mdast-util-to-hast to v12.1.2 ([1c863bc](https://github.com/adobe/helix-html-pipeline/commit/1c863bcb06ef8af8ac3c6367121fa3a71aad38f5))
|
|
7
|
+
|
|
8
|
+
# [3.1.0](https://github.com/adobe/helix-html-pipeline/compare/v3.0.2...v3.1.0) (2022-06-24)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* improve authentication implementation ([#90](https://github.com/adobe/helix-html-pipeline/issues/90)) ([def347c](https://github.com/adobe/helix-html-pipeline/commit/def347cd0d4860d2804d71be6b702a6be30d6095)), closes [#85](https://github.com/adobe/helix-html-pipeline/issues/85)
|
|
14
|
+
|
|
15
|
+
## [3.0.2](https://github.com/adobe/helix-html-pipeline/compare/v3.0.1...v3.0.2) (2022-06-16)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* make crypto.randomUUID() portable ([d40ba5a](https://github.com/adobe/helix-html-pipeline/commit/d40ba5ab67c764726d923061d4844e5adb162c86))
|
|
21
|
+
|
|
1
22
|
## [3.0.1](https://github.com/adobe/helix-html-pipeline/compare/v3.0.0...v3.0.1) (2022-06-14)
|
|
2
23
|
|
|
3
24
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/helix-html-pipeline",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Helix HTML Pipeline",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"hast-util-to-html": "8.0.3",
|
|
43
43
|
"hast-util-to-string": "2.0.0",
|
|
44
44
|
"hastscript": "7.0.2",
|
|
45
|
-
"jose": "4.8.
|
|
45
|
+
"jose": "4.8.3",
|
|
46
46
|
"mdast-util-gfm-footnote": "1.0.1",
|
|
47
47
|
"mdast-util-gfm-strikethrough": "1.0.1",
|
|
48
48
|
"mdast-util-gfm-table": "1.0.4",
|
|
49
49
|
"mdast-util-gfm-task-list-item": "1.0.1",
|
|
50
|
-
"mdast-util-to-hast": "12.1.
|
|
50
|
+
"mdast-util-to-hast": "12.1.2",
|
|
51
51
|
"mdast-util-to-string": "3.1.0",
|
|
52
52
|
"micromark-extension-gfm-footnote": "1.0.4",
|
|
53
53
|
"micromark-extension-gfm-strikethrough": "1.0.4",
|
|
@@ -75,16 +75,16 @@
|
|
|
75
75
|
"@semantic-release/git": "10.0.1",
|
|
76
76
|
"@semantic-release/npm": "9.0.1",
|
|
77
77
|
"c8": "7.11.3",
|
|
78
|
-
"eslint": "8.
|
|
78
|
+
"eslint": "8.19.0",
|
|
79
79
|
"eslint-plugin-header": "3.1.1",
|
|
80
80
|
"eslint-plugin-import": "2.26.0",
|
|
81
81
|
"esmock": "1.7.5",
|
|
82
82
|
"husky": "8.0.1",
|
|
83
83
|
"js-yaml": "4.1.0",
|
|
84
84
|
"jsdoc-to-markdown": "7.1.1",
|
|
85
|
-
"jsdom": "
|
|
85
|
+
"jsdom": "20.0.0",
|
|
86
86
|
"junit-report-builder": "3.0.0",
|
|
87
|
-
"lint-staged": "13.0.
|
|
87
|
+
"lint-staged": "13.0.3",
|
|
88
88
|
"mocha": "10.0.0",
|
|
89
89
|
"mocha-multi-reporters": "1.5.1",
|
|
90
90
|
"remark-gfm": "3.0.1",
|
package/src/PipelineState.d.ts
CHANGED
|
@@ -44,11 +44,13 @@ declare interface PipelineOptions {
|
|
|
44
44
|
path: string;
|
|
45
45
|
contentBusId: string;
|
|
46
46
|
timer: PipelineTimer;
|
|
47
|
+
env: object;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
declare class PipelineState {
|
|
50
51
|
constructor(opts: PipelineOptions);
|
|
51
52
|
log: Console;
|
|
53
|
+
env: object;
|
|
52
54
|
info: PathInfo;
|
|
53
55
|
content: PipelineContent;
|
|
54
56
|
contentBusId: string;
|
package/src/PipelineState.js
CHANGED
|
@@ -78,6 +78,6 @@ export async function authenticate(state, req, res) {
|
|
|
78
78
|
res.headers.set('x-hlx-auth-iss', authInfo.profile.iss);
|
|
79
79
|
res.headers.set('x-hlx-auth-kid', authInfo.profile.kid);
|
|
80
80
|
res.headers.set('x-hlx-auth-aud', authInfo.profile.aud);
|
|
81
|
-
res.headers.set('x-hlx-auth-
|
|
81
|
+
res.headers.set('x-hlx-auth-key', authInfo.profile.pem);
|
|
82
82
|
}
|
|
83
83
|
}
|
package/src/utils/auth.js
CHANGED
|
@@ -10,14 +10,24 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
// eslint-disable-next-line max-classes-per-file
|
|
13
|
-
import crypto from 'crypto';
|
|
14
13
|
import {
|
|
15
|
-
createLocalJWKSet, createRemoteJWKSet, decodeJwt, jwtVerify, UnsecuredJWT,
|
|
14
|
+
createLocalJWKSet, createRemoteJWKSet, decodeJwt, jwtVerify, UnsecuredJWT, exportSPKI,
|
|
16
15
|
} from 'jose';
|
|
17
16
|
import { clearAuthCookie, getAuthCookie, setAuthCookie } from './auth-cookie.js';
|
|
18
17
|
|
|
19
18
|
import idpMicrosoft from './idp-configs/microsoft.js';
|
|
20
19
|
|
|
20
|
+
let cryptoImpl;
|
|
21
|
+
import('crypto')
|
|
22
|
+
.then((crypto) => {
|
|
23
|
+
cryptoImpl = crypto;
|
|
24
|
+
})
|
|
25
|
+
/* c8 ignore next 3 */
|
|
26
|
+
.catch(() => {
|
|
27
|
+
// eslint-disable-next-line no-undef
|
|
28
|
+
cryptoImpl = crypto;
|
|
29
|
+
});
|
|
30
|
+
|
|
21
31
|
export const IDPS = [
|
|
22
32
|
idpMicrosoft,
|
|
23
33
|
];
|
|
@@ -54,16 +64,38 @@ export async function decodeIdToken(state, idp, idToken, lenient = false) {
|
|
|
54
64
|
payload.ttl = payload.exp - Math.floor(Date.now() / 1000);
|
|
55
65
|
|
|
56
66
|
// export the public key
|
|
57
|
-
payload.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
});
|
|
67
|
+
payload.pem = await exportSPKI(key);
|
|
68
|
+
// and encode it base64 url
|
|
69
|
+
payload.pem = Buffer.from(payload.pem, 'utf-8').toString('base64url');
|
|
61
70
|
payload.kid = protectedHeader.kid;
|
|
62
71
|
|
|
63
72
|
log.info(`[auth] decoded id_token${lenient ? ' (lenient)' : ''} from ${payload.iss} and validated payload.`);
|
|
64
73
|
return payload;
|
|
65
74
|
}
|
|
66
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Returns the host of the request; falls back to the configured `host`.
|
|
78
|
+
* Note that this is different from the `config.host` calculation in `fetch-config-all`,
|
|
79
|
+
* as this prefers the xfh over the config.
|
|
80
|
+
*
|
|
81
|
+
* @param {PipelineState} state
|
|
82
|
+
* @param {PipelineRequest} req
|
|
83
|
+
* @return {string}
|
|
84
|
+
*/
|
|
85
|
+
function getRequestHost(state, req) {
|
|
86
|
+
// determine the location of 'this' document based on the xfh header. so that logins to
|
|
87
|
+
// .page stay on .page. etc. but fallback to the config.host if non set
|
|
88
|
+
let host = req.headers.get('x-forwarded-host');
|
|
89
|
+
if (host) {
|
|
90
|
+
host = host.split(',')[0].trim();
|
|
91
|
+
}
|
|
92
|
+
if (!host) {
|
|
93
|
+
host = state.config.host;
|
|
94
|
+
}
|
|
95
|
+
state.log.info(`request host is: ${host}`);
|
|
96
|
+
return host;
|
|
97
|
+
}
|
|
98
|
+
|
|
67
99
|
/**
|
|
68
100
|
* AuthInfo class
|
|
69
101
|
*/
|
|
@@ -150,13 +182,7 @@ export class AuthInfo {
|
|
|
150
182
|
|
|
151
183
|
// determine the location of 'this' document based on the xfh header. so that logins to
|
|
152
184
|
// .page stay on .page. etc. but fallback to the config.host if non set
|
|
153
|
-
|
|
154
|
-
if (host) {
|
|
155
|
-
host = host.split(',')[0].trim();
|
|
156
|
-
}
|
|
157
|
-
if (!host) {
|
|
158
|
-
host = state.config.host;
|
|
159
|
-
}
|
|
185
|
+
const host = getRequestHost(state, req);
|
|
160
186
|
if (!host) {
|
|
161
187
|
log.error('[auth] unable to create login redirect: no xfh or config.host.');
|
|
162
188
|
res.status = 401;
|
|
@@ -179,7 +205,7 @@ export class AuthInfo {
|
|
|
179
205
|
url.searchParams.append('client_id', clientId);
|
|
180
206
|
url.searchParams.append('response_type', 'code');
|
|
181
207
|
url.searchParams.append('scope', idp.scope);
|
|
182
|
-
url.searchParams.append('nonce',
|
|
208
|
+
url.searchParams.append('nonce', cryptoImpl.randomUUID());
|
|
183
209
|
url.searchParams.append('state', tokenState);
|
|
184
210
|
url.searchParams.append('redirect_uri', state.createExternalLocation(AUTH_REDIRECT_URL));
|
|
185
211
|
url.searchParams.append('prompt', 'select_account');
|
|
@@ -214,7 +240,7 @@ export class AuthInfo {
|
|
|
214
240
|
|
|
215
241
|
// ensure that the request is made to the target host
|
|
216
242
|
if (req.params.state?.requestHost) {
|
|
217
|
-
const host = req
|
|
243
|
+
const host = getRequestHost(state, req);
|
|
218
244
|
if (host !== req.params.state.requestHost) {
|
|
219
245
|
const url = new URL(`https://${req.params.state.requestHost}/.auth`);
|
|
220
246
|
url.searchParams.append('state', req.params.rawState);
|
|
@@ -320,15 +346,25 @@ export function initAuthRoute(state, req, res) {
|
|
|
320
346
|
}
|
|
321
347
|
|
|
322
348
|
/**
|
|
323
|
-
* Extracts the authentication info from the cookie
|
|
349
|
+
* Extracts the authentication info from the cookie or 'authorization' header.
|
|
350
|
+
* Returns {@code null} if missing or invalid.
|
|
324
351
|
*
|
|
325
352
|
* @param {PipelineState} state
|
|
326
353
|
* @param {PipelineRequest} req
|
|
327
354
|
* @returns {Promise<AuthInfo>} the authentication info or null if the request is not authenticated
|
|
328
355
|
*/
|
|
329
|
-
async function
|
|
356
|
+
async function getAuthInfoFromCookieOrHeader(state, req) {
|
|
330
357
|
const { log } = state;
|
|
331
|
-
|
|
358
|
+
let idToken = getAuthCookie(req);
|
|
359
|
+
if (!idToken) {
|
|
360
|
+
log.info('no auth cookie');
|
|
361
|
+
const [marker, value] = (req.headers.get('authorization') || '').split(' ');
|
|
362
|
+
if (marker.toLowerCase() === 'token' && value) {
|
|
363
|
+
idToken = value.trim();
|
|
364
|
+
} else {
|
|
365
|
+
log.info('no auth header');
|
|
366
|
+
}
|
|
367
|
+
}
|
|
332
368
|
if (idToken) {
|
|
333
369
|
let idp;
|
|
334
370
|
try {
|
|
@@ -365,6 +401,7 @@ async function getAuthInfoFromCookie(state, req) {
|
|
|
365
401
|
return AuthInfo.Default().withCookieInvalid(true);
|
|
366
402
|
}
|
|
367
403
|
}
|
|
404
|
+
log.info('no id_token');
|
|
368
405
|
return null;
|
|
369
406
|
}
|
|
370
407
|
|
|
@@ -376,7 +413,7 @@ async function getAuthInfoFromCookie(state, req) {
|
|
|
376
413
|
*/
|
|
377
414
|
export async function getAuthInfo(state, req) {
|
|
378
415
|
const { log } = state;
|
|
379
|
-
const auth = await
|
|
416
|
+
const auth = await getAuthInfoFromCookieOrHeader(state, req);
|
|
380
417
|
if (auth) {
|
|
381
418
|
if (auth.authenticated) {
|
|
382
419
|
log.info(`[auth] id-token valid: iss=${auth.profile.iss}`);
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
export default {
|
|
13
13
|
name: 'microsoft',
|
|
14
14
|
mountType: 'onedrive',
|
|
15
|
-
client: () => ({
|
|
16
|
-
clientId:
|
|
17
|
-
clientSecret:
|
|
15
|
+
client: (state) => ({
|
|
16
|
+
clientId: state.env.HLX_SITE_APP_AZURE_CLIENT_ID,
|
|
17
|
+
clientSecret: state.env.HLX_SITE_APP_AZURE_CLIENT_SECRET,
|
|
18
18
|
}),
|
|
19
19
|
scope: 'openid profile email',
|
|
20
20
|
validateIssuer: (iss) => iss.startsWith('https://login.microsoftonline.com/'),
|