@adobe/helix-html-pipeline 3.0.2 → 3.1.2

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,24 @@
1
+ ## [3.1.2](https://github.com/adobe/helix-html-pipeline/compare/v3.1.1...v3.1.2) (2022-07-09)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **deps:** update dependency @adobe/helix-markdown-support to v3.1.7 ([8dd9cf2](https://github.com/adobe/helix-html-pipeline/commit/8dd9cf22bd3ea1414d2028a84ad52ff8b31b1853))
7
+
8
+ ## [3.1.1](https://github.com/adobe/helix-html-pipeline/compare/v3.1.0...v3.1.1) (2022-07-09)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **deps:** update dependency mdast-util-to-hast to v12.1.2 ([1c863bc](https://github.com/adobe/helix-html-pipeline/commit/1c863bcb06ef8af8ac3c6367121fa3a71aad38f5))
14
+
15
+ # [3.1.0](https://github.com/adobe/helix-html-pipeline/compare/v3.0.2...v3.1.0) (2022-06-24)
16
+
17
+
18
+ ### Features
19
+
20
+ * 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)
21
+
1
22
  ## [3.0.2](https://github.com/adobe/helix-html-pipeline/compare/v3.0.1...v3.0.2) (2022-06-16)
2
23
 
3
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-html-pipeline",
3
- "version": "3.0.2",
3
+ "version": "3.1.2",
4
4
  "description": "Helix HTML Pipeline",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -33,7 +33,7 @@
33
33
  "node": ">=16.x"
34
34
  },
35
35
  "dependencies": {
36
- "@adobe/helix-markdown-support": "3.1.6",
36
+ "@adobe/helix-markdown-support": "3.1.7",
37
37
  "@adobe/helix-shared-utils": "2.0.11",
38
38
  "cookie": "0.5.0",
39
39
  "github-slugger": "1.4.0",
@@ -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.1",
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.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.17.0",
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": "19.0.0",
85
+ "jsdom": "20.0.0",
86
86
  "junit-report-builder": "3.0.0",
87
- "lint-staged": "13.0.1",
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",
@@ -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;
@@ -26,6 +26,7 @@ export class PipelineState {
26
26
  constructor(opts) {
27
27
  Object.assign(this, {
28
28
  log: opts.log ?? console,
29
+ env: opts.env,
29
30
  info: getPathInfo(opts.path),
30
31
  content: new PipelineContent(),
31
32
  // todo: compute content-bus id from fstab
@@ -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-jwk', JSON.stringify(authInfo.profile.jwk));
81
+ res.headers.set('x-hlx-auth-key', authInfo.profile.pem);
82
82
  }
83
83
  }
package/src/utils/auth.js CHANGED
@@ -11,7 +11,7 @@
11
11
  */
12
12
  // eslint-disable-next-line max-classes-per-file
13
13
  import {
14
- createLocalJWKSet, createRemoteJWKSet, decodeJwt, jwtVerify, UnsecuredJWT,
14
+ createLocalJWKSet, createRemoteJWKSet, decodeJwt, jwtVerify, UnsecuredJWT, exportSPKI,
15
15
  } from 'jose';
16
16
  import { clearAuthCookie, getAuthCookie, setAuthCookie } from './auth-cookie.js';
17
17
 
@@ -64,16 +64,38 @@ export async function decodeIdToken(state, idp, idToken, lenient = false) {
64
64
  payload.ttl = payload.exp - Math.floor(Date.now() / 1000);
65
65
 
66
66
  // export the public key
67
- payload.jwk = key.export({
68
- type: 'pkcs1',
69
- format: 'jwk',
70
- });
67
+ payload.pem = await exportSPKI(key);
68
+ // and encode it base64 url
69
+ payload.pem = Buffer.from(payload.pem, 'utf-8').toString('base64url');
71
70
  payload.kid = protectedHeader.kid;
72
71
 
73
72
  log.info(`[auth] decoded id_token${lenient ? ' (lenient)' : ''} from ${payload.iss} and validated payload.`);
74
73
  return payload;
75
74
  }
76
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
+
77
99
  /**
78
100
  * AuthInfo class
79
101
  */
@@ -160,13 +182,7 @@ export class AuthInfo {
160
182
 
161
183
  // determine the location of 'this' document based on the xfh header. so that logins to
162
184
  // .page stay on .page. etc. but fallback to the config.host if non set
163
- let host = req.headers.get('x-forwarded-host');
164
- if (host) {
165
- host = host.split(',')[0].trim();
166
- }
167
- if (!host) {
168
- host = state.config.host;
169
- }
185
+ const host = getRequestHost(state, req);
170
186
  if (!host) {
171
187
  log.error('[auth] unable to create login redirect: no xfh or config.host.');
172
188
  res.status = 401;
@@ -224,7 +240,7 @@ export class AuthInfo {
224
240
 
225
241
  // ensure that the request is made to the target host
226
242
  if (req.params.state?.requestHost) {
227
- const host = req.headers.get('x-forwarded-host') || state.config.host;
243
+ const host = getRequestHost(state, req);
228
244
  if (host !== req.params.state.requestHost) {
229
245
  const url = new URL(`https://${req.params.state.requestHost}/.auth`);
230
246
  url.searchParams.append('state', req.params.rawState);
@@ -330,15 +346,25 @@ export function initAuthRoute(state, req, res) {
330
346
  }
331
347
 
332
348
  /**
333
- * Extracts the authentication info from the cookie. Returns {@code null} if missing or invalid.
349
+ * Extracts the authentication info from the cookie or 'authorization' header.
350
+ * Returns {@code null} if missing or invalid.
334
351
  *
335
352
  * @param {PipelineState} state
336
353
  * @param {PipelineRequest} req
337
354
  * @returns {Promise<AuthInfo>} the authentication info or null if the request is not authenticated
338
355
  */
339
- async function getAuthInfoFromCookie(state, req) {
356
+ async function getAuthInfoFromCookieOrHeader(state, req) {
340
357
  const { log } = state;
341
- const idToken = getAuthCookie(req);
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
+ }
342
368
  if (idToken) {
343
369
  let idp;
344
370
  try {
@@ -375,6 +401,7 @@ async function getAuthInfoFromCookie(state, req) {
375
401
  return AuthInfo.Default().withCookieInvalid(true);
376
402
  }
377
403
  }
404
+ log.info('no id_token');
378
405
  return null;
379
406
  }
380
407
 
@@ -386,7 +413,7 @@ async function getAuthInfoFromCookie(state, req) {
386
413
  */
387
414
  export async function getAuthInfo(state, req) {
388
415
  const { log } = state;
389
- const auth = await getAuthInfoFromCookie(state, req);
416
+ const auth = await getAuthInfoFromCookieOrHeader(state, req);
390
417
  if (auth) {
391
418
  if (auth.authenticated) {
392
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: process.env.HLX_SITE_APP_AZURE_CLIENT_ID,
17
- clientSecret: process.env.HLX_SITE_APP_AZURE_CLIENT_SECRET,
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/'),