@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 +61 -0
- package/config.d.ts +1 -1
- package/dist/index.cjs.js +600 -463
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +66 -18
- package/package.json +10 -8
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
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
|
|
8
|
+
var passportGithub2 = require('passport-github2');
|
|
9
|
+
var jwtDecoder = require('jwt-decode');
|
|
9
10
|
var errors = require('@backstage/errors');
|
|
10
|
-
var
|
|
11
|
+
var pickBy = require('lodash/pickBy');
|
|
11
12
|
var crypto = require('crypto');
|
|
12
13
|
var url = require('url');
|
|
13
|
-
var
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
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
|
-
|
|
508
|
-
|
|
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
|
-
|
|
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
|
|
514
|
-
return
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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 =
|
|
825
|
-
const tokenUrl =
|
|
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 = (
|
|
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
|
|
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
|
-
|
|
846
|
-
|
|
847
|
-
catalogIdentityClient,
|
|
848
|
-
logger,
|
|
849
|
-
tokenIssuer
|
|
1246
|
+
scope,
|
|
1247
|
+
logger
|
|
850
1248
|
});
|
|
851
1249
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
852
|
-
disableRefresh
|
|
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.
|
|
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
|
-
|
|
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;
|