@backstage/plugin-auth-backend 0.16.0 → 0.17.0-next.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 CHANGED
@@ -1,5 +1,46 @@
1
1
  # @backstage/plugin-auth-backend
2
2
 
3
+ ## 0.17.0-next.1
4
+
5
+ ### Minor Changes
6
+
7
+ - e2dc42e9f0: Google OAuth refresh tokens will now be revoked on logout by calling Google's API
8
+
9
+ ### Patch Changes
10
+
11
+ - b5c126010c: Auth0 provider now supports optional `connection` and `connectionScope` parameters to configure social identity providers.
12
+ - Updated dependencies
13
+ - @backstage/catalog-client@1.1.1-next.1
14
+ - @backstage/backend-common@0.15.2-next.1
15
+ - @backstage/catalog-model@1.1.2-next.1
16
+ - @backstage/config@1.0.3-next.1
17
+ - @backstage/errors@1.1.2-next.1
18
+ - @backstage/types@1.0.0
19
+ - @backstage/plugin-auth-node@0.2.6-next.1
20
+
21
+ ## 0.17.0-next.0
22
+
23
+ ### Minor Changes
24
+
25
+ - 5fa831ce55: CookieConfigurer can optionally return the `SameSite` cookie attribute.
26
+ CookieConfigurer now requires an additional argument `appOrigin` - the origin URL of the app - which is used to calculate the `SameSite` attribute.
27
+ 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.
28
+
29
+ OAuthAdapterOptions has been modified to require additional arguments, `baseUrl`, and `cookieConfigurer`.
30
+ OAuthAdapter now resolves cookie configuration using its supplied CookieConfigurer for each request to make sure that the proper attributes always are set.
31
+
32
+ ### Patch Changes
33
+
34
+ - 8c6ec175bf: Fix GitLab provider setup so that it supports GitLab installations with a path in the URL.
35
+ - Updated dependencies
36
+ - @backstage/catalog-model@1.1.2-next.0
37
+ - @backstage/catalog-client@1.1.1-next.0
38
+ - @backstage/backend-common@0.15.2-next.0
39
+ - @backstage/plugin-auth-node@0.2.6-next.0
40
+ - @backstage/config@1.0.3-next.0
41
+ - @backstage/errors@1.1.2-next.0
42
+ - @backstage/types@1.0.0
43
+
3
44
  ## 0.16.0
4
45
 
5
46
  ### 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,65 @@ 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
  };
309
+ this.getRefreshTokenFromCookie = (req) => {
310
+ return req.cookies[`${this.options.providerId}-refresh-token`];
311
+ };
302
312
  this.getGrantedScopeFromCookie = (req) => {
303
313
  return req.cookies[`${this.options.providerId}-granted-scope`];
304
314
  };
305
- this.setRefreshTokenCookie = (res, refreshToken) => {
315
+ this.setRefreshTokenCookie = (res, refreshToken, cookieConfig) => {
306
316
  res.cookie(`${this.options.providerId}-refresh-token`, refreshToken, {
307
317
  maxAge: THOUSAND_DAYS_MS,
308
- ...this.baseCookieOptions
318
+ ...this.baseCookieOptions,
319
+ ...cookieConfig
309
320
  });
310
321
  };
311
- this.removeRefreshTokenCookie = (res) => {
322
+ this.removeRefreshTokenCookie = (res, cookieConfig) => {
312
323
  res.cookie(`${this.options.providerId}-refresh-token`, "", {
313
324
  maxAge: 0,
314
- ...this.baseCookieOptions
325
+ ...this.baseCookieOptions,
326
+ ...cookieConfig
327
+ });
328
+ };
329
+ this.getCookieConfig = (origin) => {
330
+ return this.options.cookieConfigurer({
331
+ providerId: this.options.providerId,
332
+ baseUrl: this.options.baseUrl,
333
+ callbackUrl: this.options.callbackUrl,
334
+ appOrigin: origin != null ? origin : this.options.appOrigin
315
335
  });
316
336
  };
317
337
  this.baseCookieOptions = {
318
338
  httpOnly: true,
319
- sameSite: "lax",
320
- secure: this.options.secure,
321
- path: this.options.cookiePath,
322
- domain: this.options.cookieDomain
339
+ sameSite: "lax"
323
340
  };
324
341
  }
325
342
  static fromConfig(config, handlers, options) {
326
343
  var _a;
327
- const { origin: appOrigin } = new url.URL(config.appUrl);
344
+ const { appUrl, baseUrl, isOriginAllowed } = config;
345
+ const { origin: appOrigin } = new url.URL(appUrl);
328
346
  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
347
  return new OAuthAdapter(handlers, {
335
348
  ...options,
336
349
  appOrigin,
337
- cookieDomain: cookieConfig.domain,
338
- cookiePath: cookieConfig.path,
339
- secure: cookieConfig.secure,
340
- isOriginAllowed: config.isOriginAllowed
350
+ baseUrl,
351
+ cookieConfigurer,
352
+ isOriginAllowed
341
353
  });
342
354
  }
343
355
  async start(req, res) {
@@ -348,8 +360,9 @@ class OAuthAdapter {
348
360
  if (!env) {
349
361
  throw new errors.InputError("No env provided in request query parameters");
350
362
  }
363
+ const cookieConfig = this.getCookieConfig(origin);
351
364
  const nonce = crypto__default["default"].randomBytes(16).toString("base64");
352
- this.setNonceCookie(res, nonce);
365
+ this.setNonceCookie(res, nonce, cookieConfig);
353
366
  const state = { nonce, env, origin };
354
367
  if (this.options.persistScopes) {
355
368
  state.scope = scope;
@@ -380,12 +393,13 @@ class OAuthAdapter {
380
393
  }
381
394
  verifyNonce(req, this.options.providerId);
382
395
  const { response, refreshToken } = await this.handlers.handler(req);
396
+ const cookieConfig = this.getCookieConfig(appOrigin);
383
397
  if (this.options.persistScopes && state.scope) {
384
- this.setGrantedScopeCookie(res, state.scope);
398
+ this.setGrantedScopeCookie(res, state.scope, cookieConfig);
385
399
  response.providerInfo.scope = state.scope;
386
400
  }
387
401
  if (refreshToken) {
388
- this.setRefreshTokenCookie(res, refreshToken);
402
+ this.setRefreshTokenCookie(res, refreshToken, cookieConfig);
389
403
  }
390
404
  const identity = await this.populateIdentity(response.backstageIdentity);
391
405
  return postMessageResponse(res, appOrigin, {
@@ -404,7 +418,16 @@ class OAuthAdapter {
404
418
  if (!ensuresXRequestedWith(req)) {
405
419
  throw new errors.AuthenticationError("Invalid X-Requested-With header");
406
420
  }
407
- this.removeRefreshTokenCookie(res);
421
+ if (this.handlers.logout) {
422
+ const refreshToken = this.getRefreshTokenFromCookie(req);
423
+ const revokeRequest = Object.assign(req, {
424
+ refreshToken
425
+ });
426
+ await this.handlers.logout(revokeRequest);
427
+ }
428
+ const origin = req.get("origin");
429
+ const cookieConfig = this.getCookieConfig(origin);
430
+ this.removeRefreshTokenCookie(res, cookieConfig);
408
431
  res.status(200).end();
409
432
  }
410
433
  async refresh(req, res) {
@@ -418,7 +441,7 @@ class OAuthAdapter {
418
441
  );
419
442
  }
420
443
  try {
421
- const refreshToken = req.cookies[`${this.options.providerId}-refresh-token`];
444
+ const refreshToken = this.getRefreshTokenFromCookie(req);
422
445
  if (!refreshToken) {
423
446
  throw new errors.InputError("Missing session cookie");
424
447
  }
@@ -432,7 +455,9 @@ class OAuthAdapter {
432
455
  response.backstageIdentity
433
456
  );
434
457
  if (newRefreshToken && newRefreshToken !== refreshToken) {
435
- this.setRefreshTokenCookie(res, newRefreshToken);
458
+ const origin = req.get("origin");
459
+ const cookieConfig = this.getCookieConfig(origin);
460
+ this.setRefreshTokenCookie(res, newRefreshToken, cookieConfig);
436
461
  }
437
462
  res.status(200).json({ ...response, backstageIdentity });
438
463
  } catch (error) {
@@ -730,6 +755,8 @@ class Auth0AuthProvider {
730
755
  this.authHandler = options.authHandler;
731
756
  this.resolverContext = options.resolverContext;
732
757
  this.audience = options.audience;
758
+ this.connection = options.connection;
759
+ this.connectionScope = options.connectionScope;
733
760
  this._strategy = new Auth0Strategy(
734
761
  {
735
762
  clientID: options.clientId,
@@ -761,12 +788,16 @@ class Auth0AuthProvider {
761
788
  prompt: "consent",
762
789
  scope: req.scope,
763
790
  state: encodeState(req.state),
764
- ...this.audience ? { audience: this.audience } : {}
791
+ ...this.audience ? { audience: this.audience } : {},
792
+ ...this.connection ? { connection: this.connection } : {},
793
+ ...this.connectionScope ? { connection_scope: this.connectionScope } : {}
765
794
  });
766
795
  }
767
796
  async handler(req) {
768
797
  const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy, {
769
- ...this.audience ? { audience: this.audience } : {}
798
+ ...this.audience ? { audience: this.audience } : {},
799
+ ...this.connection ? { connection: this.connection } : {},
800
+ ...this.connectionScope ? { connection_scope: this.connectionScope } : {}
770
801
  });
771
802
  return {
772
803
  response: await this.handleResult(result),
@@ -824,6 +855,8 @@ const auth0 = createAuthProviderIntegration({
824
855
  const domain = envConfig.getString("domain");
825
856
  const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
826
857
  const audience = envConfig.getOptionalString("audience");
858
+ const connection = envConfig.getOptionalString("connection");
859
+ const connectionScope = envConfig.getOptionalString("connectionScope");
827
860
  const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
828
861
  const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
829
862
  profile: makeProfileInfo(fullProfile, params.id_token)
@@ -837,7 +870,9 @@ const auth0 = createAuthProviderIntegration({
837
870
  authHandler,
838
871
  signInResolver,
839
872
  resolverContext,
840
- audience
873
+ audience,
874
+ connection,
875
+ connectionScope
841
876
  });
842
877
  return OAuthAdapter.fromConfig(globalConfig, provider, {
843
878
  providerId,
@@ -1555,7 +1590,10 @@ class GitlabAuthProvider {
1555
1590
  clientID: options.clientId,
1556
1591
  clientSecret: options.clientSecret,
1557
1592
  callbackURL: options.callbackUrl,
1558
- baseURL: options.baseUrl
1593
+ baseURL: options.baseUrl,
1594
+ authorizationURL: `${options.baseUrl}/oauth/authorize`,
1595
+ tokenURL: `${options.baseUrl}/oauth/token`,
1596
+ profileURL: `${options.baseUrl}/api/v4/user`
1559
1597
  },
1560
1598
  (accessToken, refreshToken, params, fullProfile, done) => {
1561
1599
  done(
@@ -1694,6 +1732,10 @@ class GoogleAuthProvider {
1694
1732
  refreshToken: privateInfo.refreshToken
1695
1733
  };
1696
1734
  }
1735
+ async logout(req) {
1736
+ const oauthClient = new googleAuthLibrary.OAuth2Client();
1737
+ await oauthClient.revokeToken(req.refreshToken);
1738
+ }
1697
1739
  async refresh(req) {
1698
1740
  const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
1699
1741
  this.strategy,