@backstage/plugin-auth-backend 0.16.0-next.3 → 0.17.0-next.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,5 +1,54 @@
1
1
  # @backstage/plugin-auth-backend
2
2
 
3
+ ## 0.17.0-next.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5fa831ce55: CookieConfigurer can optionally return the `SameSite` cookie attribute.
8
+ CookieConfigurer now requires an additional argument `appOrigin` - the origin URL of the app - which is used to calculate the `SameSite` attribute.
9
+ defaultCookieConfigurer returns the `SameSite` attribute which defaults to `Lax`. In cases where an auth-backend is running on a different domain than the App, `SameSite=None` is used - but only for secure contexts. This is so that cookies can be included in third-party requests.
10
+
11
+ OAuthAdapterOptions has been modified to require additional arguments, `baseUrl`, and `cookieConfigurer`.
12
+ OAuthAdapter now resolves cookie configuration using its supplied CookieConfigurer for each request to make sure that the proper attributes always are set.
13
+
14
+ ### Patch Changes
15
+
16
+ - 8c6ec175bf: Fix GitLab provider setup so that it supports GitLab installations with a path in the URL.
17
+ - Updated dependencies
18
+ - @backstage/catalog-model@1.1.2-next.0
19
+ - @backstage/catalog-client@1.1.1-next.0
20
+ - @backstage/backend-common@0.15.2-next.0
21
+ - @backstage/plugin-auth-node@0.2.6-next.0
22
+ - @backstage/config@1.0.3-next.0
23
+ - @backstage/errors@1.1.2-next.0
24
+ - @backstage/types@1.0.0
25
+
26
+ ## 0.16.0
27
+
28
+ ### Minor Changes
29
+
30
+ - 2fc41ebf07: Removed the previously deprecated class `AtlassianAuthProvider`. Please use `providers.atlassian.create(...)` instead.
31
+ - a291688bc5: Renamed the `RedirectInfo` type to `OAuthStartResponse`
32
+ - 8600855fbf: The auth0 integration is updated to use the `passport-auth0` library. The configuration under `auth.providers.auth0.\*` now supports an optional `audience` parameter; providing that allows you to connect to the correct API to get permissions, access tokens, and full profile information.
33
+
34
+ [What is an Audience](https://community.auth0.com/t/what-is-the-audience/71414)
35
+
36
+ ### Patch Changes
37
+
38
+ - 5b011fb2e6: Allow adding misc claims to JWT
39
+ - d669d89206: Minor API signatures cleanup
40
+ - 667d917488: Updated dependency `msw` to `^0.47.0`.
41
+ - 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`.
42
+ - bf5e9030eb: Updated dependency `msw` to `^0.45.0`.
43
+ - e1ebaeb332: Cloudflare Access Provider: Add JWT to CloudflareAccessResult
44
+ - Updated dependencies
45
+ - @backstage/backend-common@0.15.1
46
+ - @backstage/plugin-auth-node@0.2.5
47
+ - @backstage/catalog-client@1.1.0
48
+ - @backstage/catalog-model@1.1.1
49
+ - @backstage/config@1.0.2
50
+ - @backstage/errors@1.1.1
51
+
3
52
  ## 0.16.0-next.3
4
53
 
5
54
  ### Minor Changes
package/dist/index.cjs.js CHANGED
@@ -165,12 +165,17 @@ const verifyNonce = (req, providerId) => {
165
165
  };
166
166
  const defaultCookieConfigurer = ({
167
167
  callbackUrl,
168
- providerId
168
+ providerId,
169
+ appOrigin
169
170
  }) => {
170
171
  const { hostname: domain, pathname, protocol } = new URL(callbackUrl);
171
172
  const secure = protocol === "https:";
173
+ let sameSite = "lax";
174
+ if (new URL(appOrigin).hostname !== domain && secure) {
175
+ sameSite = "none";
176
+ }
172
177
  const path = pathname.endsWith(`${providerId}/handler/frame`) ? pathname.slice(0, -"/handler/frame".length) : `${pathname}/${providerId}`;
173
- return { domain, path, secure };
178
+ return { domain, path, secure, sameSite };
174
179
  };
175
180
 
176
181
  class OAuthEnvironmentHandler {
@@ -286,58 +291,62 @@ class OAuthAdapter {
286
291
  constructor(handlers, options) {
287
292
  this.handlers = handlers;
288
293
  this.options = options;
289
- this.setNonceCookie = (res, nonce) => {
294
+ this.setNonceCookie = (res, nonce, cookieConfig) => {
290
295
  res.cookie(`${this.options.providerId}-nonce`, nonce, {
291
296
  maxAge: TEN_MINUTES_MS,
292
297
  ...this.baseCookieOptions,
293
- path: `${this.options.cookiePath}/handler`
298
+ ...cookieConfig,
299
+ path: `${cookieConfig.path}/handler`
294
300
  });
295
301
  };
296
- this.setGrantedScopeCookie = (res, scope) => {
302
+ this.setGrantedScopeCookie = (res, scope, cookieConfig) => {
297
303
  res.cookie(`${this.options.providerId}-granted-scope`, scope, {
298
304
  maxAge: THOUSAND_DAYS_MS,
299
- ...this.baseCookieOptions
305
+ ...this.baseCookieOptions,
306
+ ...cookieConfig
300
307
  });
301
308
  };
302
309
  this.getGrantedScopeFromCookie = (req) => {
303
310
  return req.cookies[`${this.options.providerId}-granted-scope`];
304
311
  };
305
- this.setRefreshTokenCookie = (res, refreshToken) => {
312
+ this.setRefreshTokenCookie = (res, refreshToken, cookieConfig) => {
306
313
  res.cookie(`${this.options.providerId}-refresh-token`, refreshToken, {
307
314
  maxAge: THOUSAND_DAYS_MS,
308
- ...this.baseCookieOptions
315
+ ...this.baseCookieOptions,
316
+ ...cookieConfig
309
317
  });
310
318
  };
311
- this.removeRefreshTokenCookie = (res) => {
319
+ this.removeRefreshTokenCookie = (res, cookieConfig) => {
312
320
  res.cookie(`${this.options.providerId}-refresh-token`, "", {
313
321
  maxAge: 0,
314
- ...this.baseCookieOptions
322
+ ...this.baseCookieOptions,
323
+ ...cookieConfig
324
+ });
325
+ };
326
+ this.getCookieConfig = (origin) => {
327
+ return this.options.cookieConfigurer({
328
+ providerId: this.options.providerId,
329
+ baseUrl: this.options.baseUrl,
330
+ callbackUrl: this.options.callbackUrl,
331
+ appOrigin: origin != null ? origin : this.options.appOrigin
315
332
  });
316
333
  };
317
334
  this.baseCookieOptions = {
318
335
  httpOnly: true,
319
- sameSite: "lax",
320
- secure: this.options.secure,
321
- path: this.options.cookiePath,
322
- domain: this.options.cookieDomain
336
+ sameSite: "lax"
323
337
  };
324
338
  }
325
339
  static fromConfig(config, handlers, options) {
326
340
  var _a;
327
- const { origin: appOrigin } = new url.URL(config.appUrl);
341
+ const { appUrl, baseUrl, isOriginAllowed } = config;
342
+ const { origin: appOrigin } = new url.URL(appUrl);
328
343
  const cookieConfigurer = (_a = config.cookieConfigurer) != null ? _a : defaultCookieConfigurer;
329
- const cookieConfig = cookieConfigurer({
330
- providerId: options.providerId,
331
- baseUrl: config.baseUrl,
332
- callbackUrl: options.callbackUrl
333
- });
334
344
  return new OAuthAdapter(handlers, {
335
345
  ...options,
336
346
  appOrigin,
337
- cookieDomain: cookieConfig.domain,
338
- cookiePath: cookieConfig.path,
339
- secure: cookieConfig.secure,
340
- isOriginAllowed: config.isOriginAllowed
347
+ baseUrl,
348
+ cookieConfigurer,
349
+ isOriginAllowed
341
350
  });
342
351
  }
343
352
  async start(req, res) {
@@ -348,8 +357,9 @@ class OAuthAdapter {
348
357
  if (!env) {
349
358
  throw new errors.InputError("No env provided in request query parameters");
350
359
  }
360
+ const cookieConfig = this.getCookieConfig(origin);
351
361
  const nonce = crypto__default["default"].randomBytes(16).toString("base64");
352
- this.setNonceCookie(res, nonce);
362
+ this.setNonceCookie(res, nonce, cookieConfig);
353
363
  const state = { nonce, env, origin };
354
364
  if (this.options.persistScopes) {
355
365
  state.scope = scope;
@@ -380,12 +390,13 @@ class OAuthAdapter {
380
390
  }
381
391
  verifyNonce(req, this.options.providerId);
382
392
  const { response, refreshToken } = await this.handlers.handler(req);
393
+ const cookieConfig = this.getCookieConfig(appOrigin);
383
394
  if (this.options.persistScopes && state.scope) {
384
- this.setGrantedScopeCookie(res, state.scope);
395
+ this.setGrantedScopeCookie(res, state.scope, cookieConfig);
385
396
  response.providerInfo.scope = state.scope;
386
397
  }
387
398
  if (refreshToken) {
388
- this.setRefreshTokenCookie(res, refreshToken);
399
+ this.setRefreshTokenCookie(res, refreshToken, cookieConfig);
389
400
  }
390
401
  const identity = await this.populateIdentity(response.backstageIdentity);
391
402
  return postMessageResponse(res, appOrigin, {
@@ -404,7 +415,9 @@ class OAuthAdapter {
404
415
  if (!ensuresXRequestedWith(req)) {
405
416
  throw new errors.AuthenticationError("Invalid X-Requested-With header");
406
417
  }
407
- this.removeRefreshTokenCookie(res);
418
+ const origin = req.get("origin");
419
+ const cookieConfig = this.getCookieConfig(origin);
420
+ this.removeRefreshTokenCookie(res, cookieConfig);
408
421
  res.status(200).end();
409
422
  }
410
423
  async refresh(req, res) {
@@ -432,7 +445,9 @@ class OAuthAdapter {
432
445
  response.backstageIdentity
433
446
  );
434
447
  if (newRefreshToken && newRefreshToken !== refreshToken) {
435
- this.setRefreshTokenCookie(res, newRefreshToken);
448
+ const origin = req.get("origin");
449
+ const cookieConfig = this.getCookieConfig(origin);
450
+ this.setRefreshTokenCookie(res, newRefreshToken, cookieConfig);
436
451
  }
437
452
  res.status(200).json({ ...response, backstageIdentity });
438
453
  } catch (error) {
@@ -1555,7 +1570,10 @@ class GitlabAuthProvider {
1555
1570
  clientID: options.clientId,
1556
1571
  clientSecret: options.clientSecret,
1557
1572
  callbackURL: options.callbackUrl,
1558
- baseURL: options.baseUrl
1573
+ baseURL: options.baseUrl,
1574
+ authorizationURL: `${options.baseUrl}/oauth/authorize`,
1575
+ tokenURL: `${options.baseUrl}/oauth/token`,
1576
+ profileURL: `${options.baseUrl}/api/v4/user`
1559
1577
  },
1560
1578
  (accessToken, refreshToken, params, fullProfile, done) => {
1561
1579
  done(
@@ -2724,8 +2742,7 @@ class TokenFactory {
2724
2742
  async issueToken(params) {
2725
2743
  const key = await this.getKey();
2726
2744
  const iss = this.issuer;
2727
- const sub = params.claims.sub;
2728
- const ent = params.claims.ent;
2745
+ const { sub, ent, ...additionalClaims } = params.claims;
2729
2746
  const aud = "backstage";
2730
2747
  const iat = Math.floor(Date.now() / MS_IN_S);
2731
2748
  const exp = iat + this.keyDurationSeconds;
@@ -2740,7 +2757,7 @@ class TokenFactory {
2740
2757
  if (!key.alg) {
2741
2758
  throw new errors.AuthenticationError("No algorithm was provided in the key");
2742
2759
  }
2743
- return new jose.SignJWT({ iss, sub, ent, aud, iat, exp }).setProtectedHeader({ alg: key.alg, kid: key.kid }).setIssuer(iss).setAudience(aud).setSubject(sub).setIssuedAt(iat).setExpirationTime(exp).sign(await jose.importJWK(key));
2760
+ return new jose.SignJWT({ ...additionalClaims, iss, sub, ent, aud, iat, exp }).setProtectedHeader({ alg: key.alg, kid: key.kid }).setIssuer(iss).setAudience(aud).setSubject(sub).setIssuedAt(iat).setExpirationTime(exp).sign(await jose.importJWK(key));
2744
2761
  }
2745
2762
  async listPublicKeys() {
2746
2763
  const { items: keys } = await this.keyStore.listKeys();