@backstage/plugin-auth-backend 0.3.21 → 0.4.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,66 @@
1
1
  # @backstage/plugin-auth-backend
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 19f45179a5: Bump `passport-saml` to version 3. This is a breaking change, in that it [now requires](https://github.com/node-saml/passport-saml/pull/548) the `auth.saml.cert` parameter to be set. If you are not using SAML auth, you can ignore this.
8
+
9
+ To update your settings, add something similar to the following to your app-config:
10
+
11
+ ```yaml
12
+ auth:
13
+ saml:
14
+ # ... other settings ...
15
+ cert: 'MIICizCCAfQCCQCY8tKaMc0BMjANBgkqh ... W=='
16
+ ```
17
+
18
+ For more information, see the [library README](https://github.com/node-saml/passport-saml#security-and-signatures).
19
+
20
+ ### Patch Changes
21
+
22
+ - 560d6810f0: Fix a bug preventing an access token to be refreshed a second time with the GitLab provider.
23
+ - de5717872d: Use a more informative error message if the configured OIDC identity provider does not provide a `userinfo_endpoint` in its metadata.
24
+ - Updated dependencies
25
+ - @backstage/backend-common@0.9.3
26
+
27
+ ## 0.3.24
28
+
29
+ ### Patch Changes
30
+
31
+ - 2a105f451: Add a warning log message that `passport-saml` will require a `cert` config parameter imminently.
32
+
33
+ We intend to upgrade this package soon, past the point where we will start to strictly require the `auth.saml.cert` configuration parameter to be present. To avoid issues starting your auth backend, please
34
+
35
+ - 31892ee25: typo fix `tenentId` in Azure auth provider docs
36
+ - e9b1e2a9f: Added signIn and authHandler resolver for oAuth2 provider
37
+ - ca45b169d: Export GitHub to allow use with Identity resolver
38
+ - Updated dependencies
39
+ - @backstage/catalog-model@0.9.1
40
+ - @backstage/backend-common@0.9.1
41
+
42
+ ## 0.3.23
43
+
44
+ ### Patch Changes
45
+
46
+ - 392b36fa1: Added support for using authenticating via GitHub Apps in addition to GitHub OAuth Apps. It used to be possible to use GitHub Apps, but they did not handle session refresh correctly.
47
+
48
+ Note that GitHub Apps handle OAuth scope at the app installation level, meaning that the `scope` parameter for `getAccessToken` has no effect. When calling `getAccessToken` in open source plugins, one should still include the appropriate scope, but also document in the plugin README what scopes are required in the case of GitHub Apps.
49
+
50
+ In addition, the `authHandler` and `signInResolver` options have been implemented for the GitHub provider in the auth backend.
51
+
52
+ - ea9fe9567: Fixed a bug where OAuth state parameters would be serialized as the string `'undefined'`.
53
+ - 39fc3d7f8: Add Sign In and Handler resolver for GitLab provider
54
+ - Updated dependencies
55
+ - @backstage/backend-common@0.9.0
56
+ - @backstage/config@0.1.8
57
+
58
+ ## 0.3.22
59
+
60
+ ### Patch Changes
61
+
62
+ - 79d24a966: Fix an issue where the default app origin was not allowed to authenticate users.
63
+
3
64
  ## 0.3.21
4
65
 
5
66
  ### Patch Changes
package/config.d.ts CHANGED
@@ -48,7 +48,7 @@ export interface Config {
48
48
  entryPoint: string;
49
49
  logoutUrl?: string;
50
50
  issuer: string;
51
- cert?: string;
51
+ cert: string;
52
52
  privateKey?: string;
53
53
  decryptionPvk?: string;
54
54
  signatureAlgorithm?: 'sha256' | 'sha512';
package/dist/index.cjs.js CHANGED
@@ -5,18 +5,19 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var express = require('express');
6
6
  var Router = require('express-promise-router');
7
7
  var cookieParser = require('cookie-parser');
8
- var passportGoogleOauth20 = require('passport-google-oauth20');
8
+ var passportGithub2 = require('passport-github2');
9
+ var jwtDecoder = require('jwt-decode');
9
10
  var errors = require('@backstage/errors');
10
- var catalogModel = require('@backstage/catalog-model');
11
+ var pickBy = require('lodash/pickBy');
11
12
  var crypto = require('crypto');
12
13
  var url = require('url');
13
- var jwtDecoder = require('jwt-decode');
14
+ var catalogModel = require('@backstage/catalog-model');
15
+ var passportGitlab2 = require('passport-gitlab2');
16
+ var passportGoogleOauth20 = require('passport-google-oauth20');
14
17
  var passportMicrosoft = require('passport-microsoft');
15
18
  var got = require('got');
16
- var passportOktaOauth = require('passport-okta-oauth');
17
- var passportGithub2 = require('passport-github2');
18
- var passportGitlab2 = require('passport-gitlab2');
19
19
  var OAuth2Strategy = require('passport-oauth2');
20
+ var passportOktaOauth = require('passport-okta-oauth');
20
21
  var openidClient = require('openid-client');
21
22
  var passportSaml = require('passport-saml');
22
23
  var passportOneloginOauth = require('passport-onelogin-oauth');
@@ -56,9 +57,10 @@ function _interopNamespace(e) {
56
57
  var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
57
58
  var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router);
58
59
  var cookieParser__default = /*#__PURE__*/_interopDefaultLegacy(cookieParser);
60
+ var jwtDecoder__default = /*#__PURE__*/_interopDefaultLegacy(jwtDecoder);
61
+ var pickBy__default = /*#__PURE__*/_interopDefaultLegacy(pickBy);
59
62
  var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
60
63
  var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
61
- var jwtDecoder__default = /*#__PURE__*/_interopDefaultLegacy(jwtDecoder);
62
64
  var got__default = /*#__PURE__*/_interopDefaultLegacy(got);
63
65
  var OAuth2Strategy__default = /*#__PURE__*/_interopDefaultLegacy(OAuth2Strategy);
64
66
  var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
@@ -66,79 +68,118 @@ var NodeCache__default = /*#__PURE__*/_interopDefaultLegacy(NodeCache);
66
68
  var session__default = /*#__PURE__*/_interopDefaultLegacy(session);
67
69
  var passport__default = /*#__PURE__*/_interopDefaultLegacy(passport);
68
70
 
69
- class CatalogIdentityClient {
70
- constructor(options) {
71
- this.catalogApi = options.catalogApi;
72
- this.tokenIssuer = options.tokenIssuer;
71
+ const makeProfileInfo = (profile, idToken) => {
72
+ var _a, _b;
73
+ let email = void 0;
74
+ if (profile.emails && profile.emails.length > 0) {
75
+ const [firstEmail] = profile.emails;
76
+ email = firstEmail.value;
73
77
  }
74
- async findUser(query) {
75
- const filter = {
76
- kind: "user"
77
- };
78
- for (const [key, value] of Object.entries(query.annotations)) {
79
- filter[`metadata.annotations.${key}`] = value;
80
- }
81
- const token = await this.tokenIssuer.issueToken({
82
- claims: {sub: "backstage.io/auth-backend"}
83
- });
84
- const {items} = await this.catalogApi.getEntities({filter}, {token});
85
- if (items.length !== 1) {
86
- if (items.length > 1) {
87
- throw new errors.ConflictError("User lookup resulted in multiple matches");
88
- } else {
89
- throw new errors.NotFoundError("User not found");
90
- }
91
- }
92
- return items[0];
78
+ let picture = void 0;
79
+ if (profile.avatarUrl) {
80
+ picture = profile.avatarUrl;
81
+ } else if (profile.photos && profile.photos.length > 0) {
82
+ const [firstPhoto] = profile.photos;
83
+ picture = firstPhoto.value;
93
84
  }
94
- async resolveCatalogMembership({
95
- entityRefs,
96
- logger
97
- }) {
98
- const resolvedEntityRefs = entityRefs.map((ref) => {
99
- try {
100
- const parsedRef = catalogModel.parseEntityRef(ref.toLocaleLowerCase("en-US"), {
101
- defaultKind: "user",
102
- defaultNamespace: "default"
103
- });
104
- return parsedRef;
105
- } catch {
106
- logger == null ? void 0 : logger.warn(`Failed to parse entityRef from ${ref}, ignoring`);
107
- return null;
85
+ let displayName = (_b = (_a = profile.displayName) != null ? _a : profile.username) != null ? _b : profile.id;
86
+ if ((!email || !picture || !displayName) && idToken) {
87
+ try {
88
+ const decoded = jwtDecoder__default['default'](idToken);
89
+ if (!email && decoded.email) {
90
+ email = decoded.email;
108
91
  }
109
- }).filter((ref) => ref !== null);
110
- const filter = resolvedEntityRefs.map((ref) => ({
111
- kind: ref.kind,
112
- "metadata.namespace": ref.namespace,
113
- "metadata.name": ref.name
114
- }));
115
- const entities = await this.catalogApi.getEntities({filter}).then((r) => r.items);
116
- if (entityRefs.length !== entities.length) {
117
- const foundEntityNames = entities.map(catalogModel.stringifyEntityRef);
118
- const missingEntityNames = resolvedEntityRefs.map(catalogModel.stringifyEntityRef).filter((s) => !foundEntityNames.includes(s));
119
- logger == null ? void 0 : logger.debug(`Entities not found for refs ${missingEntityNames.join()}`);
92
+ if (!picture && decoded.picture) {
93
+ picture = decoded.picture;
94
+ }
95
+ if (!displayName && decoded.name) {
96
+ displayName = decoded.name;
97
+ }
98
+ } catch (e) {
99
+ throw new Error(`Failed to parse id token and get profile info, ${e}`);
120
100
  }
121
- const memberOf = entities.flatMap((e) => {
122
- var _a, _b;
123
- return (_b = (_a = e.relations) == null ? void 0 : _a.filter((r) => r.type === catalogModel.RELATION_MEMBER_OF).map((r) => r.target)) != null ? _b : [];
124
- });
125
- const newEntityRefs = [
126
- ...new Set(resolvedEntityRefs.concat(memberOf).map(catalogModel.stringifyEntityRef))
127
- ];
128
- logger == null ? void 0 : logger.debug(`Found catalog membership: ${newEntityRefs.join()}`);
129
- return newEntityRefs;
130
101
  }
131
- }
132
-
133
- function getEntityClaims(entity) {
134
- var _a, _b;
135
- const userRef = catalogModel.stringifyEntityRef(entity);
136
- const membershipRefs = (_b = (_a = entity.relations) == null ? void 0 : _a.filter((r) => r.type === catalogModel.RELATION_MEMBER_OF && r.target.kind.toLocaleLowerCase("en-US") === "group").map((r) => catalogModel.stringifyEntityRef(r.target))) != null ? _b : [];
137
102
  return {
138
- sub: userRef,
139
- ent: [userRef, ...membershipRefs]
103
+ email,
104
+ picture,
105
+ displayName
140
106
  };
141
- }
107
+ };
108
+ const executeRedirectStrategy = async (req, providerStrategy, options) => {
109
+ return new Promise((resolve) => {
110
+ const strategy = Object.create(providerStrategy);
111
+ strategy.redirect = (url, status) => {
112
+ resolve({url, status: status != null ? status : void 0});
113
+ };
114
+ strategy.authenticate(req, {...options});
115
+ });
116
+ };
117
+ const executeFrameHandlerStrategy = async (req, providerStrategy) => {
118
+ return new Promise((resolve, reject) => {
119
+ const strategy = Object.create(providerStrategy);
120
+ strategy.success = (result, privateInfo) => {
121
+ resolve({result, privateInfo});
122
+ };
123
+ strategy.fail = (info) => {
124
+ var _a;
125
+ reject(new Error(`Authentication rejected, ${(_a = info.message) != null ? _a : ""}`));
126
+ };
127
+ strategy.error = (error) => {
128
+ var _a;
129
+ let message = `Authentication failed, ${error.message}`;
130
+ if ((_a = error.oauthError) == null ? void 0 : _a.data) {
131
+ try {
132
+ const errorData = JSON.parse(error.oauthError.data);
133
+ if (errorData.message) {
134
+ message += ` - ${errorData.message}`;
135
+ }
136
+ } catch (parseError) {
137
+ message += ` - ${error.oauthError}`;
138
+ }
139
+ }
140
+ reject(new Error(message));
141
+ };
142
+ strategy.redirect = () => {
143
+ reject(new Error("Unexpected redirect"));
144
+ };
145
+ strategy.authenticate(req, {});
146
+ });
147
+ };
148
+ const executeRefreshTokenStrategy = async (providerStrategy, refreshToken, scope) => {
149
+ return new Promise((resolve, reject) => {
150
+ const anyStrategy = providerStrategy;
151
+ const OAuth2 = anyStrategy._oauth2.constructor;
152
+ const oauth2 = new OAuth2(anyStrategy._oauth2._clientId, anyStrategy._oauth2._clientSecret, anyStrategy._oauth2._baseSite, anyStrategy._oauth2._authorizeUrl, anyStrategy._refreshURL || anyStrategy._oauth2._accessTokenUrl, anyStrategy._oauth2._customHeaders);
153
+ oauth2.getOAuthAccessToken(refreshToken, {
154
+ scope,
155
+ grant_type: "refresh_token"
156
+ }, (err, accessToken, newRefreshToken, params) => {
157
+ if (err) {
158
+ reject(new Error(`Failed to refresh access token ${err.toString()}`));
159
+ }
160
+ if (!accessToken) {
161
+ reject(new Error(`Failed to refresh access token, no access token received`));
162
+ }
163
+ resolve({
164
+ accessToken,
165
+ refreshToken: newRefreshToken,
166
+ params
167
+ });
168
+ });
169
+ });
170
+ };
171
+ const executeFetchUserProfileStrategy = async (providerStrategy, accessToken) => {
172
+ return new Promise((resolve, reject) => {
173
+ const anyStrategy = providerStrategy;
174
+ anyStrategy.userProfile(accessToken, (error, rawProfile) => {
175
+ if (error) {
176
+ reject(error);
177
+ } else {
178
+ resolve(rawProfile);
179
+ }
180
+ });
181
+ });
182
+ };
142
183
 
143
184
  const readState = (stateString) => {
144
185
  var _a, _b;
@@ -149,7 +190,7 @@ const readState = (stateString) => {
149
190
  return state;
150
191
  };
151
192
  const encodeState = (state) => {
152
- const stateString = new URLSearchParams(state).toString();
193
+ const stateString = new URLSearchParams(pickBy__default['default'](state, (value) => value !== void 0)).toString();
153
194
  return Buffer.from(stateString, "utf-8").toString("hex");
154
195
  };
155
196
  const verifyNonce = (req, providerId) => {
@@ -366,10 +407,7 @@ class OAuthAdapter {
366
407
  const grantedScopes = this.getScopesFromCookie(req, this.options.providerId);
367
408
  response.providerInfo.scope = grantedScopes;
368
409
  }
369
- if (!this.options.disableRefresh) {
370
- if (!refreshToken) {
371
- throw new errors.InputError("Missing refresh token");
372
- }
410
+ if (refreshToken && !this.options.disableRefresh) {
373
411
  this.setRefreshTokenCookie(res, refreshToken);
374
412
  }
375
413
  await this.populateIdentity(response.backstageIdentity);
@@ -392,9 +430,7 @@ class OAuthAdapter {
392
430
  res.status(401).send("Invalid X-Requested-With header");
393
431
  return;
394
432
  }
395
- if (!this.options.disableRefresh) {
396
- this.removeRefreshTokenCookie(res);
397
- }
433
+ this.removeRefreshTokenCookie(res);
398
434
  res.status(200).send("logout!");
399
435
  }
400
436
  async refresh(req, res) {
@@ -436,112 +472,332 @@ class OAuthAdapter {
436
472
  }
437
473
  }
438
474
 
439
- const makeProfileInfo = (profile, idToken) => {
440
- let {displayName} = profile;
441
- let email = void 0;
442
- if (profile.emails && profile.emails.length > 0) {
443
- const [firstEmail] = profile.emails;
444
- email = firstEmail.value;
445
- }
446
- let picture = void 0;
447
- if (profile.photos && profile.photos.length > 0) {
448
- const [firstPhoto] = profile.photos;
449
- picture = firstPhoto.value;
475
+ class CatalogIdentityClient {
476
+ constructor(options) {
477
+ this.catalogApi = options.catalogApi;
478
+ this.tokenIssuer = options.tokenIssuer;
450
479
  }
451
- if ((!email || !picture || !displayName) && idToken) {
452
- try {
453
- const decoded = jwtDecoder__default['default'](idToken);
454
- if (!email && decoded.email) {
455
- email = decoded.email;
456
- }
457
- if (!picture && decoded.picture) {
458
- picture = decoded.picture;
459
- }
460
- if (!displayName && decoded.name) {
461
- displayName = decoded.name;
480
+ async findUser(query) {
481
+ const filter = {
482
+ kind: "user"
483
+ };
484
+ for (const [key, value] of Object.entries(query.annotations)) {
485
+ filter[`metadata.annotations.${key}`] = value;
486
+ }
487
+ const token = await this.tokenIssuer.issueToken({
488
+ claims: {sub: "backstage.io/auth-backend"}
489
+ });
490
+ const {items} = await this.catalogApi.getEntities({filter}, {token});
491
+ if (items.length !== 1) {
492
+ if (items.length > 1) {
493
+ throw new errors.ConflictError("User lookup resulted in multiple matches");
494
+ } else {
495
+ throw new errors.NotFoundError("User not found");
462
496
  }
463
- } catch (e) {
464
- throw new Error(`Failed to parse id token and get profile info, ${e}`);
465
497
  }
498
+ return items[0];
466
499
  }
467
- return {
468
- email,
469
- picture,
470
- displayName
471
- };
472
- };
473
- const executeRedirectStrategy = async (req, providerStrategy, options) => {
474
- return new Promise((resolve) => {
475
- const strategy = Object.create(providerStrategy);
476
- strategy.redirect = (url, status) => {
477
- resolve({url, status: status != null ? status : void 0});
478
- };
479
- strategy.authenticate(req, {...options});
480
- });
481
- };
482
- const executeFrameHandlerStrategy = async (req, providerStrategy) => {
483
- return new Promise((resolve, reject) => {
484
- const strategy = Object.create(providerStrategy);
485
- strategy.success = (result, privateInfo) => {
486
- resolve({result, privateInfo});
487
- };
488
- strategy.fail = (info) => {
489
- var _a;
490
- reject(new Error(`Authentication rejected, ${(_a = info.message) != null ? _a : ""}`));
491
- };
492
- strategy.error = (error) => {
493
- var _a;
494
- let message = `Authentication failed, ${error.message}`;
495
- if ((_a = error.oauthError) == null ? void 0 : _a.data) {
496
- try {
497
- const errorData = JSON.parse(error.oauthError.data);
498
- if (errorData.message) {
499
- message += ` - ${errorData.message}`;
500
- }
501
- } catch (parseError) {
502
- message += ` - ${error.oauthError}`;
503
- }
500
+ async resolveCatalogMembership({
501
+ entityRefs,
502
+ logger
503
+ }) {
504
+ const resolvedEntityRefs = entityRefs.map((ref) => {
505
+ try {
506
+ const parsedRef = catalogModel.parseEntityRef(ref.toLocaleLowerCase("en-US"), {
507
+ defaultKind: "user",
508
+ defaultNamespace: "default"
509
+ });
510
+ return parsedRef;
511
+ } catch {
512
+ logger == null ? void 0 : logger.warn(`Failed to parse entityRef from ${ref}, ignoring`);
513
+ return null;
504
514
  }
505
- reject(new Error(message));
515
+ }).filter((ref) => ref !== null);
516
+ const filter = resolvedEntityRefs.map((ref) => ({
517
+ kind: ref.kind,
518
+ "metadata.namespace": ref.namespace,
519
+ "metadata.name": ref.name
520
+ }));
521
+ const entities = await this.catalogApi.getEntities({filter}).then((r) => r.items);
522
+ if (entityRefs.length !== entities.length) {
523
+ const foundEntityNames = entities.map(catalogModel.stringifyEntityRef);
524
+ const missingEntityNames = resolvedEntityRefs.map(catalogModel.stringifyEntityRef).filter((s) => !foundEntityNames.includes(s));
525
+ logger == null ? void 0 : logger.debug(`Entities not found for refs ${missingEntityNames.join()}`);
526
+ }
527
+ const memberOf = entities.flatMap((e) => {
528
+ var _a, _b;
529
+ return (_b = (_a = e.relations) == null ? void 0 : _a.filter((r) => r.type === catalogModel.RELATION_MEMBER_OF).map((r) => r.target)) != null ? _b : [];
530
+ });
531
+ const newEntityRefs = [
532
+ ...new Set(resolvedEntityRefs.concat(memberOf).map(catalogModel.stringifyEntityRef))
533
+ ];
534
+ logger == null ? void 0 : logger.debug(`Found catalog membership: ${newEntityRefs.join()}`);
535
+ return newEntityRefs;
536
+ }
537
+ }
538
+
539
+ function getEntityClaims(entity) {
540
+ var _a, _b;
541
+ const userRef = catalogModel.stringifyEntityRef(entity);
542
+ const membershipRefs = (_b = (_a = entity.relations) == null ? void 0 : _a.filter((r) => r.type === catalogModel.RELATION_MEMBER_OF && r.target.kind.toLocaleLowerCase("en-US") === "group").map((r) => catalogModel.stringifyEntityRef(r.target))) != null ? _b : [];
543
+ return {
544
+ sub: userRef,
545
+ ent: [userRef, ...membershipRefs]
546
+ };
547
+ }
548
+
549
+ class GithubAuthProvider {
550
+ constructor(options) {
551
+ this.signInResolver = options.signInResolver;
552
+ this.authHandler = options.authHandler;
553
+ this.tokenIssuer = options.tokenIssuer;
554
+ this.catalogIdentityClient = options.catalogIdentityClient;
555
+ this.logger = options.logger;
556
+ this._strategy = new passportGithub2.Strategy({
557
+ clientID: options.clientId,
558
+ clientSecret: options.clientSecret,
559
+ callbackURL: options.callbackUrl,
560
+ tokenURL: options.tokenUrl,
561
+ userProfileURL: options.userProfileUrl,
562
+ authorizationURL: options.authorizationUrl
563
+ }, (accessToken, refreshToken, params, fullProfile, done) => {
564
+ done(void 0, {fullProfile, params, accessToken}, {refreshToken});
565
+ });
566
+ }
567
+ async start(req) {
568
+ return await executeRedirectStrategy(req, this._strategy, {
569
+ scope: req.scope,
570
+ state: encodeState(req.state)
571
+ });
572
+ }
573
+ async handler(req) {
574
+ const {result, privateInfo} = await executeFrameHandlerStrategy(req, this._strategy);
575
+ return {
576
+ response: await this.handleResult(result),
577
+ refreshToken: privateInfo.refreshToken
506
578
  };
507
- strategy.redirect = () => {
508
- reject(new Error("Unexpected redirect"));
579
+ }
580
+ async refresh(req) {
581
+ const {accessToken, params} = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
582
+ const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
583
+ return this.handleResult({
584
+ fullProfile,
585
+ params,
586
+ accessToken,
587
+ refreshToken: req.refreshToken
588
+ });
589
+ }
590
+ async handleResult(result) {
591
+ const {profile} = await this.authHandler(result);
592
+ const expiresInStr = result.params.expires_in;
593
+ const response = {
594
+ providerInfo: {
595
+ accessToken: result.accessToken,
596
+ scope: result.params.scope,
597
+ expiresInSeconds: expiresInStr === void 0 ? void 0 : Number(expiresInStr)
598
+ },
599
+ profile
509
600
  };
510
- strategy.authenticate(req, {});
601
+ if (this.signInResolver) {
602
+ response.backstageIdentity = await this.signInResolver({
603
+ result,
604
+ profile
605
+ }, {
606
+ tokenIssuer: this.tokenIssuer,
607
+ catalogIdentityClient: this.catalogIdentityClient,
608
+ logger: this.logger
609
+ });
610
+ }
611
+ return response;
612
+ }
613
+ }
614
+ const githubDefaultSignInResolver = async (info, ctx) => {
615
+ const {fullProfile} = info.result;
616
+ const userId = fullProfile.username || fullProfile.id;
617
+ const token = await ctx.tokenIssuer.issueToken({
618
+ claims: {sub: userId, ent: [`user:default/${userId}`]}
511
619
  });
620
+ return {id: userId, token};
512
621
  };
513
- const executeRefreshTokenStrategy = async (providerStrategy, refreshToken, scope) => {
514
- return new Promise((resolve, reject) => {
515
- const anyStrategy = providerStrategy;
516
- const OAuth2 = anyStrategy._oauth2.constructor;
517
- const oauth2 = new OAuth2(anyStrategy._oauth2._clientId, anyStrategy._oauth2._clientSecret, anyStrategy._oauth2._baseSite, anyStrategy._oauth2._authorizeUrl, anyStrategy._refreshURL || anyStrategy._oauth2._accessTokenUrl, anyStrategy._oauth2._customHeaders);
518
- oauth2.getOAuthAccessToken(refreshToken, {
519
- scope,
520
- grant_type: "refresh_token"
521
- }, (err, accessToken, newRefreshToken, params) => {
522
- if (err) {
523
- reject(new Error(`Failed to refresh access token ${err.toString()}`));
524
- }
525
- if (!accessToken) {
526
- reject(new Error(`Failed to refresh access token, no access token received`));
527
- }
528
- resolve({
529
- accessToken,
530
- refreshToken: newRefreshToken,
531
- params
532
- });
622
+ const createGithubProvider = (options) => {
623
+ return ({
624
+ providerId,
625
+ globalConfig,
626
+ config,
627
+ tokenIssuer,
628
+ catalogApi,
629
+ logger
630
+ }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
631
+ var _a, _b;
632
+ const clientId = envConfig.getString("clientId");
633
+ const clientSecret = envConfig.getString("clientSecret");
634
+ const enterpriseInstanceUrl = envConfig.getOptionalString("enterpriseInstanceUrl");
635
+ const authorizationUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/login/oauth/authorize` : void 0;
636
+ const tokenUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/login/oauth/access_token` : void 0;
637
+ const userProfileUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/api/v3/user` : void 0;
638
+ const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
639
+ const catalogIdentityClient = new CatalogIdentityClient({
640
+ catalogApi,
641
+ tokenIssuer
642
+ });
643
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({fullProfile}) => ({
644
+ profile: makeProfileInfo(fullProfile)
645
+ });
646
+ const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : githubDefaultSignInResolver;
647
+ const signInResolver = (info) => signInResolverFn(info, {
648
+ catalogIdentityClient,
649
+ tokenIssuer,
650
+ logger
651
+ });
652
+ const provider = new GithubAuthProvider({
653
+ clientId,
654
+ clientSecret,
655
+ callbackUrl,
656
+ tokenUrl,
657
+ userProfileUrl,
658
+ authorizationUrl,
659
+ signInResolver,
660
+ authHandler,
661
+ tokenIssuer,
662
+ catalogIdentityClient,
663
+ logger
664
+ });
665
+ return OAuthAdapter.fromConfig(globalConfig, provider, {
666
+ persistScopes: true,
667
+ providerId,
668
+ tokenIssuer
533
669
  });
534
670
  });
535
671
  };
536
- const executeFetchUserProfileStrategy = async (providerStrategy, accessToken) => {
537
- return new Promise((resolve, reject) => {
538
- const anyStrategy = providerStrategy;
539
- anyStrategy.userProfile(accessToken, (error, rawProfile) => {
540
- if (error) {
541
- reject(error);
542
- } else {
543
- resolve(rawProfile);
544
- }
672
+
673
+ const gitlabDefaultSignInResolver = async (info, ctx) => {
674
+ const {profile, result} = info;
675
+ let id = result.fullProfile.id;
676
+ if (profile.email) {
677
+ id = profile.email.split("@")[0];
678
+ }
679
+ const token = await ctx.tokenIssuer.issueToken({
680
+ claims: {sub: id, ent: [`user:default/${id}`]}
681
+ });
682
+ return {id, token};
683
+ };
684
+ const gitlabDefaultAuthHandler = async ({
685
+ fullProfile,
686
+ params
687
+ }) => ({
688
+ profile: makeProfileInfo(fullProfile, params.id_token)
689
+ });
690
+ class GitlabAuthProvider {
691
+ constructor(options) {
692
+ this.catalogIdentityClient = options.catalogIdentityClient;
693
+ this.logger = options.logger;
694
+ this.tokenIssuer = options.tokenIssuer;
695
+ this.authHandler = options.authHandler;
696
+ this.signInResolver = options.signInResolver;
697
+ this._strategy = new passportGitlab2.Strategy({
698
+ clientID: options.clientId,
699
+ clientSecret: options.clientSecret,
700
+ callbackURL: options.callbackUrl,
701
+ baseURL: options.baseUrl
702
+ }, (accessToken, refreshToken, params, fullProfile, done) => {
703
+ done(void 0, {fullProfile, params, accessToken}, {
704
+ refreshToken
705
+ });
706
+ });
707
+ }
708
+ async start(req) {
709
+ return await executeRedirectStrategy(req, this._strategy, {
710
+ scope: req.scope,
711
+ state: encodeState(req.state)
712
+ });
713
+ }
714
+ async handler(req) {
715
+ const {result, privateInfo} = await executeFrameHandlerStrategy(req, this._strategy);
716
+ return {
717
+ response: await this.handleResult(result),
718
+ refreshToken: privateInfo.refreshToken
719
+ };
720
+ }
721
+ async refresh(req) {
722
+ const {
723
+ accessToken,
724
+ refreshToken: newRefreshToken,
725
+ params
726
+ } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
727
+ const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
728
+ return this.handleResult({
729
+ fullProfile,
730
+ params,
731
+ accessToken,
732
+ refreshToken: newRefreshToken
733
+ });
734
+ }
735
+ async handleResult(result) {
736
+ const {profile} = await this.authHandler(result);
737
+ const response = {
738
+ providerInfo: {
739
+ idToken: result.params.id_token,
740
+ accessToken: result.accessToken,
741
+ refreshToken: result.refreshToken,
742
+ scope: result.params.scope,
743
+ expiresInSeconds: result.params.expires_in
744
+ },
745
+ profile
746
+ };
747
+ if (this.signInResolver) {
748
+ response.backstageIdentity = await this.signInResolver({
749
+ result,
750
+ profile
751
+ }, {
752
+ tokenIssuer: this.tokenIssuer,
753
+ catalogIdentityClient: this.catalogIdentityClient,
754
+ logger: this.logger
755
+ });
756
+ }
757
+ return response;
758
+ }
759
+ }
760
+ const createGitlabProvider = (options) => {
761
+ return ({
762
+ providerId,
763
+ globalConfig,
764
+ config,
765
+ tokenIssuer,
766
+ catalogApi,
767
+ logger
768
+ }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
769
+ var _a, _b, _c;
770
+ const clientId = envConfig.getString("clientId");
771
+ const clientSecret = envConfig.getString("clientSecret");
772
+ const audience = envConfig.getOptionalString("audience");
773
+ const baseUrl = audience || "https://gitlab.com";
774
+ const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
775
+ const catalogIdentityClient = new CatalogIdentityClient({
776
+ catalogApi,
777
+ tokenIssuer
778
+ });
779
+ const authHandler = (_a = options == null ? void 0 : options.authHandler) != null ? _a : gitlabDefaultAuthHandler;
780
+ const signInResolverFn = (_c = (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver) != null ? _c : gitlabDefaultSignInResolver;
781
+ const signInResolver = (info) => signInResolverFn(info, {
782
+ catalogIdentityClient,
783
+ tokenIssuer,
784
+ logger
785
+ });
786
+ const provider = new GitlabAuthProvider({
787
+ clientId,
788
+ clientSecret,
789
+ callbackUrl,
790
+ baseUrl,
791
+ authHandler,
792
+ signInResolver,
793
+ catalogIdentityClient,
794
+ logger,
795
+ tokenIssuer
796
+ });
797
+ return OAuthAdapter.fromConfig(globalConfig, provider, {
798
+ disableRefresh: false,
799
+ providerId,
800
+ tokenIssuer
545
801
  });
546
802
  });
547
803
  };
@@ -807,7 +1063,147 @@ const microsoftDefaultSignInResolver = async (info, ctx) => {
807
1063
  });
808
1064
  return {id: userId, token};
809
1065
  };
810
- const createMicrosoftProvider = (options) => {
1066
+ const createMicrosoftProvider = (options) => {
1067
+ return ({
1068
+ providerId,
1069
+ globalConfig,
1070
+ config,
1071
+ tokenIssuer,
1072
+ catalogApi,
1073
+ logger
1074
+ }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
1075
+ var _a, _b;
1076
+ const clientId = envConfig.getString("clientId");
1077
+ const clientSecret = envConfig.getString("clientSecret");
1078
+ const tenantId = envConfig.getString("tenantId");
1079
+ const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1080
+ const authorizationUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`;
1081
+ const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
1082
+ const catalogIdentityClient = new CatalogIdentityClient({
1083
+ catalogApi,
1084
+ tokenIssuer
1085
+ });
1086
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({fullProfile, params}) => ({
1087
+ profile: makeProfileInfo(fullProfile, params.id_token)
1088
+ });
1089
+ const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : microsoftDefaultSignInResolver;
1090
+ const signInResolver = (info) => signInResolverFn(info, {
1091
+ catalogIdentityClient,
1092
+ tokenIssuer,
1093
+ logger
1094
+ });
1095
+ const provider = new MicrosoftAuthProvider({
1096
+ clientId,
1097
+ clientSecret,
1098
+ callbackUrl,
1099
+ authorizationUrl,
1100
+ tokenUrl,
1101
+ authHandler,
1102
+ signInResolver,
1103
+ catalogIdentityClient,
1104
+ logger,
1105
+ tokenIssuer
1106
+ });
1107
+ return OAuthAdapter.fromConfig(globalConfig, provider, {
1108
+ disableRefresh: false,
1109
+ providerId,
1110
+ tokenIssuer
1111
+ });
1112
+ });
1113
+ };
1114
+
1115
+ class OAuth2AuthProvider {
1116
+ constructor(options) {
1117
+ this.signInResolver = options.signInResolver;
1118
+ this.authHandler = options.authHandler;
1119
+ this.tokenIssuer = options.tokenIssuer;
1120
+ this.catalogIdentityClient = options.catalogIdentityClient;
1121
+ this.logger = options.logger;
1122
+ this._strategy = new OAuth2Strategy.Strategy({
1123
+ clientID: options.clientId,
1124
+ clientSecret: options.clientSecret,
1125
+ callbackURL: options.callbackUrl,
1126
+ authorizationURL: options.authorizationUrl,
1127
+ tokenURL: options.tokenUrl,
1128
+ passReqToCallback: false,
1129
+ scope: options.scope
1130
+ }, (accessToken, refreshToken, params, fullProfile, done) => {
1131
+ done(void 0, {
1132
+ fullProfile,
1133
+ accessToken,
1134
+ refreshToken,
1135
+ params
1136
+ }, {
1137
+ refreshToken
1138
+ });
1139
+ });
1140
+ }
1141
+ async start(req) {
1142
+ return await executeRedirectStrategy(req, this._strategy, {
1143
+ accessType: "offline",
1144
+ prompt: "consent",
1145
+ scope: req.scope,
1146
+ state: encodeState(req.state)
1147
+ });
1148
+ }
1149
+ async handler(req) {
1150
+ const {result, privateInfo} = await executeFrameHandlerStrategy(req, this._strategy);
1151
+ return {
1152
+ response: await this.handleResult(result),
1153
+ refreshToken: privateInfo.refreshToken
1154
+ };
1155
+ }
1156
+ async refresh(req) {
1157
+ const refreshTokenResponse = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1158
+ const {
1159
+ accessToken,
1160
+ params,
1161
+ refreshToken: updatedRefreshToken
1162
+ } = refreshTokenResponse;
1163
+ const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
1164
+ return this.handleResult({
1165
+ fullProfile,
1166
+ params,
1167
+ accessToken,
1168
+ refreshToken: updatedRefreshToken
1169
+ });
1170
+ }
1171
+ async handleResult(result) {
1172
+ const {profile} = await this.authHandler(result);
1173
+ const response = {
1174
+ providerInfo: {
1175
+ idToken: result.params.id_token,
1176
+ accessToken: result.accessToken,
1177
+ scope: result.params.scope,
1178
+ expiresInSeconds: result.params.expires_in
1179
+ },
1180
+ profile
1181
+ };
1182
+ if (this.signInResolver) {
1183
+ response.backstageIdentity = await this.signInResolver({
1184
+ result,
1185
+ profile
1186
+ }, {
1187
+ tokenIssuer: this.tokenIssuer,
1188
+ catalogIdentityClient: this.catalogIdentityClient,
1189
+ logger: this.logger
1190
+ });
1191
+ }
1192
+ return response;
1193
+ }
1194
+ }
1195
+ const oAuth2DefaultSignInResolver = async (info, ctx) => {
1196
+ const {profile} = info;
1197
+ if (!profile.email) {
1198
+ throw new Error("Profile contained no email");
1199
+ }
1200
+ const userId = profile.email.split("@")[0];
1201
+ const token = await ctx.tokenIssuer.issueToken({
1202
+ claims: {sub: userId, ent: [`user:default/${userId}`]}
1203
+ });
1204
+ return {id: userId, token};
1205
+ };
1206
+ const createOAuth2Provider = (options) => {
811
1207
  return ({
812
1208
  providerId,
813
1209
  globalConfig,
@@ -816,13 +1212,14 @@ const createMicrosoftProvider = (options) => {
816
1212
  catalogApi,
817
1213
  logger
818
1214
  }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
819
- var _a, _b;
1215
+ var _a, _b, _c;
820
1216
  const clientId = envConfig.getString("clientId");
821
1217
  const clientSecret = envConfig.getString("clientSecret");
822
- const tenantId = envConfig.getString("tenantId");
823
1218
  const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
824
- const authorizationUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`;
825
- const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
1219
+ const authorizationUrl = envConfig.getString("authorizationUrl");
1220
+ const tokenUrl = envConfig.getString("tokenUrl");
1221
+ const scope = envConfig.getOptionalString("scope");
1222
+ const disableRefresh = (_a = envConfig.getOptionalBoolean("disableRefresh")) != null ? _a : false;
826
1223
  const catalogIdentityClient = new CatalogIdentityClient({
827
1224
  catalogApi,
828
1225
  tokenIssuer
@@ -830,26 +1227,27 @@ const createMicrosoftProvider = (options) => {
830
1227
  const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({fullProfile, params}) => ({
831
1228
  profile: makeProfileInfo(fullProfile, params.id_token)
832
1229
  });
833
- const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : microsoftDefaultSignInResolver;
1230
+ const signInResolverFn = (_c = (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver) != null ? _c : oAuth2DefaultSignInResolver;
834
1231
  const signInResolver = (info) => signInResolverFn(info, {
835
1232
  catalogIdentityClient,
836
1233
  tokenIssuer,
837
1234
  logger
838
1235
  });
839
- const provider = new MicrosoftAuthProvider({
1236
+ const provider = new OAuth2AuthProvider({
840
1237
  clientId,
841
1238
  clientSecret,
1239
+ tokenIssuer,
1240
+ catalogIdentityClient,
842
1241
  callbackUrl,
1242
+ signInResolver,
1243
+ authHandler,
843
1244
  authorizationUrl,
844
1245
  tokenUrl,
845
- authHandler,
846
- signInResolver,
847
- catalogIdentityClient,
848
- logger,
849
- tokenIssuer
1246
+ scope,
1247
+ logger
850
1248
  });
851
1249
  return OAuthAdapter.fromConfig(globalConfig, provider, {
852
- disableRefresh: false,
1250
+ disableRefresh,
853
1251
  providerId,
854
1252
  tokenIssuer
855
1253
  });
@@ -1010,273 +1408,6 @@ const createOktaProvider = (_options) => {
1010
1408
  });
1011
1409
  };
1012
1410
 
1013
- class GithubAuthProvider {
1014
- constructor(options) {
1015
- this._strategy = new passportGithub2.Strategy({
1016
- clientID: options.clientId,
1017
- clientSecret: options.clientSecret,
1018
- callbackURL: options.callbackUrl,
1019
- tokenURL: options.tokenUrl,
1020
- userProfileURL: options.userProfileUrl,
1021
- authorizationURL: options.authorizationUrl
1022
- }, (accessToken, _refreshToken, params, fullProfile, done) => {
1023
- done(void 0, {fullProfile, params, accessToken});
1024
- });
1025
- }
1026
- async start(req) {
1027
- return await executeRedirectStrategy(req, this._strategy, {
1028
- scope: req.scope,
1029
- state: encodeState(req.state)
1030
- });
1031
- }
1032
- async handler(req) {
1033
- const {
1034
- result: {fullProfile, accessToken, params}
1035
- } = await executeFrameHandlerStrategy(req, this._strategy);
1036
- const profile = makeProfileInfo({
1037
- ...fullProfile,
1038
- id: fullProfile.username || fullProfile.id,
1039
- displayName: fullProfile.displayName || fullProfile.username || fullProfile.id
1040
- }, params.id_token);
1041
- return {
1042
- response: {
1043
- profile,
1044
- providerInfo: {
1045
- accessToken,
1046
- scope: params.scope,
1047
- expiresInSeconds: params.expires_in
1048
- },
1049
- backstageIdentity: {
1050
- id: fullProfile.username || fullProfile.id
1051
- }
1052
- }
1053
- };
1054
- }
1055
- }
1056
- const createGithubProvider = (_options) => {
1057
- return ({providerId, globalConfig, config, tokenIssuer}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
1058
- const clientId = envConfig.getString("clientId");
1059
- const clientSecret = envConfig.getString("clientSecret");
1060
- const enterpriseInstanceUrl = envConfig.getOptionalString("enterpriseInstanceUrl");
1061
- const authorizationUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/login/oauth/authorize` : void 0;
1062
- const tokenUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/login/oauth/access_token` : void 0;
1063
- const userProfileUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/api/v3/user` : void 0;
1064
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1065
- const provider = new GithubAuthProvider({
1066
- clientId,
1067
- clientSecret,
1068
- callbackUrl,
1069
- tokenUrl,
1070
- userProfileUrl,
1071
- authorizationUrl
1072
- });
1073
- return OAuthAdapter.fromConfig(globalConfig, provider, {
1074
- disableRefresh: true,
1075
- persistScopes: true,
1076
- providerId,
1077
- tokenIssuer
1078
- });
1079
- });
1080
- };
1081
-
1082
- function transformProfile(fullProfile) {
1083
- var _a;
1084
- const profile = makeProfileInfo({
1085
- ...fullProfile,
1086
- photos: [
1087
- ...(_a = fullProfile.photos) != null ? _a : [],
1088
- ...fullProfile.avatarUrl ? [{value: fullProfile.avatarUrl}] : []
1089
- ]
1090
- });
1091
- let id = fullProfile.id;
1092
- if (profile.email) {
1093
- id = profile.email.split("@")[0];
1094
- }
1095
- return {id, profile};
1096
- }
1097
- class GitlabAuthProvider {
1098
- constructor(options) {
1099
- this._strategy = new passportGitlab2.Strategy({
1100
- clientID: options.clientId,
1101
- clientSecret: options.clientSecret,
1102
- callbackURL: options.callbackUrl,
1103
- baseURL: options.baseUrl
1104
- }, (accessToken, refreshToken, params, fullProfile, done) => {
1105
- done(void 0, {fullProfile, params, accessToken}, {
1106
- refreshToken
1107
- });
1108
- });
1109
- }
1110
- async start(req) {
1111
- return await executeRedirectStrategy(req, this._strategy, {
1112
- scope: req.scope,
1113
- state: encodeState(req.state)
1114
- });
1115
- }
1116
- async handler(req) {
1117
- const {result, privateInfo} = await executeFrameHandlerStrategy(req, this._strategy);
1118
- const {accessToken, params} = result;
1119
- const {id, profile} = transformProfile(result.fullProfile);
1120
- return {
1121
- response: {
1122
- profile,
1123
- providerInfo: {
1124
- accessToken,
1125
- scope: params.scope,
1126
- expiresInSeconds: params.expires_in,
1127
- idToken: params.id_token
1128
- },
1129
- backstageIdentity: {
1130
- id
1131
- }
1132
- },
1133
- refreshToken: privateInfo.refreshToken
1134
- };
1135
- }
1136
- async refresh(req) {
1137
- const {
1138
- accessToken,
1139
- refreshToken: newRefreshToken,
1140
- params
1141
- } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1142
- const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
1143
- const {id, profile} = transformProfile(fullProfile);
1144
- return {
1145
- profile,
1146
- providerInfo: {
1147
- accessToken,
1148
- refreshToken: newRefreshToken,
1149
- idToken: params.id_token,
1150
- expiresInSeconds: params.expires_in,
1151
- scope: params.scope
1152
- },
1153
- backstageIdentity: {
1154
- id
1155
- }
1156
- };
1157
- }
1158
- }
1159
- const createGitlabProvider = (_options) => {
1160
- return ({providerId, globalConfig, config, tokenIssuer}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
1161
- const clientId = envConfig.getString("clientId");
1162
- const clientSecret = envConfig.getString("clientSecret");
1163
- const audience = envConfig.getOptionalString("audience");
1164
- const baseUrl = audience || "https://gitlab.com";
1165
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1166
- const provider = new GitlabAuthProvider({
1167
- clientId,
1168
- clientSecret,
1169
- callbackUrl,
1170
- baseUrl
1171
- });
1172
- return OAuthAdapter.fromConfig(globalConfig, provider, {
1173
- disableRefresh: false,
1174
- providerId,
1175
- tokenIssuer
1176
- });
1177
- });
1178
- };
1179
-
1180
- class OAuth2AuthProvider {
1181
- constructor(options) {
1182
- this._strategy = new OAuth2Strategy.Strategy({
1183
- clientID: options.clientId,
1184
- clientSecret: options.clientSecret,
1185
- callbackURL: options.callbackUrl,
1186
- authorizationURL: options.authorizationUrl,
1187
- tokenURL: options.tokenUrl,
1188
- passReqToCallback: false,
1189
- scope: options.scope
1190
- }, (accessToken, refreshToken, params, fullProfile, done) => {
1191
- done(void 0, {
1192
- fullProfile,
1193
- accessToken,
1194
- refreshToken,
1195
- params
1196
- }, {
1197
- refreshToken
1198
- });
1199
- });
1200
- }
1201
- async start(req) {
1202
- return await executeRedirectStrategy(req, this._strategy, {
1203
- accessType: "offline",
1204
- prompt: "consent",
1205
- scope: req.scope,
1206
- state: encodeState(req.state)
1207
- });
1208
- }
1209
- async handler(req) {
1210
- const {result, privateInfo} = await executeFrameHandlerStrategy(req, this._strategy);
1211
- const profile = makeProfileInfo(result.fullProfile, result.params.id_token);
1212
- return {
1213
- response: await this.populateIdentity({
1214
- profile,
1215
- providerInfo: {
1216
- idToken: result.params.id_token,
1217
- accessToken: result.accessToken,
1218
- scope: result.params.scope,
1219
- expiresInSeconds: result.params.expires_in
1220
- }
1221
- }),
1222
- refreshToken: privateInfo.refreshToken
1223
- };
1224
- }
1225
- async refresh(req) {
1226
- const refreshTokenResponse = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1227
- const {
1228
- accessToken,
1229
- params,
1230
- refreshToken: updatedRefreshToken
1231
- } = refreshTokenResponse;
1232
- const rawProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
1233
- const profile = makeProfileInfo(rawProfile, params.id_token);
1234
- return this.populateIdentity({
1235
- providerInfo: {
1236
- accessToken,
1237
- refreshToken: updatedRefreshToken,
1238
- idToken: params.id_token,
1239
- expiresInSeconds: params.expires_in,
1240
- scope: params.scope
1241
- },
1242
- profile
1243
- });
1244
- }
1245
- async populateIdentity(response) {
1246
- const {profile} = response;
1247
- if (!profile.email) {
1248
- throw new Error("Profile does not contain an email");
1249
- }
1250
- const id = profile.email.split("@")[0];
1251
- return {...response, backstageIdentity: {id}};
1252
- }
1253
- }
1254
- const createOAuth2Provider = (_options) => {
1255
- return ({providerId, globalConfig, config, tokenIssuer}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
1256
- var _a;
1257
- const clientId = envConfig.getString("clientId");
1258
- const clientSecret = envConfig.getString("clientSecret");
1259
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1260
- const authorizationUrl = envConfig.getString("authorizationUrl");
1261
- const tokenUrl = envConfig.getString("tokenUrl");
1262
- const scope = envConfig.getOptionalString("scope");
1263
- const disableRefresh = (_a = envConfig.getOptionalBoolean("disableRefresh")) != null ? _a : false;
1264
- const provider = new OAuth2AuthProvider({
1265
- clientId,
1266
- clientSecret,
1267
- callbackUrl,
1268
- authorizationUrl,
1269
- tokenUrl,
1270
- scope
1271
- });
1272
- return OAuthAdapter.fromConfig(globalConfig, provider, {
1273
- disableRefresh,
1274
- providerId,
1275
- tokenIssuer
1276
- });
1277
- });
1278
- };
1279
-
1280
1411
  class OidcAuthProvider {
1281
1412
  constructor(options) {
1282
1413
  this.implementation = this.setupStrategy(options);
@@ -1353,6 +1484,9 @@ class OidcAuthProvider {
1353
1484
  client,
1354
1485
  passReqToCallback: false
1355
1486
  }, (tokenset, userinfo, done) => {
1487
+ if (typeof done !== "function") {
1488
+ throw new Error("OIDC IdP must provide a userinfo_endpoint in the metadata response");
1489
+ }
1356
1490
  done(void 0, {tokenset, userinfo}, {
1357
1491
  refreshToken: tokenset.refresh_token
1358
1492
  });
@@ -1449,7 +1583,7 @@ const createSamlProvider = (_options) => {
1449
1583
  entryPoint: config.getString("entryPoint"),
1450
1584
  logoutUrl: config.getOptionalString("logoutUrl"),
1451
1585
  issuer: config.getString("issuer"),
1452
- cert: config.getOptionalString("cert"),
1586
+ cert: config.getString("cert"),
1453
1587
  privateCert: config.getOptionalString("privateKey"),
1454
1588
  decryptionPvk: config.getOptionalString("decryptionPvk"),
1455
1589
  signatureAlgorithm: config.getOptionalString("signatureAlgorithm"),
@@ -1458,9 +1592,6 @@ const createSamlProvider = (_options) => {
1458
1592
  tokenIssuer,
1459
1593
  appUrl: globalConfig.appUrl
1460
1594
  };
1461
- if (!opts.cert) {
1462
- delete opts.cert;
1463
- }
1464
1595
  return new SamlAuthProvider(opts);
1465
1596
  };
1466
1597
  };
@@ -2040,12 +2171,15 @@ async function createRouter({
2040
2171
  return router;
2041
2172
  }
2042
2173
  function createOriginFilter(config) {
2174
+ var _a;
2175
+ const appUrl = config.getString("app.baseUrl");
2176
+ const {origin: appOrigin} = new URL(appUrl);
2043
2177
  const allowedOrigins = config.getOptionalStringArray("auth.experimentalExtraAllowedOrigins");
2044
- if (!allowedOrigins || allowedOrigins.length === 0) {
2045
- return () => false;
2046
- }
2047
- const allowedOriginPatterns = allowedOrigins.map((pattern) => new minimatch.Minimatch(pattern, {nocase: true, noglobstar: true}));
2178
+ const allowedOriginPatterns = (_a = allowedOrigins == null ? void 0 : allowedOrigins.map((pattern) => new minimatch.Minimatch(pattern, {nocase: true, noglobstar: true}))) != null ? _a : [];
2048
2179
  return (origin) => {
2180
+ if (origin === appOrigin) {
2181
+ return true;
2182
+ }
2049
2183
  return allowedOriginPatterns.some((pattern) => pattern.match(origin));
2050
2184
  };
2051
2185
  }
@@ -2053,8 +2187,11 @@ function createOriginFilter(config) {
2053
2187
  exports.IdentityClient = IdentityClient;
2054
2188
  exports.OAuthAdapter = OAuthAdapter;
2055
2189
  exports.OAuthEnvironmentHandler = OAuthEnvironmentHandler;
2190
+ exports.createGithubProvider = createGithubProvider;
2191
+ exports.createGitlabProvider = createGitlabProvider;
2056
2192
  exports.createGoogleProvider = createGoogleProvider;
2057
2193
  exports.createMicrosoftProvider = createMicrosoftProvider;
2194
+ exports.createOAuth2Provider = createOAuth2Provider;
2058
2195
  exports.createOktaProvider = createOktaProvider;
2059
2196
  exports.createOriginFilter = createOriginFilter;
2060
2197
  exports.createRouter = createRouter;