@adobe/helix-html-pipeline 3.11.20 → 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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # [4.0.0](https://github.com/adobe/helix-html-pipeline/compare/v3.11.20...v4.0.0) (2023-07-11)
2
+
3
+
4
+ ### Features
5
+
6
+ * use global auth ([dfc0e06](https://github.com/adobe/helix-html-pipeline/commit/dfc0e060239ae5fb14473904568de1ac041a8271)), closes [#324](https://github.com/adobe/helix-html-pipeline/issues/324) [#285](https://github.com/adobe/helix-html-pipeline/issues/285) [#286](https://github.com/adobe/helix-html-pipeline/issues/286) [#287](https://github.com/adobe/helix-html-pipeline/issues/287)
7
+
8
+
9
+ ### BREAKING CHANGES
10
+
11
+ * former x-auth-* headers are no longer returned thus also needs new .hlx.live logic
12
+
1
13
  ## [3.11.20](https://github.com/adobe/helix-html-pipeline/compare/v3.11.19...v3.11.20) (2023-07-11)
2
14
 
3
15
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-html-pipeline",
3
- "version": "3.11.20",
3
+ "version": "4.0.0",
4
4
  "description": "Helix HTML Pipeline",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -9,7 +9,7 @@
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 {PathInfo, S3Loader, FormsMessageDispatcher, PipelineTimer} from "./index";
12
+ import {PathInfo, S3Loader, FormsMessageDispatcher, PipelineTimer, AuthEnvLoader } from "./index";
13
13
  import {PipelineContent} from "./PipelineContent";
14
14
  import {Modifiers} from './utils/modifiers';
15
15
 
@@ -24,6 +24,8 @@ type Fetch = (url: string|Request, options?: RequestOptions) => Promise<Response
24
24
  declare interface AccessConfig {
25
25
  allow:(string|string[]);
26
26
 
27
+ apiKeyId:(string|string[]);
28
+
27
29
  require: {
28
30
  repository:(string|string[]);
29
31
  };
@@ -40,6 +42,7 @@ declare interface PipelineOptions {
40
42
  log: Console;
41
43
  s3Loader: S3Loader;
42
44
  messageDispatcher: FormsMessageDispatcher;
45
+ authEnvLoader: AuthEnvLoader;
43
46
  fetch: Fetch;
44
47
  owner: string;
45
48
  repo: string;
@@ -59,6 +62,7 @@ declare class PipelineState {
59
62
  contentBusId: string;
60
63
  s3Loader: S3Loader;
61
64
  messageDispatcher: FormsMessageDispatcher;
65
+ authEnvLoader: AuthEnvLoader;
62
66
  fetch: Fetch;
63
67
 
64
68
  /**
@@ -39,6 +39,7 @@ export class PipelineState {
39
39
  config: {},
40
40
  s3Loader: opts.s3Loader,
41
41
  messageDispatcher: opts.messageDispatcher,
42
+ authEnvLoader: opts.authEnvLoader ?? { load: () => {} },
42
43
  fetch: opts.fetch,
43
44
  timer: opts.timer,
44
45
  type: 'html',
package/src/html-pipe.js CHANGED
@@ -82,7 +82,7 @@ export async function htmlPipe(state, req) {
82
82
 
83
83
  // check if .auth request
84
84
  if (state.partition === '.auth' || state.info.path === '/.auth') {
85
- if (!initAuthRoute(state, req, res)) {
85
+ if (!await initAuthRoute(state, req, res)) {
86
86
  return res;
87
87
  }
88
88
  }
@@ -118,7 +118,7 @@ export async function htmlPipe(state, req) {
118
118
  ]);
119
119
 
120
120
  await requireProject(state, req, res);
121
- if (!res.error) {
121
+ if (res.error !== 401) {
122
122
  await authenticate(state, req, res);
123
123
  }
124
124
 
package/src/index.d.ts CHANGED
@@ -88,6 +88,16 @@ declare interface S3Loader {
88
88
  headObject(bucketId, key): Promise<PipelineResponse>;
89
89
  }
90
90
 
91
+ declare interface AuthEnvLoader {
92
+
93
+ /**
94
+ * loads (secret) parameters needed for authentication. The parameters are added to the
95
+ * `state.env` object.
96
+ * @return {Promise<void>}
97
+ */
98
+ load(state:PipelineState):Promise<void>;
99
+ }
100
+
91
101
  declare interface DispatchMessageResponse {
92
102
  messageId:string,
93
103
  requestId:string,
@@ -9,7 +9,7 @@
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 { getAuthInfo } from '../utils/auth.js';
12
+ import { getAuthInfo, makeAuthError } from '../utils/auth.js';
13
13
 
14
14
  /**
15
15
  * Checks if the given email is allowed.
@@ -36,11 +36,9 @@ export function isAllowed(email = '', allows = []) {
36
36
  * @returns {Promise<void>}
37
37
  */
38
38
  export async function authenticate(state, req, res) {
39
- // get auth info
40
- const authInfo = await getAuthInfo(state, req);
41
-
42
39
  // check if `.auth` route to validate and exchange token
43
40
  if (state.info.path === '/.auth') {
41
+ const authInfo = await getAuthInfo(state, req);
44
42
  await authInfo.exchangeToken(state, req, res);
45
43
  return;
46
44
  }
@@ -50,41 +48,55 @@ export async function authenticate(state, req, res) {
50
48
  return;
51
49
  }
52
50
 
51
+ // get auth info
52
+ const authInfo = await getAuthInfo(state, req);
53
+
53
54
  // if not authenticated, redirect to login screen
54
55
  if (!authInfo.authenticated) {
55
56
  // send 401 for plain requests
56
57
  if (state.info.selector || state.type !== 'html') {
57
58
  state.log.warn('[auth] unauthorized. redirect to login only for extension less html.');
58
- res.status = 401;
59
- res.error = 'unauthorized.';
59
+ makeAuthError(state, req, res, 'unauthorized');
60
60
  return;
61
61
  }
62
- authInfo.redirectToLogin(state, req, res);
62
+ await authInfo.redirectToLogin(state, req, res);
63
63
  return;
64
64
  }
65
65
 
66
+ const { sub, jti, email } = authInfo.profile;
67
+
68
+ // validate subject, if present
69
+ if (sub) {
70
+ const [owner, repo] = sub.split('/');
71
+ if (owner !== state.owner || (repo !== '*' && repo !== state.repo)) {
72
+ state.log.warn(`[auth] invalid subject ${sub}: does not match ${state.owner}/${state.repo}`);
73
+ makeAuthError(state, req, res, 'invalid-subject');
74
+ return;
75
+ }
76
+ }
77
+
78
+ // validate jti
79
+ if (jti) {
80
+ const ids = Array.isArray(state.config.access.apiKeyId)
81
+ ? state.config.access.apiKeyId
82
+ : [state.config.access.apiKeyId];
83
+ if (ids.indexOf(jti) < 0) {
84
+ state.log.warn(`[auth] invalid jti ${jti}: does not match configured id ${state.config.access.apiKeyId}`);
85
+ makeAuthError(state, req, res, 'invalid-jti');
86
+ }
87
+ }
88
+
66
89
  // check profile is allowed
67
90
  const { allow } = state.config.access;
68
91
  const allows = Array.isArray(allow) ? allow : [allow];
69
- if (!isAllowed(authInfo.profile.email || authInfo.profile.preferred_username, allows)) {
92
+ if (!isAllowed(email, allows)) {
70
93
  state.log.warn(`[auth] profile not allowed for ${allows}`);
71
- res.status = 403;
72
- res.error = 'forbidden.';
73
- }
74
-
75
- // set some response headers for deferred edge authentication
76
- // AdobePatentID="P11443-US"
77
- res.headers.set('x-hlx-auth-allow', allows.join(','));
78
- if (authInfo.profile) {
79
- res.headers.set('x-hlx-auth-iss', authInfo.profile.iss);
80
- res.headers.set('x-hlx-auth-kid', authInfo.profile.kid);
81
- res.headers.set('x-hlx-auth-aud', authInfo.profile.aud);
82
- res.headers.set('x-hlx-auth-key', authInfo.profile.pem);
94
+ makeAuthError(state, req, res, 'forbidden', 403);
83
95
  }
84
96
  }
85
97
 
86
98
  /**
87
- * Checks if the given owner repo is alloed
99
+ * Checks if the given owner repo is allowed
88
100
  * @param {string} owner
89
101
  * @param {string} repo
90
102
  * @param {string[]} allows
@@ -37,9 +37,14 @@ export declare interface IDPConfig {
37
37
 
38
38
  export declare interface UserProfile {
39
39
  email:string;
40
- // hlx_hash:string;
41
- // picture:string;
40
+
42
41
  iss:string;
42
+
43
+ aud:string;
44
+
45
+ sub: string;
46
+
47
+ jti: string;
43
48
  }
44
49
 
45
50
  export declare class AuthInfo {
@@ -68,7 +73,7 @@ export declare class AuthInfo {
68
73
  * @param {PipelineRequest} req
69
74
  * @param {PipelineResponse} res
70
75
  */
71
- redirectToLogin(state, req, res);
76
+ async redirectToLogin(state, req, res);
72
77
 
73
78
  /**
74
79
  * Performs a token exchange from the code flow and redirects to the root page
package/src/utils/auth.js CHANGED
@@ -11,65 +11,88 @@
11
11
  */
12
12
  // eslint-disable-next-line max-classes-per-file
13
13
  import {
14
- createLocalJWKSet, createRemoteJWKSet, decodeJwt, jwtVerify, UnsecuredJWT, exportSPKI,
14
+ createLocalJWKSet,
15
+ decodeJwt,
16
+ jwtVerify,
17
+ SignJWT,
18
+ importJWK,
15
19
  } from 'jose';
16
20
  import { clearAuthCookie, getAuthCookie, setAuthCookie } from './auth-cookie.js';
17
21
 
18
22
  import idpMicrosoft from './idp-configs/microsoft.js';
19
- import idpAdmin from './idp-configs/admin.js';
20
23
 
21
24
  // eslint-disable-next-line import/no-unresolved
22
25
  import cryptoImpl from '#crypto';
23
26
 
24
- export const IDPS = [
25
- idpMicrosoft,
26
- idpAdmin,
27
- ];
28
-
29
27
  const AUTH_REDIRECT_URL = 'https://login.hlx.page/.auth';
30
28
 
29
+ let ADMIN_KEY_PAIR = null;
30
+
31
31
  export class AccessDeniedError extends Error {
32
32
  }
33
33
 
34
+ /**
35
+ * Signs the given JWT with the admin private key and returns the token.
36
+ * @param {PipelineState} state
37
+ * @param {SignJWT} jwt
38
+ * @returns {Promise<string>}
39
+ */
40
+ async function signJWT(state, jwt) {
41
+ if (!ADMIN_KEY_PAIR) {
42
+ ADMIN_KEY_PAIR = {
43
+ privateKey: await importJWK(JSON.parse(state.env.HLX_ADMIN_IDP_PRIVATE_KEY), 'RS256'),
44
+ publicKey: JSON.parse(state.env.HLX_ADMIN_IDP_PUBLIC_KEY),
45
+ };
46
+ }
47
+ const { privateKey, publicKey } = ADMIN_KEY_PAIR;
48
+ return jwt
49
+ .setProtectedHeader({
50
+ alg: 'RS256',
51
+ kid: publicKey.kid,
52
+ })
53
+ .setAudience(state.env.HLX_SITE_APP_AZURE_CLIENT_ID)
54
+ .setIssuer(publicKey.issuer)
55
+ .sign(privateKey);
56
+ }
57
+
58
+ /**
59
+ * Verifies and decodes the given jwt using the admin public key
60
+ * @param {PipelineState} state
61
+ * @param {string} jwt
62
+ * @param {boolean} lenient
63
+ * @returns {Promise<JWTPayload>}
64
+ */
65
+ async function verifyJwt(state, jwt, lenient = false) {
66
+ const publicKey = JSON.parse(state.env.HLX_ADMIN_IDP_PUBLIC_KEY);
67
+ const jwks = createLocalJWKSet({
68
+ keys: [publicKey],
69
+ });
70
+ const { payload } = await jwtVerify(jwt, jwks, {
71
+ audience: state.env.HLX_SITE_APP_AZURE_CLIENT_ID,
72
+ issuer: publicKey.issuer,
73
+ clockTolerance: lenient ? 7 * 24 * 60 * 60 : 0,
74
+ });
75
+ return payload;
76
+ }
77
+
34
78
  /**
35
79
  * Decodes the given id_token for the given idp. if `lenient` is `true`, the clock tolerance
36
80
  * is set to 1 week. this allows to extract some profile information that can be used as login_hint.
37
81
  * @param {PipelineState} state
38
- * @param {IDPConfig} idp
39
82
  * @param {string} idToken
40
83
  * @param {boolean} lenient
41
84
  * @returns {Promise<JWTPayload>}
42
85
  */
43
- export async function decodeIdToken(state, idp, idToken, lenient = false) {
86
+ export async function decodeIdToken(state, idToken, lenient = false) {
44
87
  const { log } = state;
45
- const jwks = idp.discovery.jwks
46
- ? createLocalJWKSet(idp.discovery.jwks)
47
- : /* c8 ignore next */ createRemoteJWKSet(new URL(idp.discovery.jwks_uri));
48
-
49
- const { payload, key, protectedHeader } = await jwtVerify(idToken, jwks, {
50
- audience: idp.client(state).clientId,
51
- clockTolerance: lenient ? 7 * 24 * 60 * 60 : 0,
52
- });
88
+ const payload = await verifyJwt(state, idToken, lenient);
53
89
 
54
90
  // delete from information not needed in the profile
55
- ['azp', 'sub', 'at_hash', 'nonce', 'aio', 'c_hash'].forEach((prop) => delete payload[prop]);
91
+ ['azp', 'at_hash', 'nonce', 'aio', 'c_hash'].forEach((prop) => delete payload[prop]);
56
92
 
57
93
  // compute ttl
58
94
  payload.ttl = payload.exp - Math.floor(Date.now() / 1000);
59
95
 
60
- // export the public key
61
- payload.pem = await exportSPKI(key);
62
- // and encode it base64 url
63
- /* c8 ignore next 3 */
64
- if (typeof Buffer === 'undefined') {
65
- // non-node runtime
66
- payload.pem = btoa(payload.pem);
67
- } else {
68
- // node runtime
69
- payload.pem = Buffer.from(payload.pem, 'utf-8').toString('base64url');
70
- }
71
- payload.kid = protectedHeader.kid;
72
-
73
96
  log.info(`[auth] decoded id_token${lenient ? ' (lenient)' : ''} from ${payload.iss} and validated payload.`);
74
97
  return payload;
75
98
  }
@@ -102,6 +125,22 @@ function getRequestHostAndProto(state, req) {
102
125
  };
103
126
  }
104
127
 
128
+ /**
129
+ * sets the auth error on the response and clears the cookie.
130
+ * @param state
131
+ * @param req
132
+ * @param res
133
+ * @param error
134
+ * @param status
135
+ */
136
+ export function makeAuthError(state, req, res, error, status = 401) {
137
+ const { proto } = getRequestHostAndProto(state, req);
138
+ res.status = status;
139
+ res.error = error;
140
+ res.headers.set('set-cookie', clearAuthCookie(proto === 'https'));
141
+ res.headers.set('x-error', error);
142
+ }
143
+
105
144
  /**
106
145
  * AuthInfo class
107
146
  */
@@ -174,10 +213,11 @@ export class AuthInfo {
174
213
  * @param {PipelineResponse} res
175
214
  * @param {IDPConfig} idp IDP config
176
215
  */
177
- redirectToLogin(state, req, res) {
216
+ async redirectToLogin(state, req, res) {
178
217
  const { log } = state;
179
218
  const { idp } = this;
180
219
 
220
+ await state.authEnvLoader.load(state);
181
221
  const { clientId, clientSecret } = idp.client(state);
182
222
  if (!clientId || !clientSecret) {
183
223
  log.error('[auth] unable to create login redirect: missing client_id or client_secret');
@@ -191,23 +231,20 @@ export class AuthInfo {
191
231
  const { host, proto } = getRequestHostAndProto(state, req);
192
232
  if (!host) {
193
233
  log.error('[auth] unable to create login redirect: no xfh or config.host.');
194
- res.status = 401;
195
- res.error = 'no host information.';
234
+ makeAuthError(state, req, res, 'no host information.');
196
235
  return;
197
236
  }
198
237
 
199
238
  const url = new URL(idp.discovery.authorization_endpoint);
200
239
 
201
- // todo: properly sign to avoid CSRF
202
- const tokenState = new UnsecuredJWT({
240
+ const tokenState = await signJWT(state, new SignJWT({
203
241
  owner: state.owner,
204
242
  repo: state.repo,
205
- contentBusId: state.contentBusId,
206
243
  // this is our own login redirect, i.e. the current document
207
244
  requestPath: state.info.path,
208
245
  requestHost: host,
209
246
  requestProto: proto,
210
- }).encode();
247
+ }));
211
248
 
212
249
  url.searchParams.append('client_id', clientId);
213
250
  url.searchParams.append('response_type', 'code');
@@ -240,11 +277,13 @@ export class AuthInfo {
240
277
  const { code } = req.params;
241
278
  if (!code) {
242
279
  log.warn('[auth] code exchange failed: code parameter missing.');
243
- res.status = 401;
244
- res.error = 'code exchange failed.';
280
+ makeAuthError(state, req, res, 'code exchange failed.');
245
281
  return;
246
282
  }
247
283
 
284
+ // TODO: exchange token on the login host, set-cookie,
285
+ // and then again set-cookie on the request host
286
+
248
287
  // ensure that the request is made to the target host
249
288
  if (req.params.state?.requestHost) {
250
289
  const { host } = getRequestHostAndProto(state, req);
@@ -263,6 +302,7 @@ export class AuthInfo {
263
302
  }
264
303
  }
265
304
 
305
+ await state.authEnvLoader.load(state);
266
306
  const { clientId, clientSecret } = idp.client(state);
267
307
  const url = new URL(idp.discovery.token_endpoint);
268
308
  const body = {
@@ -282,22 +322,37 @@ export class AuthInfo {
282
322
  });
283
323
  if (!ret.ok) {
284
324
  log.warn(`[auth] code exchange failed: ${ret.status}`, await ret.text());
285
- res.status = 401;
286
- res.error = 'code exchange failed.';
325
+ makeAuthError(state, req, res, 'code exchange failed.');
287
326
  return;
288
327
  }
289
328
 
290
329
  const tokenResponse = await ret.json();
291
330
  const { id_token: idToken } = tokenResponse;
331
+ let payload;
292
332
  try {
293
- await decodeIdToken(state, idp, idToken);
333
+ payload = decodeJwt(idToken);
294
334
  } catch (e) {
295
335
  log.warn(`[auth] id token from ${idp.name} is invalid: ${e.message}`);
296
- res.status = 401;
297
- res.error = 'id token invalid.';
336
+ makeAuthError(state, req, res, 'id token invalid.');
298
337
  return;
299
338
  }
300
339
 
340
+ const email = payload.email || payload.preferred_username;
341
+ if (!email) {
342
+ log.warn(`[auth] id token from ${idp.name} is missing email or preferred_username`);
343
+ makeAuthError(state, req, res, 'id token invalid.');
344
+ return;
345
+ }
346
+
347
+ // create new token
348
+ const jwt = new SignJWT({
349
+ email,
350
+ name: payload.name,
351
+ })
352
+ .setIssuedAt()
353
+ .setExpirationTime('12 hours');
354
+ const authToken = await signJWT(state, jwt);
355
+
301
356
  // ensure that auth cookie is not cleared again in `index.js`
302
357
  // ctx.attributes.authInfo?.withCookieInvalid(false);
303
358
 
@@ -307,13 +362,13 @@ export class AuthInfo {
307
362
  res.body = `please go to <a href="${location}">${location}</a>`;
308
363
  res.headers.set('location', location);
309
364
  res.headers.set('content-tye', 'text/plain');
310
- res.headers.set('set-cookie', setAuthCookie(idToken, req.params.state.requestProto === 'https'));
365
+ res.headers.set('set-cookie', setAuthCookie(authToken, req.params.state.requestProto === 'https'));
311
366
  res.headers.set('cache-control', 'no-store, private, must-revalidate');
312
367
  res.error = 'moved';
313
368
  }
314
369
  }
315
370
 
316
- export function initAuthRoute(state, req, res) {
371
+ export async function initAuthRoute(state, req, res) {
317
372
  const { log } = state;
318
373
 
319
374
  // use request headers if present
@@ -328,18 +383,18 @@ export function initAuthRoute(state, req, res) {
328
383
 
329
384
  if (!req.params.state) {
330
385
  log.warn('[auth] unable to exchange token: no state.');
331
- res.status = 401;
332
- res.headers.set('x-error', 'missing state parameter.');
386
+ makeAuthError(state, req, res, 'missing state parameter.');
333
387
  return false;
334
388
  }
335
389
 
336
390
  try {
337
391
  req.params.rawState = req.params.state;
338
- req.params.state = decodeJwt(req.params.state);
392
+ req.params.state = await verifyJwt(state, req.params.state);
393
+ delete req.params.state.aud;
394
+ delete req.params.state.iss;
339
395
  } catch (e) {
340
396
  log.warn(`[auth] error decoding state parameter: invalid state: ${e.message}`);
341
- res.status = 401;
342
- res.headers.set('x-error', 'missing state parameter.');
397
+ makeAuthError(state, req, res, 'missing state parameter.');
343
398
  return false;
344
399
  }
345
400
 
@@ -347,7 +402,6 @@ export function initAuthRoute(state, req, res) {
347
402
  state.owner = req.params.state.owner;
348
403
  state.repo = req.params.state.repo;
349
404
  state.ref = 'main';
350
- state.contentBusId = req.params.state.contentBusId;
351
405
  state.partition = 'preview';
352
406
  state.info.path = '/.auth';
353
407
  return true;
@@ -374,31 +428,18 @@ async function getAuthInfoFromCookieOrHeader(state, req) {
374
428
  }
375
429
  }
376
430
  if (idToken) {
377
- let idp;
378
431
  try {
379
- const { iss } = decodeJwt(idToken);
380
- if (!iss) {
381
- log.warn('[auth] missing \'iss\' claim in id_token.');
382
- return AuthInfo.Default().withCookieInvalid(true);
383
- }
384
- idp = IDPS.find((i) => i.validateIssuer(iss));
385
- if (!idp) {
386
- log.warn(`[auth] no IDP found for: ${iss}`);
387
- return AuthInfo.Default().withCookieInvalid(true);
388
- }
389
432
  return AuthInfo.Default()
390
- .withProfile(await decodeIdToken(state, idp, idToken))
433
+ .withProfile(await decodeIdToken(state, idToken))
391
434
  .withAuthenticated(true)
392
- .withIdp(idp)
393
435
  .withIdToken(idToken);
394
436
  } catch (e) {
395
- if (e.code === 'ERR_JWT_EXPIRED' && idp) {
437
+ if (e.code === 'ERR_JWT_EXPIRED') {
396
438
  try {
397
- const profile = await decodeIdToken(state, idp, idToken, true);
439
+ const profile = await decodeIdToken(state, idToken, true);
398
440
  log.warn(`[auth] decoding the id_token failed: ${e.message}, using expired token as hint.`);
399
441
  return AuthInfo.Default()
400
442
  .withExpired(true)
401
- .withIdp(idp)
402
443
  .withLoginHint(profile.email);
403
444
  } catch {
404
445
  // ignore
@@ -17,7 +17,7 @@ export default {
17
17
  clientSecret: state.env.HLX_SITE_APP_AZURE_CLIENT_SECRET,
18
18
  }),
19
19
  scope: 'openid profile email',
20
- validateIssuer: (iss) => iss.startsWith('https://login.microsoftonline.com/'),
20
+ // validateIssuer: (iss) => iss.startsWith('https://login.microsoftonline.com/'),
21
21
  discoveryUrl: 'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration',
22
22
  // todo: fetch from discovery document
23
23
  discovery: {
@@ -1,25 +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
-
13
- /**
14
- * virtual idp config for the admin service
15
- */
16
- export default {
17
- name: 'admin',
18
- client: (state) => ({
19
- clientId: state.env.HLX_SITE_APP_AZURE_CLIENT_ID,
20
- }),
21
- validateIssuer: (iss) => iss === 'https://admin.hlx.page/',
22
- discovery: {
23
- jwks_uri: 'https://admin.hlx.page/auth/discovery/keys',
24
- },
25
- };