@backstage/plugin-auth-backend 0.9.0-next.0 → 0.9.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,14 @@
1
1
  # @backstage/plugin-auth-backend
2
2
 
3
+ ## 0.9.0-next.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 9d75a939b6: Fixed a bug where providers that tracked the granted scopes through a cookie would not take failed authentication attempts into account.
8
+ - 648606b3ac: Added support for storing static GitHub access tokens in cookies and using them to refresh the Backstage session.
9
+ - Updated dependencies
10
+ - @backstage/backend-common@0.10.6-next.0
11
+
3
12
  ## 0.9.0-next.0
4
13
 
5
14
  ### Minor Changes
package/dist/index.cjs.js CHANGED
@@ -281,46 +281,38 @@ class OAuthAdapter {
281
281
  this.setNonceCookie = (res, nonce) => {
282
282
  res.cookie(`${this.options.providerId}-nonce`, nonce, {
283
283
  maxAge: TEN_MINUTES_MS,
284
- secure: this.options.secure,
285
- sameSite: "lax",
286
- domain: this.options.cookieDomain,
287
- path: `${this.options.cookiePath}/handler`,
288
- httpOnly: true
284
+ ...this.baseCookieOptions,
285
+ path: `${this.options.cookiePath}/handler`
289
286
  });
290
287
  };
291
- this.setScopesCookie = (res, scope) => {
292
- res.cookie(`${this.options.providerId}-scope`, scope, {
293
- maxAge: TEN_MINUTES_MS,
294
- secure: this.options.secure,
295
- sameSite: "lax",
296
- domain: this.options.cookieDomain,
297
- path: `${this.options.cookiePath}/handler`,
298
- httpOnly: true
288
+ this.setGrantedScopeCookie = (res, scope) => {
289
+ res.cookie(`${this.options.providerId}-granted-scope`, scope, {
290
+ maxAge: THOUSAND_DAYS_MS,
291
+ ...this.baseCookieOptions
299
292
  });
300
293
  };
301
- this.getScopesFromCookie = (req, providerId) => {
302
- return req.cookies[`${providerId}-scope`];
294
+ this.getGrantedScopeFromCookie = (req) => {
295
+ return req.cookies[`${this.options.providerId}-granted-scope`];
303
296
  };
304
297
  this.setRefreshTokenCookie = (res, refreshToken) => {
305
298
  res.cookie(`${this.options.providerId}-refresh-token`, refreshToken, {
306
299
  maxAge: THOUSAND_DAYS_MS,
307
- secure: this.options.secure,
308
- sameSite: "lax",
309
- domain: this.options.cookieDomain,
310
- path: this.options.cookiePath,
311
- httpOnly: true
300
+ ...this.baseCookieOptions
312
301
  });
313
302
  };
314
303
  this.removeRefreshTokenCookie = (res) => {
315
304
  res.cookie(`${this.options.providerId}-refresh-token`, "", {
316
305
  maxAge: 0,
317
- secure: this.options.secure,
318
- sameSite: "lax",
319
- domain: this.options.cookieDomain,
320
- path: this.options.cookiePath,
321
- httpOnly: true
306
+ ...this.baseCookieOptions
322
307
  });
323
308
  };
309
+ this.baseCookieOptions = {
310
+ httpOnly: true,
311
+ sameSite: "lax",
312
+ secure: this.options.secure,
313
+ path: this.options.cookiePath,
314
+ domain: this.options.cookieDomain
315
+ };
324
316
  }
325
317
  static fromConfig(config, handlers, options) {
326
318
  var _a;
@@ -344,12 +336,12 @@ class OAuthAdapter {
344
336
  if (!env) {
345
337
  throw new errors.InputError("No env provided in request query parameters");
346
338
  }
347
- if (this.options.persistScopes) {
348
- this.setScopesCookie(res, scope);
349
- }
350
339
  const nonce = crypto__default["default"].randomBytes(16).toString("base64");
351
340
  this.setNonceCookie(res, nonce);
352
341
  const state = { nonce, env, origin };
342
+ if (this.options.persistScopes) {
343
+ state.scope = scope;
344
+ }
353
345
  const forwardReq = Object.assign(req, { scope, state });
354
346
  const { url, status } = await this.handlers.start(forwardReq);
355
347
  res.statusCode = status || 302;
@@ -374,9 +366,9 @@ class OAuthAdapter {
374
366
  }
375
367
  verifyNonce(req, this.options.providerId);
376
368
  const { response, refreshToken } = await this.handlers.handler(req);
377
- if (this.options.persistScopes) {
378
- const grantedScopes = this.getScopesFromCookie(req, this.options.providerId);
379
- response.providerInfo.scope = grantedScopes;
369
+ if (this.options.persistScopes && state.scope) {
370
+ this.setGrantedScopeCookie(res, state.scope);
371
+ response.providerInfo.scope = state.scope;
380
372
  }
381
373
  if (refreshToken && !this.options.disableRefresh) {
382
374
  this.setRefreshTokenCookie(res, refreshToken);
@@ -414,7 +406,10 @@ class OAuthAdapter {
414
406
  if (!refreshToken) {
415
407
  throw new errors.InputError("Missing session cookie");
416
408
  }
417
- const scope = (_b = (_a = req.query.scope) == null ? void 0 : _a.toString()) != null ? _b : "";
409
+ let scope = (_b = (_a = req.query.scope) == null ? void 0 : _a.toString()) != null ? _b : "";
410
+ if (this.options.persistScopes) {
411
+ scope = this.getGrantedScopeFromCookie(req);
412
+ }
418
413
  const forwardReq = Object.assign(req, { scope, refreshToken });
419
414
  const { response, refreshToken: newRefreshToken } = await this.handlers.refresh(forwardReq);
420
415
  const backstageIdentity = await this.populateIdentity(response.backstageIdentity);
@@ -1154,6 +1149,8 @@ const createBitbucketProvider = (options) => {
1154
1149
  });
1155
1150
  };
1156
1151
 
1152
+ const ACCESS_TOKEN_PREFIX = "access-token.";
1153
+ const BACKSTAGE_SESSION_EXPIRATION = 3600;
1157
1154
  class GithubAuthProvider {
1158
1155
  constructor(options) {
1159
1156
  this.signInResolver = options.signInResolver;
@@ -1181,21 +1178,43 @@ class GithubAuthProvider {
1181
1178
  }
1182
1179
  async handler(req) {
1183
1180
  const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
1181
+ let refreshToken = privateInfo.refreshToken;
1182
+ if (!refreshToken && !result.params.expires_in) {
1183
+ refreshToken = ACCESS_TOKEN_PREFIX + result.accessToken;
1184
+ }
1184
1185
  return {
1185
1186
  response: await this.handleResult(result),
1186
- refreshToken: privateInfo.refreshToken
1187
+ refreshToken
1187
1188
  };
1188
1189
  }
1189
1190
  async refresh(req) {
1190
- const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1191
- const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
1191
+ const { scope, refreshToken } = req;
1192
+ if (refreshToken == null ? void 0 : refreshToken.startsWith(ACCESS_TOKEN_PREFIX)) {
1193
+ const accessToken = refreshToken.slice(ACCESS_TOKEN_PREFIX.length);
1194
+ const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken).catch((error) => {
1195
+ var _a;
1196
+ if (((_a = error.oauthError) == null ? void 0 : _a.statusCode) === 401) {
1197
+ throw new Error("Invalid access token");
1198
+ }
1199
+ throw error;
1200
+ });
1201
+ return {
1202
+ response: await this.handleResult({
1203
+ fullProfile,
1204
+ params: { scope },
1205
+ accessToken
1206
+ }),
1207
+ refreshToken
1208
+ };
1209
+ }
1210
+ const result = await executeRefreshTokenStrategy(this._strategy, refreshToken, scope);
1192
1211
  return {
1193
1212
  response: await this.handleResult({
1194
- fullProfile,
1195
- params,
1196
- accessToken
1213
+ fullProfile: await executeFetchUserProfileStrategy(this._strategy, result.accessToken),
1214
+ params: { ...result.params, scope },
1215
+ accessToken: result.accessToken
1197
1216
  }),
1198
- refreshToken
1217
+ refreshToken: result.refreshToken
1199
1218
  };
1200
1219
  }
1201
1220
  async handleResult(result) {
@@ -1206,21 +1225,28 @@ class GithubAuthProvider {
1206
1225
  };
1207
1226
  const { profile } = await this.authHandler(result, context);
1208
1227
  const expiresInStr = result.params.expires_in;
1209
- const response = {
1228
+ let expiresInSeconds = expiresInStr === void 0 ? void 0 : Number(expiresInStr);
1229
+ let backstageIdentity = void 0;
1230
+ if (this.signInResolver) {
1231
+ backstageIdentity = await this.signInResolver({
1232
+ result,
1233
+ profile
1234
+ }, context);
1235
+ if (expiresInSeconds) {
1236
+ expiresInSeconds = Math.min(expiresInSeconds, BACKSTAGE_SESSION_EXPIRATION);
1237
+ } else {
1238
+ expiresInSeconds = BACKSTAGE_SESSION_EXPIRATION;
1239
+ }
1240
+ }
1241
+ return {
1242
+ backstageIdentity,
1210
1243
  providerInfo: {
1211
1244
  accessToken: result.accessToken,
1212
1245
  scope: result.params.scope,
1213
- expiresInSeconds: expiresInStr === void 0 ? void 0 : Number(expiresInStr)
1246
+ expiresInSeconds
1214
1247
  },
1215
1248
  profile
1216
1249
  };
1217
- if (this.signInResolver) {
1218
- response.backstageIdentity = await this.signInResolver({
1219
- result,
1220
- profile
1221
- }, context);
1222
- }
1223
- return response;
1224
1250
  }
1225
1251
  }
1226
1252
  const githubDefaultSignInResolver = async (info, ctx) => {