@backstage/plugin-auth-backend 0.5.1 → 0.6.2
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 +75 -0
- package/dist/index.cjs.js +1218 -1050
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +238 -133
- package/package.json +13 -11
package/dist/index.cjs.js
CHANGED
|
@@ -5,26 +5,26 @@ 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
|
|
9
|
-
var jwtDecoder = require('jwt-decode');
|
|
8
|
+
var OAuth2Strategy = require('passport-oauth2');
|
|
10
9
|
var errors = require('@backstage/errors');
|
|
11
10
|
var pickBy = require('lodash/pickBy');
|
|
12
11
|
var crypto = require('crypto');
|
|
13
12
|
var url = require('url');
|
|
14
13
|
var catalogModel = require('@backstage/catalog-model');
|
|
14
|
+
var jwtDecoder = require('jwt-decode');
|
|
15
|
+
var fetch = require('node-fetch');
|
|
16
|
+
var NodeCache = require('node-cache');
|
|
17
|
+
var jose = require('jose');
|
|
18
|
+
var passportBitbucketOauth2 = require('passport-bitbucket-oauth2');
|
|
19
|
+
var passportGithub2 = require('passport-github2');
|
|
15
20
|
var passportGitlab2 = require('passport-gitlab2');
|
|
16
21
|
var passportGoogleOauth20 = require('passport-google-oauth20');
|
|
17
22
|
var passportMicrosoft = require('passport-microsoft');
|
|
18
|
-
var got = require('got');
|
|
19
|
-
var OAuth2Strategy = require('passport-oauth2');
|
|
20
23
|
var openidClient = require('openid-client');
|
|
21
24
|
var passportOktaOauth = require('passport-okta-oauth');
|
|
22
|
-
var passportBitbucketOauth2 = require('passport-bitbucket-oauth2');
|
|
23
|
-
var fetch = require('node-fetch');
|
|
24
|
-
var NodeCache = require('node-cache');
|
|
25
|
-
var jose = require('jose');
|
|
26
|
-
var passportSaml = require('passport-saml');
|
|
27
25
|
var passportOneloginOauth = require('passport-onelogin-oauth');
|
|
26
|
+
var passportSaml = require('passport-saml');
|
|
27
|
+
var googleAuthLibrary = require('google-auth-library');
|
|
28
28
|
var catalogClient = require('@backstage/catalog-client');
|
|
29
29
|
var uuid = require('uuid');
|
|
30
30
|
var luxon = require('luxon');
|
|
@@ -58,129 +58,69 @@ function _interopNamespace(e) {
|
|
|
58
58
|
var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
|
|
59
59
|
var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router);
|
|
60
60
|
var cookieParser__default = /*#__PURE__*/_interopDefaultLegacy(cookieParser);
|
|
61
|
-
var
|
|
61
|
+
var OAuth2Strategy__default = /*#__PURE__*/_interopDefaultLegacy(OAuth2Strategy);
|
|
62
62
|
var pickBy__default = /*#__PURE__*/_interopDefaultLegacy(pickBy);
|
|
63
63
|
var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
|
|
64
64
|
var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
|
|
65
|
-
var
|
|
66
|
-
var OAuth2Strategy__default = /*#__PURE__*/_interopDefaultLegacy(OAuth2Strategy);
|
|
65
|
+
var jwtDecoder__default = /*#__PURE__*/_interopDefaultLegacy(jwtDecoder);
|
|
67
66
|
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
68
67
|
var NodeCache__default = /*#__PURE__*/_interopDefaultLegacy(NodeCache);
|
|
69
68
|
var session__default = /*#__PURE__*/_interopDefaultLegacy(session);
|
|
70
69
|
var passport__default = /*#__PURE__*/_interopDefaultLegacy(passport);
|
|
71
70
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
email = firstEmail.value;
|
|
78
|
-
}
|
|
79
|
-
let picture = void 0;
|
|
80
|
-
if (profile.avatarUrl) {
|
|
81
|
-
picture = profile.avatarUrl;
|
|
82
|
-
} else if (profile.photos && profile.photos.length > 0) {
|
|
83
|
-
const [firstPhoto] = profile.photos;
|
|
84
|
-
picture = firstPhoto.value;
|
|
85
|
-
}
|
|
86
|
-
let displayName = (_b = (_a = profile.displayName) != null ? _a : profile.username) != null ? _b : profile.id;
|
|
87
|
-
if ((!email || !picture || !displayName) && idToken) {
|
|
88
|
-
try {
|
|
89
|
-
const decoded = jwtDecoder__default["default"](idToken);
|
|
90
|
-
if (!email && decoded.email) {
|
|
91
|
-
email = decoded.email;
|
|
92
|
-
}
|
|
93
|
-
if (!picture && decoded.picture) {
|
|
94
|
-
picture = decoded.picture;
|
|
95
|
-
}
|
|
96
|
-
if (!displayName && decoded.name) {
|
|
97
|
-
displayName = decoded.name;
|
|
98
|
-
}
|
|
99
|
-
} catch (e) {
|
|
100
|
-
throw new Error(`Failed to parse id token and get profile info, ${e}`);
|
|
71
|
+
const defaultScopes = ["offline_access", "read:me"];
|
|
72
|
+
class AtlassianStrategy extends OAuth2Strategy__default["default"] {
|
|
73
|
+
constructor(options, verify) {
|
|
74
|
+
if (!options.scope) {
|
|
75
|
+
throw new TypeError("Atlassian requires a scope option");
|
|
101
76
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
};
|
|
109
|
-
const executeRedirectStrategy = async (req, providerStrategy, options) => {
|
|
110
|
-
return new Promise((resolve) => {
|
|
111
|
-
const strategy = Object.create(providerStrategy);
|
|
112
|
-
strategy.redirect = (url, status) => {
|
|
113
|
-
resolve({ url, status: status != null ? status : void 0 });
|
|
114
|
-
};
|
|
115
|
-
strategy.authenticate(req, { ...options });
|
|
116
|
-
});
|
|
117
|
-
};
|
|
118
|
-
const executeFrameHandlerStrategy = async (req, providerStrategy) => {
|
|
119
|
-
return new Promise((resolve, reject) => {
|
|
120
|
-
const strategy = Object.create(providerStrategy);
|
|
121
|
-
strategy.success = (result, privateInfo) => {
|
|
122
|
-
resolve({ result, privateInfo });
|
|
123
|
-
};
|
|
124
|
-
strategy.fail = (info) => {
|
|
125
|
-
var _a;
|
|
126
|
-
reject(new Error(`Authentication rejected, ${(_a = info.message) != null ? _a : ""}`));
|
|
127
|
-
};
|
|
128
|
-
strategy.error = (error) => {
|
|
129
|
-
var _a;
|
|
130
|
-
let message = `Authentication failed, ${error.message}`;
|
|
131
|
-
if ((_a = error.oauthError) == null ? void 0 : _a.data) {
|
|
132
|
-
try {
|
|
133
|
-
const errorData = JSON.parse(error.oauthError.data);
|
|
134
|
-
if (errorData.message) {
|
|
135
|
-
message += ` - ${errorData.message}`;
|
|
136
|
-
}
|
|
137
|
-
} catch (parseError) {
|
|
138
|
-
message += ` - ${error.oauthError}`;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
reject(new Error(message));
|
|
77
|
+
const scopes = options.scope.split(" ");
|
|
78
|
+
const optionsWithURLs = {
|
|
79
|
+
...options,
|
|
80
|
+
authorizationURL: `https://auth.atlassian.com/authorize`,
|
|
81
|
+
tokenURL: `https://auth.atlassian.com/oauth/token`,
|
|
82
|
+
scope: Array.from(/* @__PURE__ */ new Set([...defaultScopes, ...scopes]))
|
|
142
83
|
};
|
|
143
|
-
|
|
144
|
-
|
|
84
|
+
super(optionsWithURLs, verify);
|
|
85
|
+
this.profileURL = "https://api.atlassian.com/me";
|
|
86
|
+
this.name = "atlassian";
|
|
87
|
+
this._oauth2.useAuthorizationHeaderforGET(true);
|
|
88
|
+
}
|
|
89
|
+
authorizationParams() {
|
|
90
|
+
return {
|
|
91
|
+
audience: "api.atlassian.com",
|
|
92
|
+
prompt: "consent"
|
|
145
93
|
};
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const executeRefreshTokenStrategy = async (providerStrategy, refreshToken, scope) => {
|
|
150
|
-
return new Promise((resolve, reject) => {
|
|
151
|
-
const anyStrategy = providerStrategy;
|
|
152
|
-
const OAuth2 = anyStrategy._oauth2.constructor;
|
|
153
|
-
const oauth2 = new OAuth2(anyStrategy._oauth2._clientId, anyStrategy._oauth2._clientSecret, anyStrategy._oauth2._baseSite, anyStrategy._oauth2._authorizeUrl, anyStrategy._refreshURL || anyStrategy._oauth2._accessTokenUrl, anyStrategy._oauth2._customHeaders);
|
|
154
|
-
oauth2.getOAuthAccessToken(refreshToken, {
|
|
155
|
-
scope,
|
|
156
|
-
grant_type: "refresh_token"
|
|
157
|
-
}, (err, accessToken, newRefreshToken, params) => {
|
|
94
|
+
}
|
|
95
|
+
userProfile(accessToken, done) {
|
|
96
|
+
this._oauth2.get(this.profileURL, accessToken, (err, body) => {
|
|
158
97
|
if (err) {
|
|
159
|
-
|
|
98
|
+
return done(new OAuth2Strategy.InternalOAuthError("Failed to fetch user profile", err.statusCode));
|
|
160
99
|
}
|
|
161
|
-
if (!
|
|
162
|
-
|
|
100
|
+
if (!body) {
|
|
101
|
+
return done(new Error("Failed to fetch user profile, body cannot be empty"));
|
|
163
102
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
});
|
|
171
|
-
};
|
|
172
|
-
const executeFetchUserProfileStrategy = async (providerStrategy, accessToken) => {
|
|
173
|
-
return new Promise((resolve, reject) => {
|
|
174
|
-
const anyStrategy = providerStrategy;
|
|
175
|
-
anyStrategy.userProfile(accessToken, (error, rawProfile) => {
|
|
176
|
-
if (error) {
|
|
177
|
-
reject(error);
|
|
178
|
-
} else {
|
|
179
|
-
resolve(rawProfile);
|
|
103
|
+
try {
|
|
104
|
+
const json = typeof body !== "string" ? body.toString() : body;
|
|
105
|
+
const profile = AtlassianStrategy.parse(json);
|
|
106
|
+
return done(null, profile);
|
|
107
|
+
} catch (e) {
|
|
108
|
+
return done(new Error("Failed to parse user profile"));
|
|
180
109
|
}
|
|
181
110
|
});
|
|
182
|
-
}
|
|
183
|
-
|
|
111
|
+
}
|
|
112
|
+
static parse(json) {
|
|
113
|
+
const resp = JSON.parse(json);
|
|
114
|
+
return {
|
|
115
|
+
id: resp.account_id,
|
|
116
|
+
provider: "atlassian",
|
|
117
|
+
username: resp.nickname,
|
|
118
|
+
displayName: resp.name,
|
|
119
|
+
emails: [{ value: resp.email }],
|
|
120
|
+
photos: [{ value: resp.picture }]
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
184
124
|
|
|
185
125
|
const readState = (stateString) => {
|
|
186
126
|
var _a, _b;
|
|
@@ -462,10 +402,10 @@ class OAuthAdapter {
|
|
|
462
402
|
}
|
|
463
403
|
const scope = (_b = (_a = req.query.scope) == null ? void 0 : _a.toString()) != null ? _b : "";
|
|
464
404
|
const forwardReq = Object.assign(req, { scope, refreshToken });
|
|
465
|
-
const response = await this.handlers.refresh(forwardReq);
|
|
405
|
+
const { response, refreshToken: newRefreshToken } = await this.handlers.refresh(forwardReq);
|
|
466
406
|
const backstageIdentity = await this.populateIdentity(response.backstageIdentity);
|
|
467
|
-
if (
|
|
468
|
-
this.setRefreshTokenCookie(res,
|
|
407
|
+
if (newRefreshToken && newRefreshToken !== refreshToken) {
|
|
408
|
+
this.setRefreshTokenCookie(res, newRefreshToken);
|
|
469
409
|
}
|
|
470
410
|
res.status(200).json({ ...response, backstageIdentity });
|
|
471
411
|
} catch (error) {
|
|
@@ -479,78 +419,195 @@ class OAuthAdapter {
|
|
|
479
419
|
if (identity.token) {
|
|
480
420
|
return prepareBackstageIdentityResponse(identity);
|
|
481
421
|
}
|
|
422
|
+
const userEntityRef = catalogModel.stringifyEntityRef(catalogModel.parseEntityRef(identity.id, {
|
|
423
|
+
defaultKind: "user",
|
|
424
|
+
defaultNamespace: catalogModel.ENTITY_DEFAULT_NAMESPACE
|
|
425
|
+
}));
|
|
482
426
|
const token = await this.options.tokenIssuer.issueToken({
|
|
483
|
-
claims: { sub:
|
|
427
|
+
claims: { sub: userEntityRef }
|
|
484
428
|
});
|
|
485
429
|
return prepareBackstageIdentityResponse({ ...identity, token });
|
|
486
430
|
}
|
|
487
431
|
}
|
|
488
432
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
433
|
+
const makeProfileInfo = (profile, idToken) => {
|
|
434
|
+
var _a, _b;
|
|
435
|
+
let email = void 0;
|
|
436
|
+
if (profile.emails && profile.emails.length > 0) {
|
|
437
|
+
const [firstEmail] = profile.emails;
|
|
438
|
+
email = firstEmail.value;
|
|
493
439
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
const token = await this.tokenIssuer.issueToken({
|
|
502
|
-
claims: { sub: "backstage.io/auth-backend" }
|
|
503
|
-
});
|
|
504
|
-
const { items } = await this.catalogApi.getEntities({ filter }, { token });
|
|
505
|
-
if (items.length !== 1) {
|
|
506
|
-
if (items.length > 1) {
|
|
507
|
-
throw new errors.ConflictError("User lookup resulted in multiple matches");
|
|
508
|
-
} else {
|
|
509
|
-
throw new errors.NotFoundError("User not found");
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
return items[0];
|
|
440
|
+
let picture = void 0;
|
|
441
|
+
if (profile.avatarUrl) {
|
|
442
|
+
picture = profile.avatarUrl;
|
|
443
|
+
} else if (profile.photos && profile.photos.length > 0) {
|
|
444
|
+
const [firstPhoto] = profile.photos;
|
|
445
|
+
picture = firstPhoto.value;
|
|
513
446
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
defaultNamespace: "default"
|
|
521
|
-
});
|
|
522
|
-
return parsedRef;
|
|
523
|
-
} catch {
|
|
524
|
-
logger == null ? void 0 : logger.warn(`Failed to parse entityRef from ${ref}, ignoring`);
|
|
525
|
-
return null;
|
|
447
|
+
let displayName = (_b = (_a = profile.displayName) != null ? _a : profile.username) != null ? _b : profile.id;
|
|
448
|
+
if ((!email || !picture || !displayName) && idToken) {
|
|
449
|
+
try {
|
|
450
|
+
const decoded = jwtDecoder__default["default"](idToken);
|
|
451
|
+
if (!email && decoded.email) {
|
|
452
|
+
email = decoded.email;
|
|
526
453
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const foundEntityNames = entities.map(catalogModel.stringifyEntityRef);
|
|
536
|
-
const missingEntityNames = resolvedEntityRefs.map(catalogModel.stringifyEntityRef).filter((s) => !foundEntityNames.includes(s));
|
|
537
|
-
logger == null ? void 0 : logger.debug(`Entities not found for refs ${missingEntityNames.join()}`);
|
|
454
|
+
if (!picture && decoded.picture) {
|
|
455
|
+
picture = decoded.picture;
|
|
456
|
+
}
|
|
457
|
+
if (!displayName && decoded.name) {
|
|
458
|
+
displayName = decoded.name;
|
|
459
|
+
}
|
|
460
|
+
} catch (e) {
|
|
461
|
+
throw new Error(`Failed to parse id token and get profile info, ${e}`);
|
|
538
462
|
}
|
|
539
|
-
const memberOf = entities.flatMap((e) => {
|
|
540
|
-
var _a, _b;
|
|
541
|
-
return (_b = (_a = e.relations) == null ? void 0 : _a.filter((r) => r.type === catalogModel.RELATION_MEMBER_OF).map((r) => r.target)) != null ? _b : [];
|
|
542
|
-
});
|
|
543
|
-
const newEntityRefs = [
|
|
544
|
-
...new Set(resolvedEntityRefs.concat(memberOf).map(catalogModel.stringifyEntityRef))
|
|
545
|
-
];
|
|
546
|
-
logger == null ? void 0 : logger.debug(`Found catalog membership: ${newEntityRefs.join()}`);
|
|
547
|
-
return newEntityRefs;
|
|
548
463
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
464
|
+
return {
|
|
465
|
+
email,
|
|
466
|
+
picture,
|
|
467
|
+
displayName
|
|
468
|
+
};
|
|
469
|
+
};
|
|
470
|
+
const executeRedirectStrategy = async (req, providerStrategy, options) => {
|
|
471
|
+
return new Promise((resolve) => {
|
|
472
|
+
const strategy = Object.create(providerStrategy);
|
|
473
|
+
strategy.redirect = (url, status) => {
|
|
474
|
+
resolve({ url, status: status != null ? status : void 0 });
|
|
475
|
+
};
|
|
476
|
+
strategy.authenticate(req, { ...options });
|
|
477
|
+
});
|
|
478
|
+
};
|
|
479
|
+
const executeFrameHandlerStrategy = async (req, providerStrategy) => {
|
|
480
|
+
return new Promise((resolve, reject) => {
|
|
481
|
+
const strategy = Object.create(providerStrategy);
|
|
482
|
+
strategy.success = (result, privateInfo) => {
|
|
483
|
+
resolve({ result, privateInfo });
|
|
484
|
+
};
|
|
485
|
+
strategy.fail = (info) => {
|
|
486
|
+
var _a;
|
|
487
|
+
reject(new Error(`Authentication rejected, ${(_a = info.message) != null ? _a : ""}`));
|
|
488
|
+
};
|
|
489
|
+
strategy.error = (error) => {
|
|
490
|
+
var _a;
|
|
491
|
+
let message = `Authentication failed, ${error.message}`;
|
|
492
|
+
if ((_a = error.oauthError) == null ? void 0 : _a.data) {
|
|
493
|
+
try {
|
|
494
|
+
const errorData = JSON.parse(error.oauthError.data);
|
|
495
|
+
if (errorData.message) {
|
|
496
|
+
message += ` - ${errorData.message}`;
|
|
497
|
+
}
|
|
498
|
+
} catch (parseError) {
|
|
499
|
+
message += ` - ${error.oauthError}`;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
reject(new Error(message));
|
|
503
|
+
};
|
|
504
|
+
strategy.redirect = () => {
|
|
505
|
+
reject(new Error("Unexpected redirect"));
|
|
506
|
+
};
|
|
507
|
+
strategy.authenticate(req, {});
|
|
508
|
+
});
|
|
509
|
+
};
|
|
510
|
+
const executeRefreshTokenStrategy = async (providerStrategy, refreshToken, scope) => {
|
|
511
|
+
return new Promise((resolve, reject) => {
|
|
512
|
+
const anyStrategy = providerStrategy;
|
|
513
|
+
const OAuth2 = anyStrategy._oauth2.constructor;
|
|
514
|
+
const oauth2 = new OAuth2(anyStrategy._oauth2._clientId, anyStrategy._oauth2._clientSecret, anyStrategy._oauth2._baseSite, anyStrategy._oauth2._authorizeUrl, anyStrategy._refreshURL || anyStrategy._oauth2._accessTokenUrl, anyStrategy._oauth2._customHeaders);
|
|
515
|
+
oauth2.getOAuthAccessToken(refreshToken, {
|
|
516
|
+
scope,
|
|
517
|
+
grant_type: "refresh_token"
|
|
518
|
+
}, (err, accessToken, newRefreshToken, params) => {
|
|
519
|
+
if (err) {
|
|
520
|
+
reject(new Error(`Failed to refresh access token ${err.toString()}`));
|
|
521
|
+
}
|
|
522
|
+
if (!accessToken) {
|
|
523
|
+
reject(new Error(`Failed to refresh access token, no access token received`));
|
|
524
|
+
}
|
|
525
|
+
resolve({
|
|
526
|
+
accessToken,
|
|
527
|
+
refreshToken: newRefreshToken,
|
|
528
|
+
params
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
};
|
|
533
|
+
const executeFetchUserProfileStrategy = async (providerStrategy, accessToken) => {
|
|
534
|
+
return new Promise((resolve, reject) => {
|
|
535
|
+
const anyStrategy = providerStrategy;
|
|
536
|
+
anyStrategy.userProfile(accessToken, (error, rawProfile) => {
|
|
537
|
+
if (error) {
|
|
538
|
+
reject(error);
|
|
539
|
+
} else {
|
|
540
|
+
resolve(rawProfile);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
class CatalogIdentityClient {
|
|
547
|
+
constructor(options) {
|
|
548
|
+
this.catalogApi = options.catalogApi;
|
|
549
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
550
|
+
}
|
|
551
|
+
async findUser(query) {
|
|
552
|
+
const filter = {
|
|
553
|
+
kind: "user"
|
|
554
|
+
};
|
|
555
|
+
for (const [key, value] of Object.entries(query.annotations)) {
|
|
556
|
+
filter[`metadata.annotations.${key}`] = value;
|
|
557
|
+
}
|
|
558
|
+
const token = await this.tokenIssuer.issueToken({
|
|
559
|
+
claims: { sub: "backstage.io/auth-backend" }
|
|
560
|
+
});
|
|
561
|
+
const { items } = await this.catalogApi.getEntities({ filter }, { token });
|
|
562
|
+
if (items.length !== 1) {
|
|
563
|
+
if (items.length > 1) {
|
|
564
|
+
throw new errors.ConflictError("User lookup resulted in multiple matches");
|
|
565
|
+
} else {
|
|
566
|
+
throw new errors.NotFoundError("User not found");
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return items[0];
|
|
570
|
+
}
|
|
571
|
+
async resolveCatalogMembership(query) {
|
|
572
|
+
const { entityRefs, logger } = query;
|
|
573
|
+
const resolvedEntityRefs = entityRefs.map((ref) => {
|
|
574
|
+
try {
|
|
575
|
+
const parsedRef = catalogModel.parseEntityRef(ref.toLocaleLowerCase("en-US"), {
|
|
576
|
+
defaultKind: "user",
|
|
577
|
+
defaultNamespace: "default"
|
|
578
|
+
});
|
|
579
|
+
return parsedRef;
|
|
580
|
+
} catch {
|
|
581
|
+
logger == null ? void 0 : logger.warn(`Failed to parse entityRef from ${ref}, ignoring`);
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
}).filter((ref) => ref !== null);
|
|
585
|
+
const filter = resolvedEntityRefs.map((ref) => ({
|
|
586
|
+
kind: ref.kind,
|
|
587
|
+
"metadata.namespace": ref.namespace,
|
|
588
|
+
"metadata.name": ref.name
|
|
589
|
+
}));
|
|
590
|
+
const entities = await this.catalogApi.getEntities({ filter }).then((r) => r.items);
|
|
591
|
+
if (entityRefs.length !== entities.length) {
|
|
592
|
+
const foundEntityNames = entities.map(catalogModel.stringifyEntityRef);
|
|
593
|
+
const missingEntityNames = resolvedEntityRefs.map(catalogModel.stringifyEntityRef).filter((s) => !foundEntityNames.includes(s));
|
|
594
|
+
logger == null ? void 0 : logger.debug(`Entities not found for refs ${missingEntityNames.join()}`);
|
|
595
|
+
}
|
|
596
|
+
const memberOf = entities.flatMap((e) => {
|
|
597
|
+
var _a, _b;
|
|
598
|
+
return (_b = (_a = e.relations) == null ? void 0 : _a.filter((r) => r.type === catalogModel.RELATION_MEMBER_OF).map((r) => r.target)) != null ? _b : [];
|
|
599
|
+
});
|
|
600
|
+
const newEntityRefs = [
|
|
601
|
+
...new Set(resolvedEntityRefs.concat(memberOf).map(catalogModel.stringifyEntityRef))
|
|
602
|
+
];
|
|
603
|
+
logger == null ? void 0 : logger.debug(`Found catalog membership: ${newEntityRefs.join()}`);
|
|
604
|
+
return newEntityRefs;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function getEntityClaims(entity) {
|
|
609
|
+
var _a, _b;
|
|
610
|
+
const userRef = catalogModel.stringifyEntityRef(entity);
|
|
554
611
|
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 : [];
|
|
555
612
|
return {
|
|
556
613
|
sub: userRef,
|
|
@@ -558,29 +615,162 @@ function getEntityClaims(entity) {
|
|
|
558
615
|
};
|
|
559
616
|
}
|
|
560
617
|
|
|
561
|
-
|
|
618
|
+
const atlassianDefaultAuthHandler = async ({
|
|
619
|
+
fullProfile,
|
|
620
|
+
params
|
|
621
|
+
}) => ({
|
|
622
|
+
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
623
|
+
});
|
|
624
|
+
class AtlassianAuthProvider {
|
|
625
|
+
constructor(options) {
|
|
626
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
627
|
+
this.logger = options.logger;
|
|
628
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
629
|
+
this.authHandler = options.authHandler;
|
|
630
|
+
this.signInResolver = options.signInResolver;
|
|
631
|
+
this._strategy = new AtlassianStrategy({
|
|
632
|
+
clientID: options.clientId,
|
|
633
|
+
clientSecret: options.clientSecret,
|
|
634
|
+
callbackURL: options.callbackUrl,
|
|
635
|
+
scope: options.scopes
|
|
636
|
+
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
637
|
+
done(void 0, {
|
|
638
|
+
fullProfile,
|
|
639
|
+
accessToken,
|
|
640
|
+
refreshToken,
|
|
641
|
+
params
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
async start(req) {
|
|
646
|
+
return await executeRedirectStrategy(req, this._strategy, {
|
|
647
|
+
state: encodeState(req.state)
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
async handler(req) {
|
|
651
|
+
const { result } = await executeFrameHandlerStrategy(req, this._strategy);
|
|
652
|
+
return {
|
|
653
|
+
response: await this.handleResult(result),
|
|
654
|
+
refreshToken: result.refreshToken
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
async handleResult(result) {
|
|
658
|
+
const { profile } = await this.authHandler(result);
|
|
659
|
+
const response = {
|
|
660
|
+
providerInfo: {
|
|
661
|
+
idToken: result.params.id_token,
|
|
662
|
+
accessToken: result.accessToken,
|
|
663
|
+
scope: result.params.scope,
|
|
664
|
+
expiresInSeconds: result.params.expires_in
|
|
665
|
+
},
|
|
666
|
+
profile
|
|
667
|
+
};
|
|
668
|
+
if (this.signInResolver) {
|
|
669
|
+
response.backstageIdentity = await this.signInResolver({
|
|
670
|
+
result,
|
|
671
|
+
profile
|
|
672
|
+
}, {
|
|
673
|
+
tokenIssuer: this.tokenIssuer,
|
|
674
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
675
|
+
logger: this.logger
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
return response;
|
|
679
|
+
}
|
|
680
|
+
async refresh(req) {
|
|
681
|
+
const { accessToken, params, refreshToken } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
682
|
+
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
683
|
+
return {
|
|
684
|
+
response: await this.handleResult({
|
|
685
|
+
fullProfile,
|
|
686
|
+
params,
|
|
687
|
+
accessToken
|
|
688
|
+
}),
|
|
689
|
+
refreshToken
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
const createAtlassianProvider = (options) => {
|
|
694
|
+
return ({
|
|
695
|
+
providerId,
|
|
696
|
+
globalConfig,
|
|
697
|
+
config,
|
|
698
|
+
tokenIssuer,
|
|
699
|
+
catalogApi,
|
|
700
|
+
logger
|
|
701
|
+
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
702
|
+
var _a, _b;
|
|
703
|
+
const clientId = envConfig.getString("clientId");
|
|
704
|
+
const clientSecret = envConfig.getString("clientSecret");
|
|
705
|
+
const scopes = envConfig.getString("scopes");
|
|
706
|
+
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
707
|
+
const catalogIdentityClient = new CatalogIdentityClient({
|
|
708
|
+
catalogApi,
|
|
709
|
+
tokenIssuer
|
|
710
|
+
});
|
|
711
|
+
const authHandler = (_a = options == null ? void 0 : options.authHandler) != null ? _a : atlassianDefaultAuthHandler;
|
|
712
|
+
const provider = new AtlassianAuthProvider({
|
|
713
|
+
clientId,
|
|
714
|
+
clientSecret,
|
|
715
|
+
scopes,
|
|
716
|
+
callbackUrl,
|
|
717
|
+
authHandler,
|
|
718
|
+
signInResolver: (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver,
|
|
719
|
+
catalogIdentityClient,
|
|
720
|
+
logger,
|
|
721
|
+
tokenIssuer
|
|
722
|
+
});
|
|
723
|
+
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
724
|
+
disableRefresh: true,
|
|
725
|
+
providerId,
|
|
726
|
+
tokenIssuer
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
class Auth0Strategy extends OAuth2Strategy__default["default"] {
|
|
732
|
+
constructor(options, verify) {
|
|
733
|
+
const optionsWithURLs = {
|
|
734
|
+
...options,
|
|
735
|
+
authorizationURL: `https://${options.domain}/authorize`,
|
|
736
|
+
tokenURL: `https://${options.domain}/oauth/token`,
|
|
737
|
+
userInfoURL: `https://${options.domain}/userinfo`,
|
|
738
|
+
apiUrl: `https://${options.domain}/api`
|
|
739
|
+
};
|
|
740
|
+
super(optionsWithURLs, verify);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
class Auth0AuthProvider {
|
|
562
745
|
constructor(options) {
|
|
563
746
|
this.signInResolver = options.signInResolver;
|
|
564
747
|
this.authHandler = options.authHandler;
|
|
565
|
-
this.stateEncoder = options.stateEncoder;
|
|
566
748
|
this.tokenIssuer = options.tokenIssuer;
|
|
567
749
|
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
568
750
|
this.logger = options.logger;
|
|
569
|
-
this._strategy = new
|
|
751
|
+
this._strategy = new Auth0Strategy({
|
|
570
752
|
clientID: options.clientId,
|
|
571
753
|
clientSecret: options.clientSecret,
|
|
572
754
|
callbackURL: options.callbackUrl,
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
authorizationURL: options.authorizationUrl
|
|
755
|
+
domain: options.domain,
|
|
756
|
+
passReqToCallback: false
|
|
576
757
|
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
577
|
-
done(void 0, {
|
|
758
|
+
done(void 0, {
|
|
759
|
+
fullProfile,
|
|
760
|
+
accessToken,
|
|
761
|
+
refreshToken,
|
|
762
|
+
params
|
|
763
|
+
}, {
|
|
764
|
+
refreshToken
|
|
765
|
+
});
|
|
578
766
|
});
|
|
579
767
|
}
|
|
580
768
|
async start(req) {
|
|
581
769
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
770
|
+
accessType: "offline",
|
|
771
|
+
prompt: "consent",
|
|
582
772
|
scope: req.scope,
|
|
583
|
-
state: (
|
|
773
|
+
state: encodeState(req.state)
|
|
584
774
|
});
|
|
585
775
|
}
|
|
586
776
|
async handler(req) {
|
|
@@ -591,28 +781,25 @@ class GithubAuthProvider {
|
|
|
591
781
|
};
|
|
592
782
|
}
|
|
593
783
|
async refresh(req) {
|
|
594
|
-
const {
|
|
595
|
-
accessToken,
|
|
596
|
-
refreshToken: newRefreshToken,
|
|
597
|
-
params
|
|
598
|
-
} = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
784
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
599
785
|
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
600
|
-
return
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
786
|
+
return {
|
|
787
|
+
response: await this.handleResult({
|
|
788
|
+
fullProfile,
|
|
789
|
+
params,
|
|
790
|
+
accessToken
|
|
791
|
+
}),
|
|
792
|
+
refreshToken
|
|
793
|
+
};
|
|
606
794
|
}
|
|
607
795
|
async handleResult(result) {
|
|
608
796
|
const { profile } = await this.authHandler(result);
|
|
609
|
-
const expiresInStr = result.params.expires_in;
|
|
610
797
|
const response = {
|
|
611
798
|
providerInfo: {
|
|
799
|
+
idToken: result.params.id_token,
|
|
612
800
|
accessToken: result.accessToken,
|
|
613
|
-
refreshToken: result.refreshToken,
|
|
614
801
|
scope: result.params.scope,
|
|
615
|
-
expiresInSeconds:
|
|
802
|
+
expiresInSeconds: result.params.expires_in
|
|
616
803
|
},
|
|
617
804
|
profile
|
|
618
805
|
};
|
|
@@ -629,15 +816,15 @@ class GithubAuthProvider {
|
|
|
629
816
|
return response;
|
|
630
817
|
}
|
|
631
818
|
}
|
|
632
|
-
const
|
|
633
|
-
const {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
return { id
|
|
819
|
+
const defaultSignInResolver$1 = async (info) => {
|
|
820
|
+
const { profile } = info;
|
|
821
|
+
if (!profile.email) {
|
|
822
|
+
throw new Error("Profile does not contain an email");
|
|
823
|
+
}
|
|
824
|
+
const id = profile.email.split("@")[0];
|
|
825
|
+
return { id, token: "" };
|
|
639
826
|
};
|
|
640
|
-
const
|
|
827
|
+
const createAuth0Provider = (options) => {
|
|
641
828
|
return ({
|
|
642
829
|
providerId,
|
|
643
830
|
globalConfig,
|
|
@@ -646,90 +833,193 @@ const createGithubProvider = (options) => {
|
|
|
646
833
|
catalogApi,
|
|
647
834
|
logger
|
|
648
835
|
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
649
|
-
var _a, _b
|
|
836
|
+
var _a, _b;
|
|
650
837
|
const clientId = envConfig.getString("clientId");
|
|
651
838
|
const clientSecret = envConfig.getString("clientSecret");
|
|
652
|
-
const
|
|
653
|
-
const
|
|
654
|
-
const authorizationUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/login/oauth/authorize` : void 0;
|
|
655
|
-
const tokenUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/login/oauth/access_token` : void 0;
|
|
656
|
-
const userProfileUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/api/v3/user` : void 0;
|
|
657
|
-
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
839
|
+
const domain = envConfig.getString("domain");
|
|
840
|
+
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
658
841
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
659
842
|
catalogApi,
|
|
660
843
|
tokenIssuer
|
|
661
844
|
});
|
|
662
|
-
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
|
|
663
|
-
profile: makeProfileInfo(fullProfile)
|
|
664
|
-
});
|
|
665
|
-
const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : githubDefaultSignInResolver;
|
|
666
|
-
const signInResolver = (info) => signInResolverFn(info, {
|
|
667
|
-
catalogIdentityClient,
|
|
668
|
-
tokenIssuer,
|
|
669
|
-
logger
|
|
845
|
+
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
|
|
846
|
+
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
670
847
|
});
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
};
|
|
674
|
-
const provider = new GithubAuthProvider({
|
|
848
|
+
const signInResolver = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : defaultSignInResolver$1;
|
|
849
|
+
const provider = new Auth0AuthProvider({
|
|
675
850
|
clientId,
|
|
676
851
|
clientSecret,
|
|
677
852
|
callbackUrl,
|
|
678
|
-
|
|
679
|
-
userProfileUrl,
|
|
680
|
-
authorizationUrl,
|
|
681
|
-
signInResolver,
|
|
853
|
+
domain,
|
|
682
854
|
authHandler,
|
|
855
|
+
signInResolver,
|
|
683
856
|
tokenIssuer,
|
|
684
857
|
catalogIdentityClient,
|
|
685
|
-
stateEncoder,
|
|
686
858
|
logger
|
|
687
859
|
});
|
|
688
860
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
689
|
-
|
|
861
|
+
disableRefresh: true,
|
|
690
862
|
providerId,
|
|
691
863
|
tokenIssuer
|
|
692
864
|
});
|
|
693
865
|
});
|
|
694
866
|
};
|
|
695
867
|
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
868
|
+
const ALB_JWT_HEADER = "x-amzn-oidc-data";
|
|
869
|
+
const ALB_ACCESS_TOKEN_HEADER = "x-amzn-oidc-accesstoken";
|
|
870
|
+
const getJWTHeaders = (input) => {
|
|
871
|
+
const encoded = input.split(".")[0];
|
|
872
|
+
return JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
|
|
873
|
+
};
|
|
874
|
+
class AwsAlbAuthProvider {
|
|
875
|
+
constructor(options) {
|
|
876
|
+
this.region = options.region;
|
|
877
|
+
this.issuer = options.issuer;
|
|
878
|
+
this.authHandler = options.authHandler;
|
|
879
|
+
this.signInResolver = options.signInResolver;
|
|
880
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
881
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
882
|
+
this.logger = options.logger;
|
|
883
|
+
this.keyCache = new NodeCache__default["default"]({ stdTTL: 3600 });
|
|
701
884
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
}
|
|
705
|
-
|
|
885
|
+
frameHandler() {
|
|
886
|
+
return Promise.resolve(void 0);
|
|
887
|
+
}
|
|
888
|
+
async refresh(req, res) {
|
|
889
|
+
try {
|
|
890
|
+
const result = await this.getResult(req);
|
|
891
|
+
const response = await this.handleResult(result);
|
|
892
|
+
res.json(response);
|
|
893
|
+
} catch (e) {
|
|
894
|
+
this.logger.error("Exception occurred during AWS ALB token refresh", e);
|
|
895
|
+
res.status(401);
|
|
896
|
+
res.end();
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
start() {
|
|
900
|
+
return Promise.resolve(void 0);
|
|
901
|
+
}
|
|
902
|
+
async getResult(req) {
|
|
903
|
+
const jwt = req.header(ALB_JWT_HEADER);
|
|
904
|
+
const accessToken = req.header(ALB_ACCESS_TOKEN_HEADER);
|
|
905
|
+
if (jwt === void 0) {
|
|
906
|
+
throw new errors.AuthenticationError(`Missing ALB OIDC header: ${ALB_JWT_HEADER}`);
|
|
907
|
+
}
|
|
908
|
+
if (accessToken === void 0) {
|
|
909
|
+
throw new errors.AuthenticationError(`Missing ALB OIDC header: ${ALB_ACCESS_TOKEN_HEADER}`);
|
|
910
|
+
}
|
|
911
|
+
try {
|
|
912
|
+
const headers = getJWTHeaders(jwt);
|
|
913
|
+
const key = await this.getKey(headers.kid);
|
|
914
|
+
const claims = jose.JWT.verify(jwt, key);
|
|
915
|
+
if (this.issuer && claims.iss !== this.issuer) {
|
|
916
|
+
throw new errors.AuthenticationError("Issuer mismatch on JWT token");
|
|
917
|
+
}
|
|
918
|
+
const fullProfile = {
|
|
919
|
+
provider: "unknown",
|
|
920
|
+
id: claims.sub,
|
|
921
|
+
displayName: claims.name,
|
|
922
|
+
username: claims.email.split("@")[0].toLowerCase(),
|
|
923
|
+
name: {
|
|
924
|
+
familyName: claims.family_name,
|
|
925
|
+
givenName: claims.given_name
|
|
926
|
+
},
|
|
927
|
+
emails: [{ value: claims.email.toLowerCase() }],
|
|
928
|
+
photos: [{ value: claims.picture }]
|
|
929
|
+
};
|
|
930
|
+
return {
|
|
931
|
+
fullProfile,
|
|
932
|
+
expiresInSeconds: claims.exp,
|
|
933
|
+
accessToken
|
|
934
|
+
};
|
|
935
|
+
} catch (e) {
|
|
936
|
+
throw new Error(`Exception occurred during JWT processing: ${e}`);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
async handleResult(result) {
|
|
940
|
+
const { profile } = await this.authHandler(result);
|
|
941
|
+
const backstageIdentity = await this.signInResolver({
|
|
942
|
+
result,
|
|
943
|
+
profile
|
|
944
|
+
}, {
|
|
945
|
+
tokenIssuer: this.tokenIssuer,
|
|
946
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
947
|
+
logger: this.logger
|
|
948
|
+
});
|
|
949
|
+
return {
|
|
950
|
+
providerInfo: {
|
|
951
|
+
accessToken: result.accessToken,
|
|
952
|
+
expiresInSeconds: result.expiresInSeconds
|
|
953
|
+
},
|
|
954
|
+
backstageIdentity: prepareBackstageIdentityResponse(backstageIdentity),
|
|
955
|
+
profile
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
async getKey(keyId) {
|
|
959
|
+
const optionalCacheKey = this.keyCache.get(keyId);
|
|
960
|
+
if (optionalCacheKey) {
|
|
961
|
+
return crypto__namespace.createPublicKey(optionalCacheKey);
|
|
962
|
+
}
|
|
963
|
+
const keyText = await fetch__default["default"](`https://public-keys.auth.elb.${this.region}.amazonaws.com/${keyId}`).then((response) => response.text());
|
|
964
|
+
const keyValue = crypto__namespace.createPublicKey(keyText);
|
|
965
|
+
this.keyCache.set(keyId, keyValue.export({ format: "pem", type: "spki" }));
|
|
966
|
+
return keyValue;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
const createAwsAlbProvider = (options) => {
|
|
970
|
+
return ({ config, tokenIssuer, catalogApi, logger }) => {
|
|
971
|
+
const region = config.getString("region");
|
|
972
|
+
const issuer = config.getOptionalString("iss");
|
|
973
|
+
if ((options == null ? void 0 : options.signIn.resolver) === void 0) {
|
|
974
|
+
throw new Error("SignInResolver is required to use this authentication provider");
|
|
975
|
+
}
|
|
976
|
+
const catalogIdentityClient = new CatalogIdentityClient({
|
|
977
|
+
catalogApi,
|
|
978
|
+
tokenIssuer
|
|
979
|
+
});
|
|
980
|
+
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
|
|
981
|
+
profile: makeProfileInfo(fullProfile)
|
|
982
|
+
});
|
|
983
|
+
const signInResolver = options == null ? void 0 : options.signIn.resolver;
|
|
984
|
+
return new AwsAlbAuthProvider({
|
|
985
|
+
region,
|
|
986
|
+
issuer,
|
|
987
|
+
signInResolver,
|
|
988
|
+
authHandler,
|
|
989
|
+
tokenIssuer,
|
|
990
|
+
catalogIdentityClient,
|
|
991
|
+
logger
|
|
992
|
+
});
|
|
993
|
+
};
|
|
706
994
|
};
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
params
|
|
710
|
-
}) => ({
|
|
711
|
-
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
712
|
-
});
|
|
713
|
-
class GitlabAuthProvider {
|
|
995
|
+
|
|
996
|
+
class BitbucketAuthProvider {
|
|
714
997
|
constructor(options) {
|
|
998
|
+
this.signInResolver = options.signInResolver;
|
|
999
|
+
this.authHandler = options.authHandler;
|
|
1000
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
715
1001
|
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
716
1002
|
this.logger = options.logger;
|
|
717
|
-
this.
|
|
718
|
-
this.authHandler = options.authHandler;
|
|
719
|
-
this.signInResolver = options.signInResolver;
|
|
720
|
-
this._strategy = new passportGitlab2.Strategy({
|
|
1003
|
+
this._strategy = new passportBitbucketOauth2.Strategy({
|
|
721
1004
|
clientID: options.clientId,
|
|
722
1005
|
clientSecret: options.clientSecret,
|
|
723
1006
|
callbackURL: options.callbackUrl,
|
|
724
|
-
|
|
1007
|
+
passReqToCallback: false
|
|
725
1008
|
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
726
|
-
done(void 0, {
|
|
1009
|
+
done(void 0, {
|
|
1010
|
+
fullProfile,
|
|
1011
|
+
params,
|
|
1012
|
+
accessToken,
|
|
1013
|
+
refreshToken
|
|
1014
|
+
}, {
|
|
727
1015
|
refreshToken
|
|
728
1016
|
});
|
|
729
1017
|
});
|
|
730
1018
|
}
|
|
731
1019
|
async start(req) {
|
|
732
1020
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
1021
|
+
accessType: "offline",
|
|
1022
|
+
prompt: "consent",
|
|
733
1023
|
scope: req.scope,
|
|
734
1024
|
state: encodeState(req.state)
|
|
735
1025
|
});
|
|
@@ -742,26 +1032,24 @@ class GitlabAuthProvider {
|
|
|
742
1032
|
};
|
|
743
1033
|
}
|
|
744
1034
|
async refresh(req) {
|
|
745
|
-
const {
|
|
746
|
-
accessToken,
|
|
747
|
-
refreshToken: newRefreshToken,
|
|
748
|
-
params
|
|
749
|
-
} = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
1035
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
750
1036
|
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
751
|
-
return
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1037
|
+
return {
|
|
1038
|
+
response: await this.handleResult({
|
|
1039
|
+
fullProfile,
|
|
1040
|
+
params,
|
|
1041
|
+
accessToken
|
|
1042
|
+
}),
|
|
1043
|
+
refreshToken
|
|
1044
|
+
};
|
|
757
1045
|
}
|
|
758
1046
|
async handleResult(result) {
|
|
1047
|
+
result.fullProfile.avatarUrl = result.fullProfile._json.links.avatar.href;
|
|
759
1048
|
const { profile } = await this.authHandler(result);
|
|
760
1049
|
const response = {
|
|
761
1050
|
providerInfo: {
|
|
762
1051
|
idToken: result.params.id_token,
|
|
763
1052
|
accessToken: result.accessToken,
|
|
764
|
-
refreshToken: result.refreshToken,
|
|
765
1053
|
scope: result.params.scope,
|
|
766
1054
|
expiresInSeconds: result.params.expires_in
|
|
767
1055
|
},
|
|
@@ -780,7 +1068,35 @@ class GitlabAuthProvider {
|
|
|
780
1068
|
return response;
|
|
781
1069
|
}
|
|
782
1070
|
}
|
|
783
|
-
const
|
|
1071
|
+
const bitbucketUsernameSignInResolver = async (info, ctx) => {
|
|
1072
|
+
const { result } = info;
|
|
1073
|
+
if (!result.fullProfile.username) {
|
|
1074
|
+
throw new Error("Bitbucket profile contained no Username");
|
|
1075
|
+
}
|
|
1076
|
+
const entity = await ctx.catalogIdentityClient.findUser({
|
|
1077
|
+
annotations: {
|
|
1078
|
+
"bitbucket.org/username": result.fullProfile.username
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
const claims = getEntityClaims(entity);
|
|
1082
|
+
const token = await ctx.tokenIssuer.issueToken({ claims });
|
|
1083
|
+
return { id: entity.metadata.name, entity, token };
|
|
1084
|
+
};
|
|
1085
|
+
const bitbucketUserIdSignInResolver = async (info, ctx) => {
|
|
1086
|
+
const { result } = info;
|
|
1087
|
+
if (!result.fullProfile.id) {
|
|
1088
|
+
throw new Error("Bitbucket profile contained no User ID");
|
|
1089
|
+
}
|
|
1090
|
+
const entity = await ctx.catalogIdentityClient.findUser({
|
|
1091
|
+
annotations: {
|
|
1092
|
+
"bitbucket.org/user-id": result.fullProfile.id
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
const claims = getEntityClaims(entity);
|
|
1096
|
+
const token = await ctx.tokenIssuer.issueToken({ claims });
|
|
1097
|
+
return { id: entity.metadata.name, entity, token };
|
|
1098
|
+
};
|
|
1099
|
+
const createBitbucketProvider = (options) => {
|
|
784
1100
|
return ({
|
|
785
1101
|
providerId,
|
|
786
1102
|
globalConfig,
|
|
@@ -789,33 +1105,26 @@ const createGitlabProvider = (options) => {
|
|
|
789
1105
|
catalogApi,
|
|
790
1106
|
logger
|
|
791
1107
|
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
792
|
-
var _a
|
|
1108
|
+
var _a;
|
|
793
1109
|
const clientId = envConfig.getString("clientId");
|
|
794
1110
|
const clientSecret = envConfig.getString("clientSecret");
|
|
795
|
-
const audience = envConfig.getOptionalString("audience");
|
|
796
|
-
const baseUrl = audience || "https://gitlab.com";
|
|
797
1111
|
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
798
1112
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
799
1113
|
catalogApi,
|
|
800
1114
|
tokenIssuer
|
|
801
1115
|
});
|
|
802
|
-
const authHandler = (
|
|
803
|
-
|
|
804
|
-
const signInResolver = (info) => signInResolverFn(info, {
|
|
805
|
-
catalogIdentityClient,
|
|
806
|
-
tokenIssuer,
|
|
807
|
-
logger
|
|
1116
|
+
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
|
|
1117
|
+
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
808
1118
|
});
|
|
809
|
-
const provider = new
|
|
1119
|
+
const provider = new BitbucketAuthProvider({
|
|
810
1120
|
clientId,
|
|
811
1121
|
clientSecret,
|
|
812
1122
|
callbackUrl,
|
|
813
|
-
|
|
1123
|
+
signInResolver: (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver,
|
|
814
1124
|
authHandler,
|
|
815
|
-
|
|
1125
|
+
tokenIssuer,
|
|
816
1126
|
catalogIdentityClient,
|
|
817
|
-
logger
|
|
818
|
-
tokenIssuer
|
|
1127
|
+
logger
|
|
819
1128
|
});
|
|
820
1129
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
821
1130
|
disableRefresh: false,
|
|
@@ -825,35 +1134,29 @@ const createGitlabProvider = (options) => {
|
|
|
825
1134
|
});
|
|
826
1135
|
};
|
|
827
1136
|
|
|
828
|
-
class
|
|
1137
|
+
class GithubAuthProvider {
|
|
829
1138
|
constructor(options) {
|
|
830
1139
|
this.signInResolver = options.signInResolver;
|
|
831
1140
|
this.authHandler = options.authHandler;
|
|
1141
|
+
this.stateEncoder = options.stateEncoder;
|
|
832
1142
|
this.tokenIssuer = options.tokenIssuer;
|
|
833
1143
|
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
834
1144
|
this.logger = options.logger;
|
|
835
|
-
this._strategy = new
|
|
1145
|
+
this._strategy = new passportGithub2.Strategy({
|
|
836
1146
|
clientID: options.clientId,
|
|
837
1147
|
clientSecret: options.clientSecret,
|
|
838
1148
|
callbackURL: options.callbackUrl,
|
|
839
|
-
|
|
1149
|
+
tokenURL: options.tokenUrl,
|
|
1150
|
+
userProfileURL: options.userProfileUrl,
|
|
1151
|
+
authorizationURL: options.authorizationUrl
|
|
840
1152
|
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
841
|
-
done(void 0, {
|
|
842
|
-
fullProfile,
|
|
843
|
-
params,
|
|
844
|
-
accessToken,
|
|
845
|
-
refreshToken
|
|
846
|
-
}, {
|
|
847
|
-
refreshToken
|
|
848
|
-
});
|
|
1153
|
+
done(void 0, { fullProfile, params, accessToken }, { refreshToken });
|
|
849
1154
|
});
|
|
850
1155
|
}
|
|
851
1156
|
async start(req) {
|
|
852
1157
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
853
|
-
accessType: "offline",
|
|
854
|
-
prompt: "consent",
|
|
855
1158
|
scope: req.scope,
|
|
856
|
-
state:
|
|
1159
|
+
state: (await this.stateEncoder(req)).encodedState
|
|
857
1160
|
});
|
|
858
1161
|
}
|
|
859
1162
|
async handler(req) {
|
|
@@ -864,23 +1167,25 @@ class GoogleAuthProvider {
|
|
|
864
1167
|
};
|
|
865
1168
|
}
|
|
866
1169
|
async refresh(req) {
|
|
867
|
-
const { accessToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
1170
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
868
1171
|
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
869
|
-
return
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1172
|
+
return {
|
|
1173
|
+
response: await this.handleResult({
|
|
1174
|
+
fullProfile,
|
|
1175
|
+
params,
|
|
1176
|
+
accessToken
|
|
1177
|
+
}),
|
|
1178
|
+
refreshToken
|
|
1179
|
+
};
|
|
875
1180
|
}
|
|
876
1181
|
async handleResult(result) {
|
|
877
1182
|
const { profile } = await this.authHandler(result);
|
|
1183
|
+
const expiresInStr = result.params.expires_in;
|
|
878
1184
|
const response = {
|
|
879
1185
|
providerInfo: {
|
|
880
|
-
idToken: result.params.id_token,
|
|
881
1186
|
accessToken: result.accessToken,
|
|
882
1187
|
scope: result.params.scope,
|
|
883
|
-
expiresInSeconds:
|
|
1188
|
+
expiresInSeconds: expiresInStr === void 0 ? void 0 : Number(expiresInStr)
|
|
884
1189
|
},
|
|
885
1190
|
profile
|
|
886
1191
|
};
|
|
@@ -897,43 +1202,15 @@ class GoogleAuthProvider {
|
|
|
897
1202
|
return response;
|
|
898
1203
|
}
|
|
899
1204
|
}
|
|
900
|
-
const
|
|
901
|
-
const {
|
|
902
|
-
|
|
903
|
-
throw new Error("Google profile contained no email");
|
|
904
|
-
}
|
|
905
|
-
const entity = await ctx.catalogIdentityClient.findUser({
|
|
906
|
-
annotations: {
|
|
907
|
-
"google.com/email": profile.email
|
|
908
|
-
}
|
|
909
|
-
});
|
|
910
|
-
const claims = getEntityClaims(entity);
|
|
911
|
-
const token = await ctx.tokenIssuer.issueToken({ claims });
|
|
912
|
-
return { id: entity.metadata.name, entity, token };
|
|
913
|
-
};
|
|
914
|
-
const googleDefaultSignInResolver = async (info, ctx) => {
|
|
915
|
-
const { profile } = info;
|
|
916
|
-
if (!profile.email) {
|
|
917
|
-
throw new Error("Google profile contained no email");
|
|
918
|
-
}
|
|
919
|
-
let userId;
|
|
920
|
-
try {
|
|
921
|
-
const entity = await ctx.catalogIdentityClient.findUser({
|
|
922
|
-
annotations: {
|
|
923
|
-
"google.com/email": profile.email
|
|
924
|
-
}
|
|
925
|
-
});
|
|
926
|
-
userId = entity.metadata.name;
|
|
927
|
-
} catch (error) {
|
|
928
|
-
ctx.logger.warn(`Failed to look up user, ${error}, falling back to allowing login based on email pattern, this will probably break in the future`);
|
|
929
|
-
userId = profile.email.split("@")[0];
|
|
930
|
-
}
|
|
1205
|
+
const githubDefaultSignInResolver = async (info, ctx) => {
|
|
1206
|
+
const { fullProfile } = info.result;
|
|
1207
|
+
const userId = fullProfile.username || fullProfile.id;
|
|
931
1208
|
const token = await ctx.tokenIssuer.issueToken({
|
|
932
1209
|
claims: { sub: userId, ent: [`user:default/${userId}`] }
|
|
933
1210
|
});
|
|
934
1211
|
return { id: userId, token };
|
|
935
1212
|
};
|
|
936
|
-
const
|
|
1213
|
+
const createGithubProvider = (options) => {
|
|
937
1214
|
return ({
|
|
938
1215
|
providerId,
|
|
939
1216
|
globalConfig,
|
|
@@ -942,57 +1219,86 @@ const createGoogleProvider = (options) => {
|
|
|
942
1219
|
catalogApi,
|
|
943
1220
|
logger
|
|
944
1221
|
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
945
|
-
var _a, _b;
|
|
1222
|
+
var _a, _b, _c;
|
|
946
1223
|
const clientId = envConfig.getString("clientId");
|
|
947
1224
|
const clientSecret = envConfig.getString("clientSecret");
|
|
948
|
-
const
|
|
1225
|
+
const enterpriseInstanceUrl = envConfig.getOptionalString("enterpriseInstanceUrl");
|
|
1226
|
+
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
1227
|
+
const authorizationUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/login/oauth/authorize` : void 0;
|
|
1228
|
+
const tokenUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/login/oauth/access_token` : void 0;
|
|
1229
|
+
const userProfileUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/api/v3/user` : void 0;
|
|
1230
|
+
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
949
1231
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
950
1232
|
catalogApi,
|
|
951
1233
|
tokenIssuer
|
|
952
1234
|
});
|
|
953
|
-
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile
|
|
954
|
-
profile: makeProfileInfo(fullProfile
|
|
1235
|
+
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
|
|
1236
|
+
profile: makeProfileInfo(fullProfile)
|
|
955
1237
|
});
|
|
956
|
-
const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b :
|
|
1238
|
+
const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : githubDefaultSignInResolver;
|
|
957
1239
|
const signInResolver = (info) => signInResolverFn(info, {
|
|
958
1240
|
catalogIdentityClient,
|
|
959
1241
|
tokenIssuer,
|
|
960
1242
|
logger
|
|
961
1243
|
});
|
|
962
|
-
const
|
|
1244
|
+
const stateEncoder = (_c = options == null ? void 0 : options.stateEncoder) != null ? _c : async (req) => {
|
|
1245
|
+
return { encodedState: encodeState(req.state) };
|
|
1246
|
+
};
|
|
1247
|
+
const provider = new GithubAuthProvider({
|
|
963
1248
|
clientId,
|
|
964
1249
|
clientSecret,
|
|
965
1250
|
callbackUrl,
|
|
1251
|
+
tokenUrl,
|
|
1252
|
+
userProfileUrl,
|
|
1253
|
+
authorizationUrl,
|
|
966
1254
|
signInResolver,
|
|
967
1255
|
authHandler,
|
|
968
1256
|
tokenIssuer,
|
|
969
1257
|
catalogIdentityClient,
|
|
1258
|
+
stateEncoder,
|
|
970
1259
|
logger
|
|
971
1260
|
});
|
|
972
1261
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
973
|
-
|
|
1262
|
+
persistScopes: true,
|
|
974
1263
|
providerId,
|
|
975
1264
|
tokenIssuer
|
|
976
1265
|
});
|
|
977
1266
|
});
|
|
978
1267
|
};
|
|
979
1268
|
|
|
980
|
-
|
|
1269
|
+
const gitlabDefaultSignInResolver = async (info, ctx) => {
|
|
1270
|
+
const { profile, result } = info;
|
|
1271
|
+
let id = result.fullProfile.id;
|
|
1272
|
+
if (profile.email) {
|
|
1273
|
+
id = profile.email.split("@")[0];
|
|
1274
|
+
}
|
|
1275
|
+
const token = await ctx.tokenIssuer.issueToken({
|
|
1276
|
+
claims: { sub: id, ent: [`user:default/${id}`] }
|
|
1277
|
+
});
|
|
1278
|
+
return { id, token };
|
|
1279
|
+
};
|
|
1280
|
+
const gitlabDefaultAuthHandler = async ({
|
|
1281
|
+
fullProfile,
|
|
1282
|
+
params
|
|
1283
|
+
}) => ({
|
|
1284
|
+
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
1285
|
+
});
|
|
1286
|
+
class GitlabAuthProvider {
|
|
981
1287
|
constructor(options) {
|
|
982
|
-
this.signInResolver = options.signInResolver;
|
|
983
|
-
this.authHandler = options.authHandler;
|
|
984
|
-
this.tokenIssuer = options.tokenIssuer;
|
|
985
|
-
this.logger = options.logger;
|
|
986
1288
|
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
987
|
-
this.
|
|
1289
|
+
this.logger = options.logger;
|
|
1290
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
1291
|
+
this.authHandler = options.authHandler;
|
|
1292
|
+
this.signInResolver = options.signInResolver;
|
|
1293
|
+
this._strategy = new passportGitlab2.Strategy({
|
|
988
1294
|
clientID: options.clientId,
|
|
989
1295
|
clientSecret: options.clientSecret,
|
|
990
1296
|
callbackURL: options.callbackUrl,
|
|
991
|
-
|
|
992
|
-
tokenURL: options.tokenUrl,
|
|
993
|
-
passReqToCallback: false
|
|
1297
|
+
baseURL: options.baseUrl
|
|
994
1298
|
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
995
|
-
done(void 0, { fullProfile,
|
|
1299
|
+
done(void 0, { fullProfile, params, accessToken }, {
|
|
1300
|
+
refreshToken
|
|
1301
|
+
});
|
|
996
1302
|
});
|
|
997
1303
|
}
|
|
998
1304
|
async start(req) {
|
|
@@ -1009,18 +1315,18 @@ class MicrosoftAuthProvider {
|
|
|
1009
1315
|
};
|
|
1010
1316
|
}
|
|
1011
1317
|
async refresh(req) {
|
|
1012
|
-
const { accessToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
1318
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
1013
1319
|
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
1014
|
-
return
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1320
|
+
return {
|
|
1321
|
+
response: await this.handleResult({
|
|
1322
|
+
fullProfile,
|
|
1323
|
+
params,
|
|
1324
|
+
accessToken
|
|
1325
|
+
}),
|
|
1326
|
+
refreshToken
|
|
1327
|
+
};
|
|
1020
1328
|
}
|
|
1021
1329
|
async handleResult(result) {
|
|
1022
|
-
const photo = await this.getUserPhoto(result.accessToken);
|
|
1023
|
-
result.fullProfile.photos = photo ? [{ value: photo }] : void 0;
|
|
1024
1330
|
const { profile } = await this.authHandler(result);
|
|
1025
1331
|
const response = {
|
|
1026
1332
|
providerInfo: {
|
|
@@ -1037,56 +1343,14 @@ class MicrosoftAuthProvider {
|
|
|
1037
1343
|
profile
|
|
1038
1344
|
}, {
|
|
1039
1345
|
tokenIssuer: this.tokenIssuer,
|
|
1040
|
-
catalogIdentityClient: this.catalogIdentityClient,
|
|
1041
|
-
logger: this.logger
|
|
1042
|
-
});
|
|
1043
|
-
}
|
|
1044
|
-
return response;
|
|
1045
|
-
}
|
|
1046
|
-
getUserPhoto(accessToken) {
|
|
1047
|
-
return new Promise((resolve) => {
|
|
1048
|
-
got__default["default"].get("https://graph.microsoft.com/v1.0/me/photos/48x48/$value", {
|
|
1049
|
-
encoding: "binary",
|
|
1050
|
-
responseType: "buffer",
|
|
1051
|
-
headers: {
|
|
1052
|
-
Authorization: `Bearer ${accessToken}`
|
|
1053
|
-
}
|
|
1054
|
-
}).then((photoData) => {
|
|
1055
|
-
const photoURL = `data:image/jpeg;base64,${Buffer.from(photoData.body).toString("base64")}`;
|
|
1056
|
-
resolve(photoURL);
|
|
1057
|
-
}).catch((error) => {
|
|
1058
|
-
this.logger.warn(`Could not retrieve user profile photo from Microsoft Graph API: ${error}`);
|
|
1059
|
-
resolve(void 0);
|
|
1060
|
-
});
|
|
1061
|
-
});
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
const microsoftEmailSignInResolver = async (info, ctx) => {
|
|
1065
|
-
const { profile } = info;
|
|
1066
|
-
if (!profile.email) {
|
|
1067
|
-
throw new Error("Microsoft profile contained no email");
|
|
1068
|
-
}
|
|
1069
|
-
const entity = await ctx.catalogIdentityClient.findUser({
|
|
1070
|
-
annotations: {
|
|
1071
|
-
"microsoft.com/email": profile.email
|
|
1346
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
1347
|
+
logger: this.logger
|
|
1348
|
+
});
|
|
1072
1349
|
}
|
|
1073
|
-
|
|
1074
|
-
const claims = getEntityClaims(entity);
|
|
1075
|
-
const token = await ctx.tokenIssuer.issueToken({ claims });
|
|
1076
|
-
return { id: entity.metadata.name, entity, token };
|
|
1077
|
-
};
|
|
1078
|
-
const microsoftDefaultSignInResolver = async (info, ctx) => {
|
|
1079
|
-
const { profile } = info;
|
|
1080
|
-
if (!profile.email) {
|
|
1081
|
-
throw new Error("Profile contained no email");
|
|
1350
|
+
return response;
|
|
1082
1351
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
claims: { sub: userId, ent: [`user:default/${userId}`] }
|
|
1086
|
-
});
|
|
1087
|
-
return { id: userId, token };
|
|
1088
|
-
};
|
|
1089
|
-
const createMicrosoftProvider = (options) => {
|
|
1352
|
+
}
|
|
1353
|
+
const createGitlabProvider = (options) => {
|
|
1090
1354
|
return ({
|
|
1091
1355
|
providerId,
|
|
1092
1356
|
globalConfig,
|
|
@@ -1095,32 +1359,28 @@ const createMicrosoftProvider = (options) => {
|
|
|
1095
1359
|
catalogApi,
|
|
1096
1360
|
logger
|
|
1097
1361
|
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
1098
|
-
var _a, _b;
|
|
1362
|
+
var _a, _b, _c;
|
|
1099
1363
|
const clientId = envConfig.getString("clientId");
|
|
1100
1364
|
const clientSecret = envConfig.getString("clientSecret");
|
|
1101
|
-
const
|
|
1365
|
+
const audience = envConfig.getOptionalString("audience");
|
|
1366
|
+
const baseUrl = audience || "https://gitlab.com";
|
|
1102
1367
|
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1103
|
-
const authorizationUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`;
|
|
1104
|
-
const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
|
|
1105
1368
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
1106
1369
|
catalogApi,
|
|
1107
1370
|
tokenIssuer
|
|
1108
1371
|
});
|
|
1109
|
-
const authHandler = (options == null ? void 0 : options.authHandler) ?
|
|
1110
|
-
|
|
1111
|
-
});
|
|
1112
|
-
const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : microsoftDefaultSignInResolver;
|
|
1372
|
+
const authHandler = (_a = options == null ? void 0 : options.authHandler) != null ? _a : gitlabDefaultAuthHandler;
|
|
1373
|
+
const signInResolverFn = (_c = (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver) != null ? _c : gitlabDefaultSignInResolver;
|
|
1113
1374
|
const signInResolver = (info) => signInResolverFn(info, {
|
|
1114
1375
|
catalogIdentityClient,
|
|
1115
1376
|
tokenIssuer,
|
|
1116
1377
|
logger
|
|
1117
1378
|
});
|
|
1118
|
-
const provider = new
|
|
1379
|
+
const provider = new GitlabAuthProvider({
|
|
1119
1380
|
clientId,
|
|
1120
1381
|
clientSecret,
|
|
1121
1382
|
callbackUrl,
|
|
1122
|
-
|
|
1123
|
-
tokenUrl,
|
|
1383
|
+
baseUrl,
|
|
1124
1384
|
authHandler,
|
|
1125
1385
|
signInResolver,
|
|
1126
1386
|
catalogIdentityClient,
|
|
@@ -1135,30 +1395,24 @@ const createMicrosoftProvider = (options) => {
|
|
|
1135
1395
|
});
|
|
1136
1396
|
};
|
|
1137
1397
|
|
|
1138
|
-
class
|
|
1398
|
+
class GoogleAuthProvider {
|
|
1139
1399
|
constructor(options) {
|
|
1140
1400
|
this.signInResolver = options.signInResolver;
|
|
1141
1401
|
this.authHandler = options.authHandler;
|
|
1142
1402
|
this.tokenIssuer = options.tokenIssuer;
|
|
1143
1403
|
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
1144
1404
|
this.logger = options.logger;
|
|
1145
|
-
this._strategy = new
|
|
1405
|
+
this._strategy = new passportGoogleOauth20.Strategy({
|
|
1146
1406
|
clientID: options.clientId,
|
|
1147
1407
|
clientSecret: options.clientSecret,
|
|
1148
1408
|
callbackURL: options.callbackUrl,
|
|
1149
|
-
|
|
1150
|
-
tokenURL: options.tokenUrl,
|
|
1151
|
-
passReqToCallback: false,
|
|
1152
|
-
scope: options.scope,
|
|
1153
|
-
customHeaders: options.includeBasicAuth ? {
|
|
1154
|
-
Authorization: `Basic ${this.encodeClientCredentials(options.clientId, options.clientSecret)}`
|
|
1155
|
-
} : void 0
|
|
1409
|
+
passReqToCallback: false
|
|
1156
1410
|
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
1157
1411
|
done(void 0, {
|
|
1158
1412
|
fullProfile,
|
|
1413
|
+
params,
|
|
1159
1414
|
accessToken,
|
|
1160
|
-
refreshToken
|
|
1161
|
-
params
|
|
1415
|
+
refreshToken
|
|
1162
1416
|
}, {
|
|
1163
1417
|
refreshToken
|
|
1164
1418
|
});
|
|
@@ -1180,19 +1434,16 @@ class OAuth2AuthProvider {
|
|
|
1180
1434
|
};
|
|
1181
1435
|
}
|
|
1182
1436
|
async refresh(req) {
|
|
1183
|
-
const
|
|
1184
|
-
const {
|
|
1185
|
-
accessToken,
|
|
1186
|
-
params,
|
|
1187
|
-
refreshToken: updatedRefreshToken
|
|
1188
|
-
} = refreshTokenResponse;
|
|
1437
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
1189
1438
|
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
1190
|
-
return
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1439
|
+
return {
|
|
1440
|
+
response: await this.handleResult({
|
|
1441
|
+
fullProfile,
|
|
1442
|
+
params,
|
|
1443
|
+
accessToken
|
|
1444
|
+
}),
|
|
1445
|
+
refreshToken
|
|
1446
|
+
};
|
|
1196
1447
|
}
|
|
1197
1448
|
async handleResult(result) {
|
|
1198
1449
|
const { profile } = await this.authHandler(result);
|
|
@@ -1201,8 +1452,7 @@ class OAuth2AuthProvider {
|
|
|
1201
1452
|
idToken: result.params.id_token,
|
|
1202
1453
|
accessToken: result.accessToken,
|
|
1203
1454
|
scope: result.params.scope,
|
|
1204
|
-
expiresInSeconds: result.params.expires_in
|
|
1205
|
-
refreshToken: result.refreshToken
|
|
1455
|
+
expiresInSeconds: result.params.expires_in
|
|
1206
1456
|
},
|
|
1207
1457
|
profile
|
|
1208
1458
|
};
|
|
@@ -1218,22 +1468,44 @@ class OAuth2AuthProvider {
|
|
|
1218
1468
|
}
|
|
1219
1469
|
return response;
|
|
1220
1470
|
}
|
|
1221
|
-
encodeClientCredentials(clientID, clientSecret) {
|
|
1222
|
-
return Buffer.from(`${clientID}:${clientSecret}`).toString("base64");
|
|
1223
|
-
}
|
|
1224
1471
|
}
|
|
1225
|
-
const
|
|
1472
|
+
const googleEmailSignInResolver = async (info, ctx) => {
|
|
1226
1473
|
const { profile } = info;
|
|
1227
1474
|
if (!profile.email) {
|
|
1228
|
-
throw new Error("
|
|
1475
|
+
throw new Error("Google profile contained no email");
|
|
1476
|
+
}
|
|
1477
|
+
const entity = await ctx.catalogIdentityClient.findUser({
|
|
1478
|
+
annotations: {
|
|
1479
|
+
"google.com/email": profile.email
|
|
1480
|
+
}
|
|
1481
|
+
});
|
|
1482
|
+
const claims = getEntityClaims(entity);
|
|
1483
|
+
const token = await ctx.tokenIssuer.issueToken({ claims });
|
|
1484
|
+
return { id: entity.metadata.name, entity, token };
|
|
1485
|
+
};
|
|
1486
|
+
const googleDefaultSignInResolver = async (info, ctx) => {
|
|
1487
|
+
const { profile } = info;
|
|
1488
|
+
if (!profile.email) {
|
|
1489
|
+
throw new Error("Google profile contained no email");
|
|
1490
|
+
}
|
|
1491
|
+
let userId;
|
|
1492
|
+
try {
|
|
1493
|
+
const entity = await ctx.catalogIdentityClient.findUser({
|
|
1494
|
+
annotations: {
|
|
1495
|
+
"google.com/email": profile.email
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
userId = entity.metadata.name;
|
|
1499
|
+
} catch (error) {
|
|
1500
|
+
ctx.logger.warn(`Failed to look up user, ${error}, falling back to allowing login based on email pattern, this will probably break in the future`);
|
|
1501
|
+
userId = profile.email.split("@")[0];
|
|
1229
1502
|
}
|
|
1230
|
-
const userId = profile.email.split("@")[0];
|
|
1231
1503
|
const token = await ctx.tokenIssuer.issueToken({
|
|
1232
1504
|
claims: { sub: userId, ent: [`user:default/${userId}`] }
|
|
1233
1505
|
});
|
|
1234
1506
|
return { id: userId, token };
|
|
1235
1507
|
};
|
|
1236
|
-
const
|
|
1508
|
+
const createGoogleProvider = (options) => {
|
|
1237
1509
|
return ({
|
|
1238
1510
|
providerId,
|
|
1239
1511
|
globalConfig,
|
|
@@ -1242,15 +1514,10 @@ const createOAuth2Provider = (options) => {
|
|
|
1242
1514
|
catalogApi,
|
|
1243
1515
|
logger
|
|
1244
1516
|
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
1245
|
-
var _a, _b
|
|
1517
|
+
var _a, _b;
|
|
1246
1518
|
const clientId = envConfig.getString("clientId");
|
|
1247
1519
|
const clientSecret = envConfig.getString("clientSecret");
|
|
1248
1520
|
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1249
|
-
const authorizationUrl = envConfig.getString("authorizationUrl");
|
|
1250
|
-
const tokenUrl = envConfig.getString("tokenUrl");
|
|
1251
|
-
const scope = envConfig.getOptionalString("scope");
|
|
1252
|
-
const includeBasicAuth = envConfig.getOptionalBoolean("includeBasicAuth");
|
|
1253
|
-
const disableRefresh = (_a = envConfig.getOptionalBoolean("disableRefresh")) != null ? _a : false;
|
|
1254
1521
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
1255
1522
|
catalogApi,
|
|
1256
1523
|
tokenIssuer
|
|
@@ -1258,113 +1525,83 @@ const createOAuth2Provider = (options) => {
|
|
|
1258
1525
|
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
|
|
1259
1526
|
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
1260
1527
|
});
|
|
1261
|
-
const signInResolverFn = (
|
|
1528
|
+
const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : googleDefaultSignInResolver;
|
|
1262
1529
|
const signInResolver = (info) => signInResolverFn(info, {
|
|
1263
1530
|
catalogIdentityClient,
|
|
1264
1531
|
tokenIssuer,
|
|
1265
1532
|
logger
|
|
1266
1533
|
});
|
|
1267
|
-
const provider = new
|
|
1534
|
+
const provider = new GoogleAuthProvider({
|
|
1268
1535
|
clientId,
|
|
1269
1536
|
clientSecret,
|
|
1270
|
-
tokenIssuer,
|
|
1271
|
-
catalogIdentityClient,
|
|
1272
1537
|
callbackUrl,
|
|
1273
1538
|
signInResolver,
|
|
1274
1539
|
authHandler,
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
logger,
|
|
1279
|
-
includeBasicAuth
|
|
1540
|
+
tokenIssuer,
|
|
1541
|
+
catalogIdentityClient,
|
|
1542
|
+
logger
|
|
1280
1543
|
});
|
|
1281
1544
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
1282
|
-
disableRefresh,
|
|
1545
|
+
disableRefresh: false,
|
|
1283
1546
|
providerId,
|
|
1284
1547
|
tokenIssuer
|
|
1285
1548
|
});
|
|
1286
1549
|
});
|
|
1287
1550
|
};
|
|
1288
1551
|
|
|
1289
|
-
class
|
|
1552
|
+
class MicrosoftAuthProvider {
|
|
1290
1553
|
constructor(options) {
|
|
1291
|
-
this.implementation = this.setupStrategy(options);
|
|
1292
|
-
this.scope = options.scope;
|
|
1293
|
-
this.prompt = options.prompt;
|
|
1294
1554
|
this.signInResolver = options.signInResolver;
|
|
1295
1555
|
this.authHandler = options.authHandler;
|
|
1296
1556
|
this.tokenIssuer = options.tokenIssuer;
|
|
1297
|
-
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
1298
1557
|
this.logger = options.logger;
|
|
1558
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
1559
|
+
this._strategy = new passportMicrosoft.Strategy({
|
|
1560
|
+
clientID: options.clientId,
|
|
1561
|
+
clientSecret: options.clientSecret,
|
|
1562
|
+
callbackURL: options.callbackUrl,
|
|
1563
|
+
authorizationURL: options.authorizationUrl,
|
|
1564
|
+
tokenURL: options.tokenUrl,
|
|
1565
|
+
passReqToCallback: false
|
|
1566
|
+
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
1567
|
+
done(void 0, { fullProfile, accessToken, params }, { refreshToken });
|
|
1568
|
+
});
|
|
1299
1569
|
}
|
|
1300
1570
|
async start(req) {
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
scope: req.scope || this.scope || "openid profile email",
|
|
1571
|
+
return await executeRedirectStrategy(req, this._strategy, {
|
|
1572
|
+
scope: req.scope,
|
|
1304
1573
|
state: encodeState(req.state)
|
|
1305
|
-
};
|
|
1306
|
-
const prompt = this.prompt || "none";
|
|
1307
|
-
if (prompt !== "auto") {
|
|
1308
|
-
options.prompt = prompt;
|
|
1309
|
-
}
|
|
1310
|
-
return await executeRedirectStrategy(req, strategy, options);
|
|
1574
|
+
});
|
|
1311
1575
|
}
|
|
1312
1576
|
async handler(req) {
|
|
1313
|
-
const {
|
|
1314
|
-
const strategyResponse = await executeFrameHandlerStrategy(req, strategy);
|
|
1315
|
-
const {
|
|
1316
|
-
result: { userinfo, tokenset },
|
|
1317
|
-
privateInfo
|
|
1318
|
-
} = strategyResponse;
|
|
1319
|
-
const identityResponse = await this.handleResult({ tokenset, userinfo });
|
|
1577
|
+
const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
|
|
1320
1578
|
return {
|
|
1321
|
-
response:
|
|
1579
|
+
response: await this.handleResult(result),
|
|
1322
1580
|
refreshToken: privateInfo.refreshToken
|
|
1323
1581
|
};
|
|
1324
1582
|
}
|
|
1325
1583
|
async refresh(req) {
|
|
1326
|
-
const {
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
const client = new issuer.Client({
|
|
1337
|
-
access_type: "offline",
|
|
1338
|
-
client_id: options.clientId,
|
|
1339
|
-
client_secret: options.clientSecret,
|
|
1340
|
-
redirect_uris: [options.callbackUrl],
|
|
1341
|
-
response_types: ["code"],
|
|
1342
|
-
id_token_signed_response_alg: options.tokenSignedResponseAlg || "RS256",
|
|
1343
|
-
scope: options.scope || ""
|
|
1344
|
-
});
|
|
1345
|
-
const strategy = new openidClient.Strategy({
|
|
1346
|
-
client,
|
|
1347
|
-
passReqToCallback: false
|
|
1348
|
-
}, (tokenset, userinfo, done) => {
|
|
1349
|
-
if (typeof done !== "function") {
|
|
1350
|
-
throw new Error("OIDC IdP must provide a userinfo_endpoint in the metadata response");
|
|
1351
|
-
}
|
|
1352
|
-
done(void 0, { tokenset, userinfo }, {
|
|
1353
|
-
refreshToken: tokenset.refresh_token
|
|
1354
|
-
});
|
|
1355
|
-
});
|
|
1356
|
-
strategy.error = console.error;
|
|
1357
|
-
return { strategy, client };
|
|
1584
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
1585
|
+
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
1586
|
+
return {
|
|
1587
|
+
response: await this.handleResult({
|
|
1588
|
+
fullProfile,
|
|
1589
|
+
params,
|
|
1590
|
+
accessToken
|
|
1591
|
+
}),
|
|
1592
|
+
refreshToken
|
|
1593
|
+
};
|
|
1358
1594
|
}
|
|
1359
1595
|
async handleResult(result) {
|
|
1596
|
+
const photo = await this.getUserPhoto(result.accessToken);
|
|
1597
|
+
result.fullProfile.photos = photo ? [{ value: photo }] : void 0;
|
|
1360
1598
|
const { profile } = await this.authHandler(result);
|
|
1361
1599
|
const response = {
|
|
1362
1600
|
providerInfo: {
|
|
1363
|
-
idToken: result.
|
|
1364
|
-
accessToken: result.
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
expiresInSeconds: result.tokenset.expires_in
|
|
1601
|
+
idToken: result.params.id_token,
|
|
1602
|
+
accessToken: result.accessToken,
|
|
1603
|
+
scope: result.params.scope,
|
|
1604
|
+
expiresInSeconds: result.params.expires_in
|
|
1368
1605
|
},
|
|
1369
1606
|
profile
|
|
1370
1607
|
};
|
|
@@ -1380,8 +1617,37 @@ class OidcAuthProvider {
|
|
|
1380
1617
|
}
|
|
1381
1618
|
return response;
|
|
1382
1619
|
}
|
|
1620
|
+
getUserPhoto(accessToken) {
|
|
1621
|
+
return new Promise((resolve) => {
|
|
1622
|
+
fetch__default["default"]("https://graph.microsoft.com/v1.0/me/photos/48x48/$value", {
|
|
1623
|
+
headers: {
|
|
1624
|
+
Authorization: `Bearer ${accessToken}`
|
|
1625
|
+
}
|
|
1626
|
+
}).then((response) => response.arrayBuffer()).then((arrayBuffer) => {
|
|
1627
|
+
const imageUrl = `data:image/jpeg;base64,${Buffer.from(arrayBuffer).toString("base64")}`;
|
|
1628
|
+
resolve(imageUrl);
|
|
1629
|
+
}).catch((error) => {
|
|
1630
|
+
this.logger.warn(`Could not retrieve user profile photo from Microsoft Graph API: ${error}`);
|
|
1631
|
+
resolve(void 0);
|
|
1632
|
+
});
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1383
1635
|
}
|
|
1384
|
-
const
|
|
1636
|
+
const microsoftEmailSignInResolver = async (info, ctx) => {
|
|
1637
|
+
const { profile } = info;
|
|
1638
|
+
if (!profile.email) {
|
|
1639
|
+
throw new Error("Microsoft profile contained no email");
|
|
1640
|
+
}
|
|
1641
|
+
const entity = await ctx.catalogIdentityClient.findUser({
|
|
1642
|
+
annotations: {
|
|
1643
|
+
"microsoft.com/email": profile.email
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
const claims = getEntityClaims(entity);
|
|
1647
|
+
const token = await ctx.tokenIssuer.issueToken({ claims });
|
|
1648
|
+
return { id: entity.metadata.name, entity, token };
|
|
1649
|
+
};
|
|
1650
|
+
const microsoftDefaultSignInResolver = async (info, ctx) => {
|
|
1385
1651
|
const { profile } = info;
|
|
1386
1652
|
if (!profile.email) {
|
|
1387
1653
|
throw new Error("Profile contained no email");
|
|
@@ -1392,7 +1658,7 @@ const oAuth2DefaultSignInResolver = async (info, ctx) => {
|
|
|
1392
1658
|
});
|
|
1393
1659
|
return { id: userId, token };
|
|
1394
1660
|
};
|
|
1395
|
-
const
|
|
1661
|
+
const createMicrosoftProvider = (options) => {
|
|
1396
1662
|
return ({
|
|
1397
1663
|
providerId,
|
|
1398
1664
|
globalConfig,
|
|
@@ -1404,41 +1670,34 @@ const createOidcProvider = (options) => {
|
|
|
1404
1670
|
var _a, _b;
|
|
1405
1671
|
const clientId = envConfig.getString("clientId");
|
|
1406
1672
|
const clientSecret = envConfig.getString("clientSecret");
|
|
1673
|
+
const tenantId = envConfig.getString("tenantId");
|
|
1407
1674
|
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1408
|
-
const
|
|
1409
|
-
const
|
|
1410
|
-
const scope = envConfig.getOptionalString("scope");
|
|
1411
|
-
const prompt = envConfig.getOptionalString("prompt");
|
|
1675
|
+
const authorizationUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`;
|
|
1676
|
+
const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
|
|
1412
1677
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
1413
1678
|
catalogApi,
|
|
1414
1679
|
tokenIssuer
|
|
1415
1680
|
});
|
|
1416
|
-
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({
|
|
1417
|
-
profile:
|
|
1418
|
-
displayName: userinfo.name,
|
|
1419
|
-
email: userinfo.email,
|
|
1420
|
-
picture: userinfo.picture
|
|
1421
|
-
}
|
|
1681
|
+
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
|
|
1682
|
+
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
1422
1683
|
});
|
|
1423
|
-
const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b :
|
|
1684
|
+
const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : microsoftDefaultSignInResolver;
|
|
1424
1685
|
const signInResolver = (info) => signInResolverFn(info, {
|
|
1425
1686
|
catalogIdentityClient,
|
|
1426
1687
|
tokenIssuer,
|
|
1427
1688
|
logger
|
|
1428
1689
|
});
|
|
1429
|
-
const provider = new
|
|
1690
|
+
const provider = new MicrosoftAuthProvider({
|
|
1430
1691
|
clientId,
|
|
1431
1692
|
clientSecret,
|
|
1432
1693
|
callbackUrl,
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
scope,
|
|
1436
|
-
prompt,
|
|
1437
|
-
signInResolver,
|
|
1694
|
+
authorizationUrl,
|
|
1695
|
+
tokenUrl,
|
|
1438
1696
|
authHandler,
|
|
1697
|
+
signInResolver,
|
|
1698
|
+
catalogIdentityClient,
|
|
1439
1699
|
logger,
|
|
1440
|
-
tokenIssuer
|
|
1441
|
-
catalogIdentityClient
|
|
1700
|
+
tokenIssuer
|
|
1442
1701
|
});
|
|
1443
1702
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
1444
1703
|
disableRefresh: false,
|
|
@@ -1448,35 +1707,30 @@ const createOidcProvider = (options) => {
|
|
|
1448
1707
|
});
|
|
1449
1708
|
};
|
|
1450
1709
|
|
|
1451
|
-
class
|
|
1710
|
+
class OAuth2AuthProvider {
|
|
1452
1711
|
constructor(options) {
|
|
1453
|
-
this.
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
}
|
|
1460
|
-
};
|
|
1461
|
-
this._signInResolver = options.signInResolver;
|
|
1462
|
-
this._authHandler = options.authHandler;
|
|
1463
|
-
this._tokenIssuer = options.tokenIssuer;
|
|
1464
|
-
this._catalogIdentityClient = options.catalogIdentityClient;
|
|
1465
|
-
this._logger = options.logger;
|
|
1466
|
-
this._strategy = new passportOktaOauth.Strategy({
|
|
1712
|
+
this.signInResolver = options.signInResolver;
|
|
1713
|
+
this.authHandler = options.authHandler;
|
|
1714
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
1715
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
1716
|
+
this.logger = options.logger;
|
|
1717
|
+
this._strategy = new OAuth2Strategy.Strategy({
|
|
1467
1718
|
clientID: options.clientId,
|
|
1468
1719
|
clientSecret: options.clientSecret,
|
|
1469
1720
|
callbackURL: options.callbackUrl,
|
|
1470
|
-
|
|
1721
|
+
authorizationURL: options.authorizationUrl,
|
|
1722
|
+
tokenURL: options.tokenUrl,
|
|
1471
1723
|
passReqToCallback: false,
|
|
1472
|
-
|
|
1473
|
-
|
|
1724
|
+
scope: options.scope,
|
|
1725
|
+
customHeaders: options.includeBasicAuth ? {
|
|
1726
|
+
Authorization: `Basic ${this.encodeClientCredentials(options.clientId, options.clientSecret)}`
|
|
1727
|
+
} : void 0
|
|
1474
1728
|
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
1475
1729
|
done(void 0, {
|
|
1730
|
+
fullProfile,
|
|
1476
1731
|
accessToken,
|
|
1477
1732
|
refreshToken,
|
|
1478
|
-
params
|
|
1479
|
-
fullProfile
|
|
1733
|
+
params
|
|
1480
1734
|
}, {
|
|
1481
1735
|
refreshToken
|
|
1482
1736
|
});
|
|
@@ -1498,17 +1752,20 @@ class OktaAuthProvider {
|
|
|
1498
1752
|
};
|
|
1499
1753
|
}
|
|
1500
1754
|
async refresh(req) {
|
|
1501
|
-
const
|
|
1755
|
+
const refreshTokenResponse = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
1756
|
+
const { accessToken, params, refreshToken } = refreshTokenResponse;
|
|
1502
1757
|
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
1503
|
-
return
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1758
|
+
return {
|
|
1759
|
+
response: await this.handleResult({
|
|
1760
|
+
fullProfile,
|
|
1761
|
+
params,
|
|
1762
|
+
accessToken
|
|
1763
|
+
}),
|
|
1764
|
+
refreshToken
|
|
1765
|
+
};
|
|
1509
1766
|
}
|
|
1510
1767
|
async handleResult(result) {
|
|
1511
|
-
const { profile } = await this.
|
|
1768
|
+
const { profile } = await this.authHandler(result);
|
|
1512
1769
|
const response = {
|
|
1513
1770
|
providerInfo: {
|
|
1514
1771
|
idToken: result.params.id_token,
|
|
@@ -1518,37 +1775,26 @@ class OktaAuthProvider {
|
|
|
1518
1775
|
},
|
|
1519
1776
|
profile
|
|
1520
1777
|
};
|
|
1521
|
-
if (this.
|
|
1522
|
-
response.backstageIdentity = await this.
|
|
1778
|
+
if (this.signInResolver) {
|
|
1779
|
+
response.backstageIdentity = await this.signInResolver({
|
|
1523
1780
|
result,
|
|
1524
1781
|
profile
|
|
1525
1782
|
}, {
|
|
1526
|
-
tokenIssuer: this.
|
|
1527
|
-
catalogIdentityClient: this.
|
|
1528
|
-
logger: this.
|
|
1783
|
+
tokenIssuer: this.tokenIssuer,
|
|
1784
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
1785
|
+
logger: this.logger
|
|
1529
1786
|
});
|
|
1530
1787
|
}
|
|
1531
1788
|
return response;
|
|
1532
1789
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
const { profile } = info;
|
|
1536
|
-
if (!profile.email) {
|
|
1537
|
-
throw new Error("Okta profile contained no email");
|
|
1790
|
+
encodeClientCredentials(clientID, clientSecret) {
|
|
1791
|
+
return Buffer.from(`${clientID}:${clientSecret}`).toString("base64");
|
|
1538
1792
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
"okta.com/email": profile.email
|
|
1542
|
-
}
|
|
1543
|
-
});
|
|
1544
|
-
const claims = getEntityClaims(entity);
|
|
1545
|
-
const token = await ctx.tokenIssuer.issueToken({ claims });
|
|
1546
|
-
return { id: entity.metadata.name, entity, token };
|
|
1547
|
-
};
|
|
1548
|
-
const oktaDefaultSignInResolver = async (info, ctx) => {
|
|
1793
|
+
}
|
|
1794
|
+
const oAuth2DefaultSignInResolver$1 = async (info, ctx) => {
|
|
1549
1795
|
const { profile } = info;
|
|
1550
1796
|
if (!profile.email) {
|
|
1551
|
-
throw new Error("
|
|
1797
|
+
throw new Error("Profile contained no email");
|
|
1552
1798
|
}
|
|
1553
1799
|
const userId = profile.email.split("@")[0];
|
|
1554
1800
|
const token = await ctx.tokenIssuer.issueToken({
|
|
@@ -1556,7 +1802,7 @@ const oktaDefaultSignInResolver = async (info, ctx) => {
|
|
|
1556
1802
|
});
|
|
1557
1803
|
return { id: userId, token };
|
|
1558
1804
|
};
|
|
1559
|
-
const
|
|
1805
|
+
const createOAuth2Provider = (options) => {
|
|
1560
1806
|
return ({
|
|
1561
1807
|
providerId,
|
|
1562
1808
|
globalConfig,
|
|
@@ -1565,103 +1811,126 @@ const createOktaProvider = (_options) => {
|
|
|
1565
1811
|
catalogApi,
|
|
1566
1812
|
logger
|
|
1567
1813
|
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
1568
|
-
var _a, _b;
|
|
1814
|
+
var _a, _b, _c;
|
|
1569
1815
|
const clientId = envConfig.getString("clientId");
|
|
1570
1816
|
const clientSecret = envConfig.getString("clientSecret");
|
|
1571
|
-
const audience = envConfig.getString("audience");
|
|
1572
1817
|
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1818
|
+
const authorizationUrl = envConfig.getString("authorizationUrl");
|
|
1819
|
+
const tokenUrl = envConfig.getString("tokenUrl");
|
|
1820
|
+
const scope = envConfig.getOptionalString("scope");
|
|
1821
|
+
const includeBasicAuth = envConfig.getOptionalBoolean("includeBasicAuth");
|
|
1822
|
+
const disableRefresh = (_a = envConfig.getOptionalBoolean("disableRefresh")) != null ? _a : false;
|
|
1576
1823
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
1577
1824
|
catalogApi,
|
|
1578
1825
|
tokenIssuer
|
|
1579
1826
|
});
|
|
1580
|
-
const authHandler = (
|
|
1827
|
+
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
|
|
1581
1828
|
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
1582
1829
|
});
|
|
1583
|
-
const signInResolverFn = (
|
|
1830
|
+
const signInResolverFn = (_c = (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver) != null ? _c : oAuth2DefaultSignInResolver$1;
|
|
1584
1831
|
const signInResolver = (info) => signInResolverFn(info, {
|
|
1585
1832
|
catalogIdentityClient,
|
|
1586
1833
|
tokenIssuer,
|
|
1587
1834
|
logger
|
|
1588
1835
|
});
|
|
1589
|
-
const provider = new
|
|
1590
|
-
audience,
|
|
1836
|
+
const provider = new OAuth2AuthProvider({
|
|
1591
1837
|
clientId,
|
|
1592
1838
|
clientSecret,
|
|
1593
|
-
callbackUrl,
|
|
1594
|
-
authHandler,
|
|
1595
|
-
signInResolver,
|
|
1596
1839
|
tokenIssuer,
|
|
1597
1840
|
catalogIdentityClient,
|
|
1598
|
-
|
|
1841
|
+
callbackUrl,
|
|
1842
|
+
signInResolver,
|
|
1843
|
+
authHandler,
|
|
1844
|
+
authorizationUrl,
|
|
1845
|
+
tokenUrl,
|
|
1846
|
+
scope,
|
|
1847
|
+
logger,
|
|
1848
|
+
includeBasicAuth
|
|
1599
1849
|
});
|
|
1600
1850
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
1601
|
-
disableRefresh
|
|
1851
|
+
disableRefresh,
|
|
1602
1852
|
providerId,
|
|
1603
1853
|
tokenIssuer
|
|
1604
1854
|
});
|
|
1605
1855
|
});
|
|
1606
1856
|
};
|
|
1607
1857
|
|
|
1608
|
-
class
|
|
1858
|
+
class OidcAuthProvider {
|
|
1609
1859
|
constructor(options) {
|
|
1860
|
+
this.implementation = this.setupStrategy(options);
|
|
1861
|
+
this.scope = options.scope;
|
|
1862
|
+
this.prompt = options.prompt;
|
|
1610
1863
|
this.signInResolver = options.signInResolver;
|
|
1611
1864
|
this.authHandler = options.authHandler;
|
|
1612
1865
|
this.tokenIssuer = options.tokenIssuer;
|
|
1613
1866
|
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
1614
1867
|
this.logger = options.logger;
|
|
1615
|
-
this._strategy = new passportBitbucketOauth2.Strategy({
|
|
1616
|
-
clientID: options.clientId,
|
|
1617
|
-
clientSecret: options.clientSecret,
|
|
1618
|
-
callbackURL: options.callbackUrl,
|
|
1619
|
-
passReqToCallback: false
|
|
1620
|
-
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
1621
|
-
done(void 0, {
|
|
1622
|
-
fullProfile,
|
|
1623
|
-
params,
|
|
1624
|
-
accessToken,
|
|
1625
|
-
refreshToken
|
|
1626
|
-
}, {
|
|
1627
|
-
refreshToken
|
|
1628
|
-
});
|
|
1629
|
-
});
|
|
1630
1868
|
}
|
|
1631
1869
|
async start(req) {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
scope: req.scope,
|
|
1870
|
+
const { strategy } = await this.implementation;
|
|
1871
|
+
const options = {
|
|
1872
|
+
scope: req.scope || this.scope || "openid profile email",
|
|
1636
1873
|
state: encodeState(req.state)
|
|
1637
|
-
}
|
|
1874
|
+
};
|
|
1875
|
+
const prompt = this.prompt || "none";
|
|
1876
|
+
if (prompt !== "auto") {
|
|
1877
|
+
options.prompt = prompt;
|
|
1878
|
+
}
|
|
1879
|
+
return await executeRedirectStrategy(req, strategy, options);
|
|
1638
1880
|
}
|
|
1639
1881
|
async handler(req) {
|
|
1640
|
-
const {
|
|
1882
|
+
const { strategy } = await this.implementation;
|
|
1883
|
+
const { result, privateInfo } = await executeFrameHandlerStrategy(req, strategy);
|
|
1641
1884
|
return {
|
|
1642
1885
|
response: await this.handleResult(result),
|
|
1643
1886
|
refreshToken: privateInfo.refreshToken
|
|
1644
1887
|
};
|
|
1645
1888
|
}
|
|
1646
1889
|
async refresh(req) {
|
|
1647
|
-
const {
|
|
1648
|
-
const
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1890
|
+
const { client } = await this.implementation;
|
|
1891
|
+
const tokenset = await client.refresh(req.refreshToken);
|
|
1892
|
+
if (!tokenset.access_token) {
|
|
1893
|
+
throw new Error("Refresh failed");
|
|
1894
|
+
}
|
|
1895
|
+
const userinfo = await client.userinfo(tokenset.access_token);
|
|
1896
|
+
return {
|
|
1897
|
+
response: await this.handleResult({ tokenset, userinfo }),
|
|
1898
|
+
refreshToken: tokenset.refresh_token
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
async setupStrategy(options) {
|
|
1902
|
+
const issuer = await openidClient.Issuer.discover(options.metadataUrl);
|
|
1903
|
+
const client = new issuer.Client({
|
|
1904
|
+
access_type: "offline",
|
|
1905
|
+
client_id: options.clientId,
|
|
1906
|
+
client_secret: options.clientSecret,
|
|
1907
|
+
redirect_uris: [options.callbackUrl],
|
|
1908
|
+
response_types: ["code"],
|
|
1909
|
+
id_token_signed_response_alg: options.tokenSignedResponseAlg || "RS256",
|
|
1910
|
+
scope: options.scope || ""
|
|
1911
|
+
});
|
|
1912
|
+
const strategy = new openidClient.Strategy({
|
|
1913
|
+
client,
|
|
1914
|
+
passReqToCallback: false
|
|
1915
|
+
}, (tokenset, userinfo, done) => {
|
|
1916
|
+
if (typeof done !== "function") {
|
|
1917
|
+
throw new Error("OIDC IdP must provide a userinfo_endpoint in the metadata response");
|
|
1918
|
+
}
|
|
1919
|
+
done(void 0, { tokenset, userinfo }, {
|
|
1920
|
+
refreshToken: tokenset.refresh_token
|
|
1921
|
+
});
|
|
1654
1922
|
});
|
|
1923
|
+
strategy.error = console.error;
|
|
1924
|
+
return { strategy, client };
|
|
1655
1925
|
}
|
|
1656
1926
|
async handleResult(result) {
|
|
1657
|
-
result.fullProfile.avatarUrl = result.fullProfile._json.links.avatar.href;
|
|
1658
1927
|
const { profile } = await this.authHandler(result);
|
|
1659
1928
|
const response = {
|
|
1660
1929
|
providerInfo: {
|
|
1661
|
-
idToken: result.
|
|
1662
|
-
accessToken: result.
|
|
1663
|
-
scope: result.
|
|
1664
|
-
expiresInSeconds: result.
|
|
1930
|
+
idToken: result.tokenset.id_token,
|
|
1931
|
+
accessToken: result.tokenset.access_token,
|
|
1932
|
+
scope: result.tokenset.scope,
|
|
1933
|
+
expiresInSeconds: result.tokenset.expires_in
|
|
1665
1934
|
},
|
|
1666
1935
|
profile
|
|
1667
1936
|
};
|
|
@@ -1678,35 +1947,18 @@ class BitbucketAuthProvider {
|
|
|
1678
1947
|
return response;
|
|
1679
1948
|
}
|
|
1680
1949
|
}
|
|
1681
|
-
const
|
|
1682
|
-
const {
|
|
1683
|
-
if (!
|
|
1684
|
-
throw new Error("
|
|
1685
|
-
}
|
|
1686
|
-
const entity = await ctx.catalogIdentityClient.findUser({
|
|
1687
|
-
annotations: {
|
|
1688
|
-
"bitbucket.org/username": result.fullProfile.username
|
|
1689
|
-
}
|
|
1690
|
-
});
|
|
1691
|
-
const claims = getEntityClaims(entity);
|
|
1692
|
-
const token = await ctx.tokenIssuer.issueToken({ claims });
|
|
1693
|
-
return { id: entity.metadata.name, entity, token };
|
|
1694
|
-
};
|
|
1695
|
-
const bitbucketUserIdSignInResolver = async (info, ctx) => {
|
|
1696
|
-
const { result } = info;
|
|
1697
|
-
if (!result.fullProfile.id) {
|
|
1698
|
-
throw new Error("Bitbucket profile contained no User ID");
|
|
1950
|
+
const oAuth2DefaultSignInResolver = async (info, ctx) => {
|
|
1951
|
+
const { profile } = info;
|
|
1952
|
+
if (!profile.email) {
|
|
1953
|
+
throw new Error("Profile contained no email");
|
|
1699
1954
|
}
|
|
1700
|
-
const
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
}
|
|
1955
|
+
const userId = profile.email.split("@")[0];
|
|
1956
|
+
const token = await ctx.tokenIssuer.issueToken({
|
|
1957
|
+
claims: { sub: userId, ent: [`user:default/${userId}`] }
|
|
1704
1958
|
});
|
|
1705
|
-
|
|
1706
|
-
const token = await ctx.tokenIssuer.issueToken({ claims });
|
|
1707
|
-
return { id: entity.metadata.name, entity, token };
|
|
1959
|
+
return { id: userId, token };
|
|
1708
1960
|
};
|
|
1709
|
-
const
|
|
1961
|
+
const createOidcProvider = (options) => {
|
|
1710
1962
|
return ({
|
|
1711
1963
|
providerId,
|
|
1712
1964
|
globalConfig,
|
|
@@ -1715,26 +1967,44 @@ const createBitbucketProvider = (options) => {
|
|
|
1715
1967
|
catalogApi,
|
|
1716
1968
|
logger
|
|
1717
1969
|
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
1718
|
-
var _a;
|
|
1970
|
+
var _a, _b;
|
|
1719
1971
|
const clientId = envConfig.getString("clientId");
|
|
1720
1972
|
const clientSecret = envConfig.getString("clientSecret");
|
|
1721
1973
|
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1974
|
+
const metadataUrl = envConfig.getString("metadataUrl");
|
|
1975
|
+
const tokenSignedResponseAlg = envConfig.getOptionalString("tokenSignedResponseAlg");
|
|
1976
|
+
const scope = envConfig.getOptionalString("scope");
|
|
1977
|
+
const prompt = envConfig.getOptionalString("prompt");
|
|
1722
1978
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
1723
1979
|
catalogApi,
|
|
1724
1980
|
tokenIssuer
|
|
1725
1981
|
});
|
|
1726
|
-
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({
|
|
1727
|
-
profile:
|
|
1982
|
+
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ userinfo }) => ({
|
|
1983
|
+
profile: {
|
|
1984
|
+
displayName: userinfo.name,
|
|
1985
|
+
email: userinfo.email,
|
|
1986
|
+
picture: userinfo.picture
|
|
1987
|
+
}
|
|
1728
1988
|
});
|
|
1729
|
-
const
|
|
1989
|
+
const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : oAuth2DefaultSignInResolver;
|
|
1990
|
+
const signInResolver = (info) => signInResolverFn(info, {
|
|
1991
|
+
catalogIdentityClient,
|
|
1992
|
+
tokenIssuer,
|
|
1993
|
+
logger
|
|
1994
|
+
});
|
|
1995
|
+
const provider = new OidcAuthProvider({
|
|
1730
1996
|
clientId,
|
|
1731
1997
|
clientSecret,
|
|
1732
1998
|
callbackUrl,
|
|
1733
|
-
|
|
1999
|
+
tokenSignedResponseAlg,
|
|
2000
|
+
metadataUrl,
|
|
2001
|
+
scope,
|
|
2002
|
+
prompt,
|
|
2003
|
+
signInResolver,
|
|
1734
2004
|
authHandler,
|
|
2005
|
+
logger,
|
|
1735
2006
|
tokenIssuer,
|
|
1736
|
-
catalogIdentityClient
|
|
1737
|
-
logger
|
|
2007
|
+
catalogIdentityClient
|
|
1738
2008
|
});
|
|
1739
2009
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
1740
2010
|
disableRefresh: false,
|
|
@@ -1744,140 +2014,117 @@ const createBitbucketProvider = (options) => {
|
|
|
1744
2014
|
});
|
|
1745
2015
|
};
|
|
1746
2016
|
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
...options,
|
|
1756
|
-
authorizationURL: `https://auth.atlassian.com/authorize`,
|
|
1757
|
-
tokenURL: `https://auth.atlassian.com/oauth/token`,
|
|
1758
|
-
scope: Array.from(/* @__PURE__ */ new Set([...defaultScopes, ...scopes]))
|
|
1759
|
-
};
|
|
1760
|
-
super(optionsWithURLs, verify);
|
|
1761
|
-
this.profileURL = "https://api.atlassian.com/me";
|
|
1762
|
-
this.name = "atlassian";
|
|
1763
|
-
this._oauth2.useAuthorizationHeaderforGET(true);
|
|
1764
|
-
}
|
|
1765
|
-
authorizationParams() {
|
|
1766
|
-
return {
|
|
1767
|
-
audience: "api.atlassian.com",
|
|
1768
|
-
prompt: "consent"
|
|
1769
|
-
};
|
|
1770
|
-
}
|
|
1771
|
-
userProfile(accessToken, done) {
|
|
1772
|
-
this._oauth2.get(this.profileURL, accessToken, (err, body) => {
|
|
1773
|
-
if (err) {
|
|
1774
|
-
return done(new OAuth2Strategy.InternalOAuthError("Failed to fetch user profile", err.statusCode));
|
|
1775
|
-
}
|
|
1776
|
-
if (!body) {
|
|
1777
|
-
return done(new Error("Failed to fetch user profile, body cannot be empty"));
|
|
1778
|
-
}
|
|
1779
|
-
try {
|
|
1780
|
-
const json = typeof body !== "string" ? body.toString() : body;
|
|
1781
|
-
const profile = AtlassianStrategy.parse(json);
|
|
1782
|
-
return done(null, profile);
|
|
1783
|
-
} catch (e) {
|
|
1784
|
-
return done(new Error("Failed to parse user profile"));
|
|
2017
|
+
class OktaAuthProvider {
|
|
2018
|
+
constructor(options) {
|
|
2019
|
+
this._store = {
|
|
2020
|
+
store(_req, cb) {
|
|
2021
|
+
cb(null, null);
|
|
2022
|
+
},
|
|
2023
|
+
verify(_req, _state, cb) {
|
|
2024
|
+
cb(null, true);
|
|
1785
2025
|
}
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
static parse(json) {
|
|
1789
|
-
const resp = JSON.parse(json);
|
|
1790
|
-
return {
|
|
1791
|
-
id: resp.account_id,
|
|
1792
|
-
provider: "atlassian",
|
|
1793
|
-
username: resp.nickname,
|
|
1794
|
-
displayName: resp.name,
|
|
1795
|
-
emails: [{ value: resp.email }],
|
|
1796
|
-
photos: [{ value: resp.picture }]
|
|
1797
2026
|
};
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
}) => ({
|
|
1805
|
-
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
1806
|
-
});
|
|
1807
|
-
class AtlassianAuthProvider {
|
|
1808
|
-
constructor(options) {
|
|
1809
|
-
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
1810
|
-
this.logger = options.logger;
|
|
1811
|
-
this.tokenIssuer = options.tokenIssuer;
|
|
1812
|
-
this.authHandler = options.authHandler;
|
|
1813
|
-
this.signInResolver = options.signInResolver;
|
|
1814
|
-
this._strategy = new AtlassianStrategy({
|
|
2027
|
+
this._signInResolver = options.signInResolver;
|
|
2028
|
+
this._authHandler = options.authHandler;
|
|
2029
|
+
this._tokenIssuer = options.tokenIssuer;
|
|
2030
|
+
this._catalogIdentityClient = options.catalogIdentityClient;
|
|
2031
|
+
this._logger = options.logger;
|
|
2032
|
+
this._strategy = new passportOktaOauth.Strategy({
|
|
1815
2033
|
clientID: options.clientId,
|
|
1816
2034
|
clientSecret: options.clientSecret,
|
|
1817
2035
|
callbackURL: options.callbackUrl,
|
|
1818
|
-
|
|
2036
|
+
audience: options.audience,
|
|
2037
|
+
passReqToCallback: false,
|
|
2038
|
+
store: this._store,
|
|
2039
|
+
response_type: "code"
|
|
1819
2040
|
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
1820
2041
|
done(void 0, {
|
|
1821
|
-
fullProfile,
|
|
1822
2042
|
accessToken,
|
|
1823
2043
|
refreshToken,
|
|
1824
|
-
params
|
|
2044
|
+
params,
|
|
2045
|
+
fullProfile
|
|
2046
|
+
}, {
|
|
2047
|
+
refreshToken
|
|
1825
2048
|
});
|
|
1826
2049
|
});
|
|
1827
2050
|
}
|
|
1828
2051
|
async start(req) {
|
|
1829
2052
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
2053
|
+
accessType: "offline",
|
|
2054
|
+
prompt: "consent",
|
|
2055
|
+
scope: req.scope,
|
|
1830
2056
|
state: encodeState(req.state)
|
|
1831
2057
|
});
|
|
1832
2058
|
}
|
|
1833
2059
|
async handler(req) {
|
|
1834
|
-
|
|
1835
|
-
const { result } = await executeFrameHandlerStrategy(req, this._strategy);
|
|
2060
|
+
const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
|
|
1836
2061
|
return {
|
|
1837
2062
|
response: await this.handleResult(result),
|
|
1838
|
-
refreshToken:
|
|
2063
|
+
refreshToken: privateInfo.refreshToken
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
async refresh(req) {
|
|
2067
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
2068
|
+
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
2069
|
+
return {
|
|
2070
|
+
response: await this.handleResult({
|
|
2071
|
+
fullProfile,
|
|
2072
|
+
params,
|
|
2073
|
+
accessToken
|
|
2074
|
+
}),
|
|
2075
|
+
refreshToken
|
|
1839
2076
|
};
|
|
1840
2077
|
}
|
|
1841
2078
|
async handleResult(result) {
|
|
1842
|
-
const { profile } = await this.
|
|
2079
|
+
const { profile } = await this._authHandler(result);
|
|
1843
2080
|
const response = {
|
|
1844
2081
|
providerInfo: {
|
|
1845
2082
|
idToken: result.params.id_token,
|
|
1846
2083
|
accessToken: result.accessToken,
|
|
1847
|
-
refreshToken: result.refreshToken,
|
|
1848
2084
|
scope: result.params.scope,
|
|
1849
2085
|
expiresInSeconds: result.params.expires_in
|
|
1850
2086
|
},
|
|
1851
2087
|
profile
|
|
1852
2088
|
};
|
|
1853
|
-
if (this.
|
|
1854
|
-
response.backstageIdentity = await this.
|
|
2089
|
+
if (this._signInResolver) {
|
|
2090
|
+
response.backstageIdentity = await this._signInResolver({
|
|
1855
2091
|
result,
|
|
1856
2092
|
profile
|
|
1857
2093
|
}, {
|
|
1858
|
-
tokenIssuer: this.
|
|
1859
|
-
catalogIdentityClient: this.
|
|
1860
|
-
logger: this.
|
|
2094
|
+
tokenIssuer: this._tokenIssuer,
|
|
2095
|
+
catalogIdentityClient: this._catalogIdentityClient,
|
|
2096
|
+
logger: this._logger
|
|
1861
2097
|
});
|
|
1862
2098
|
}
|
|
1863
2099
|
return response;
|
|
1864
2100
|
}
|
|
1865
|
-
async refresh(req) {
|
|
1866
|
-
const {
|
|
1867
|
-
accessToken,
|
|
1868
|
-
params,
|
|
1869
|
-
refreshToken: newRefreshToken
|
|
1870
|
-
} = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
1871
|
-
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
1872
|
-
return this.handleResult({
|
|
1873
|
-
fullProfile,
|
|
1874
|
-
params,
|
|
1875
|
-
accessToken,
|
|
1876
|
-
refreshToken: newRefreshToken
|
|
1877
|
-
});
|
|
1878
|
-
}
|
|
1879
2101
|
}
|
|
1880
|
-
const
|
|
2102
|
+
const oktaEmailSignInResolver = async (info, ctx) => {
|
|
2103
|
+
const { profile } = info;
|
|
2104
|
+
if (!profile.email) {
|
|
2105
|
+
throw new Error("Okta profile contained no email");
|
|
2106
|
+
}
|
|
2107
|
+
const entity = await ctx.catalogIdentityClient.findUser({
|
|
2108
|
+
annotations: {
|
|
2109
|
+
"okta.com/email": profile.email
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
const claims = getEntityClaims(entity);
|
|
2113
|
+
const token = await ctx.tokenIssuer.issueToken({ claims });
|
|
2114
|
+
return { id: entity.metadata.name, entity, token };
|
|
2115
|
+
};
|
|
2116
|
+
const oktaDefaultSignInResolver = async (info, ctx) => {
|
|
2117
|
+
const { profile } = info;
|
|
2118
|
+
if (!profile.email) {
|
|
2119
|
+
throw new Error("Okta profile contained no email");
|
|
2120
|
+
}
|
|
2121
|
+
const userId = profile.email.split("@")[0];
|
|
2122
|
+
const token = await ctx.tokenIssuer.issueToken({
|
|
2123
|
+
claims: { sub: userId, ent: [`user:default/${userId}`] }
|
|
2124
|
+
});
|
|
2125
|
+
return { id: userId, token };
|
|
2126
|
+
};
|
|
2127
|
+
const createOktaProvider = (_options) => {
|
|
1881
2128
|
return ({
|
|
1882
2129
|
providerId,
|
|
1883
2130
|
globalConfig,
|
|
@@ -1889,158 +2136,165 @@ const createAtlassianProvider = (options) => {
|
|
|
1889
2136
|
var _a, _b;
|
|
1890
2137
|
const clientId = envConfig.getString("clientId");
|
|
1891
2138
|
const clientSecret = envConfig.getString("clientSecret");
|
|
1892
|
-
const
|
|
2139
|
+
const audience = envConfig.getString("audience");
|
|
1893
2140
|
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
2141
|
+
if (!audience.startsWith("https://")) {
|
|
2142
|
+
throw new Error("URL for 'audience' must start with 'https://'.");
|
|
2143
|
+
}
|
|
1894
2144
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
1895
2145
|
catalogApi,
|
|
1896
2146
|
tokenIssuer
|
|
1897
2147
|
});
|
|
1898
|
-
const authHandler = (
|
|
1899
|
-
|
|
2148
|
+
const authHandler = (_options == null ? void 0 : _options.authHandler) ? _options.authHandler : async ({ fullProfile, params }) => ({
|
|
2149
|
+
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
2150
|
+
});
|
|
2151
|
+
const signInResolverFn = (_b = (_a = _options == null ? void 0 : _options.signIn) == null ? void 0 : _a.resolver) != null ? _b : oktaDefaultSignInResolver;
|
|
2152
|
+
const signInResolver = (info) => signInResolverFn(info, {
|
|
2153
|
+
catalogIdentityClient,
|
|
2154
|
+
tokenIssuer,
|
|
2155
|
+
logger
|
|
2156
|
+
});
|
|
2157
|
+
const provider = new OktaAuthProvider({
|
|
2158
|
+
audience,
|
|
1900
2159
|
clientId,
|
|
1901
2160
|
clientSecret,
|
|
1902
|
-
scopes,
|
|
1903
2161
|
callbackUrl,
|
|
1904
2162
|
authHandler,
|
|
1905
|
-
signInResolver
|
|
2163
|
+
signInResolver,
|
|
2164
|
+
tokenIssuer,
|
|
1906
2165
|
catalogIdentityClient,
|
|
1907
|
-
logger
|
|
1908
|
-
tokenIssuer
|
|
2166
|
+
logger
|
|
1909
2167
|
});
|
|
1910
2168
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
1911
|
-
disableRefresh:
|
|
2169
|
+
disableRefresh: false,
|
|
1912
2170
|
providerId,
|
|
1913
2171
|
tokenIssuer
|
|
1914
2172
|
});
|
|
1915
2173
|
});
|
|
1916
2174
|
};
|
|
1917
2175
|
|
|
1918
|
-
|
|
1919
|
-
const ALB_ACCESSTOKEN_HEADER = "x-amzn-oidc-accesstoken";
|
|
1920
|
-
const getJWTHeaders = (input) => {
|
|
1921
|
-
const encoded = input.split(".")[0];
|
|
1922
|
-
return JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
|
|
1923
|
-
};
|
|
1924
|
-
class AwsAlbAuthProvider {
|
|
2176
|
+
class OneLoginProvider {
|
|
1925
2177
|
constructor(options) {
|
|
1926
|
-
this.region = options.region;
|
|
1927
|
-
this.issuer = options.issuer;
|
|
1928
|
-
this.authHandler = options.authHandler;
|
|
1929
2178
|
this.signInResolver = options.signInResolver;
|
|
2179
|
+
this.authHandler = options.authHandler;
|
|
1930
2180
|
this.tokenIssuer = options.tokenIssuer;
|
|
1931
2181
|
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
1932
2182
|
this.logger = options.logger;
|
|
1933
|
-
this.
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
start() {
|
|
1950
|
-
return Promise.resolve(void 0);
|
|
2183
|
+
this._strategy = new passportOneloginOauth.Strategy({
|
|
2184
|
+
issuer: options.issuer,
|
|
2185
|
+
clientID: options.clientId,
|
|
2186
|
+
clientSecret: options.clientSecret,
|
|
2187
|
+
callbackURL: options.callbackUrl,
|
|
2188
|
+
passReqToCallback: false
|
|
2189
|
+
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
2190
|
+
done(void 0, {
|
|
2191
|
+
accessToken,
|
|
2192
|
+
refreshToken,
|
|
2193
|
+
params,
|
|
2194
|
+
fullProfile
|
|
2195
|
+
}, {
|
|
2196
|
+
refreshToken
|
|
2197
|
+
});
|
|
2198
|
+
});
|
|
1951
2199
|
}
|
|
1952
|
-
async
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
username: claims.email.split("@")[0].toLowerCase(),
|
|
1973
|
-
name: {
|
|
1974
|
-
familyName: claims.family_name,
|
|
1975
|
-
givenName: claims.given_name
|
|
1976
|
-
},
|
|
1977
|
-
emails: [{ value: claims.email.toLowerCase() }],
|
|
1978
|
-
photos: [{ value: claims.picture }]
|
|
1979
|
-
};
|
|
1980
|
-
return {
|
|
2200
|
+
async start(req) {
|
|
2201
|
+
return await executeRedirectStrategy(req, this._strategy, {
|
|
2202
|
+
accessType: "offline",
|
|
2203
|
+
prompt: "consent",
|
|
2204
|
+
scope: "openid",
|
|
2205
|
+
state: encodeState(req.state)
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
async handler(req) {
|
|
2209
|
+
const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
|
|
2210
|
+
return {
|
|
2211
|
+
response: await this.handleResult(result),
|
|
2212
|
+
refreshToken: privateInfo.refreshToken
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
async refresh(req) {
|
|
2216
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
2217
|
+
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
2218
|
+
return {
|
|
2219
|
+
response: await this.handleResult({
|
|
1981
2220
|
fullProfile,
|
|
1982
|
-
|
|
2221
|
+
params,
|
|
1983
2222
|
accessToken
|
|
1984
|
-
}
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
}
|
|
2223
|
+
}),
|
|
2224
|
+
refreshToken
|
|
2225
|
+
};
|
|
1988
2226
|
}
|
|
1989
2227
|
async handleResult(result) {
|
|
1990
2228
|
const { profile } = await this.authHandler(result);
|
|
1991
|
-
const
|
|
1992
|
-
result,
|
|
1993
|
-
profile
|
|
1994
|
-
}, {
|
|
1995
|
-
tokenIssuer: this.tokenIssuer,
|
|
1996
|
-
catalogIdentityClient: this.catalogIdentityClient,
|
|
1997
|
-
logger: this.logger
|
|
1998
|
-
});
|
|
1999
|
-
return {
|
|
2229
|
+
const response = {
|
|
2000
2230
|
providerInfo: {
|
|
2231
|
+
idToken: result.params.id_token,
|
|
2001
2232
|
accessToken: result.accessToken,
|
|
2002
|
-
|
|
2233
|
+
scope: result.params.scope,
|
|
2234
|
+
expiresInSeconds: result.params.expires_in
|
|
2003
2235
|
},
|
|
2004
|
-
backstageIdentity: prepareBackstageIdentityResponse(backstageIdentity),
|
|
2005
2236
|
profile
|
|
2006
2237
|
};
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2238
|
+
if (this.signInResolver) {
|
|
2239
|
+
response.backstageIdentity = await this.signInResolver({
|
|
2240
|
+
result,
|
|
2241
|
+
profile
|
|
2242
|
+
}, {
|
|
2243
|
+
tokenIssuer: this.tokenIssuer,
|
|
2244
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
2245
|
+
logger: this.logger
|
|
2246
|
+
});
|
|
2012
2247
|
}
|
|
2013
|
-
|
|
2014
|
-
const keyValue = crypto__namespace.createPublicKey(keyText);
|
|
2015
|
-
this.keyCache.set(keyId, keyValue.export({ format: "pem", type: "spki" }));
|
|
2016
|
-
return keyValue;
|
|
2248
|
+
return response;
|
|
2017
2249
|
}
|
|
2018
2250
|
}
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2251
|
+
const defaultSignInResolver = async (info) => {
|
|
2252
|
+
const { profile } = info;
|
|
2253
|
+
if (!profile.email) {
|
|
2254
|
+
throw new Error("OIDC profile contained no email");
|
|
2255
|
+
}
|
|
2256
|
+
const id = profile.email.split("@")[0];
|
|
2257
|
+
return { id, token: "" };
|
|
2258
|
+
};
|
|
2259
|
+
const createOneLoginProvider = (options) => {
|
|
2260
|
+
return ({
|
|
2261
|
+
providerId,
|
|
2262
|
+
globalConfig,
|
|
2263
|
+
config,
|
|
2264
|
+
tokenIssuer,
|
|
2265
|
+
catalogApi,
|
|
2266
|
+
logger
|
|
2267
|
+
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
2268
|
+
var _a, _b;
|
|
2269
|
+
const clientId = envConfig.getString("clientId");
|
|
2270
|
+
const clientSecret = envConfig.getString("clientSecret");
|
|
2271
|
+
const issuer = envConfig.getString("issuer");
|
|
2272
|
+
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
2026
2273
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2027
2274
|
catalogApi,
|
|
2028
2275
|
tokenIssuer
|
|
2029
2276
|
});
|
|
2030
|
-
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
|
|
2031
|
-
profile: makeProfileInfo(fullProfile)
|
|
2277
|
+
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
|
|
2278
|
+
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
2032
2279
|
});
|
|
2033
|
-
const signInResolver = options == null ? void 0 : options.signIn.resolver;
|
|
2034
|
-
|
|
2035
|
-
|
|
2280
|
+
const signInResolver = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : defaultSignInResolver;
|
|
2281
|
+
const provider = new OneLoginProvider({
|
|
2282
|
+
clientId,
|
|
2283
|
+
clientSecret,
|
|
2284
|
+
callbackUrl,
|
|
2036
2285
|
issuer,
|
|
2037
|
-
signInResolver,
|
|
2038
2286
|
authHandler,
|
|
2287
|
+
signInResolver,
|
|
2039
2288
|
tokenIssuer,
|
|
2040
2289
|
catalogIdentityClient,
|
|
2041
2290
|
logger
|
|
2042
2291
|
});
|
|
2043
|
-
|
|
2292
|
+
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
2293
|
+
disableRefresh: false,
|
|
2294
|
+
providerId,
|
|
2295
|
+
tokenIssuer
|
|
2296
|
+
});
|
|
2297
|
+
});
|
|
2044
2298
|
};
|
|
2045
2299
|
|
|
2046
2300
|
class SamlAuthProvider {
|
|
@@ -2151,190 +2405,95 @@ const createSamlProvider = (options) => {
|
|
|
2151
2405
|
};
|
|
2152
2406
|
};
|
|
2153
2407
|
|
|
2154
|
-
|
|
2155
|
-
constructor(options, verify) {
|
|
2156
|
-
const optionsWithURLs = {
|
|
2157
|
-
...options,
|
|
2158
|
-
authorizationURL: `https://${options.domain}/authorize`,
|
|
2159
|
-
tokenURL: `https://${options.domain}/oauth/token`,
|
|
2160
|
-
userInfoURL: `https://${options.domain}/userinfo`,
|
|
2161
|
-
apiUrl: `https://${options.domain}/api`
|
|
2162
|
-
};
|
|
2163
|
-
super(optionsWithURLs, verify);
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2408
|
+
const IAP_JWT_HEADER = "x-goog-iap-jwt-assertion";
|
|
2166
2409
|
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
refreshToken
|
|
2183
|
-
});
|
|
2184
|
-
});
|
|
2185
|
-
}
|
|
2186
|
-
async start(req) {
|
|
2187
|
-
return await executeRedirectStrategy(req, this._strategy, {
|
|
2188
|
-
accessType: "offline",
|
|
2189
|
-
prompt: "consent",
|
|
2190
|
-
scope: req.scope,
|
|
2191
|
-
state: encodeState(req.state)
|
|
2192
|
-
});
|
|
2410
|
+
function createTokenValidator(audience, mockClient) {
|
|
2411
|
+
const client = mockClient != null ? mockClient : new googleAuthLibrary.OAuth2Client();
|
|
2412
|
+
return async function tokenValidator(token) {
|
|
2413
|
+
const response = await client.getIapPublicKeys();
|
|
2414
|
+
const ticket = await client.verifySignedJwtWithCertsAsync(token, response.pubkeys, audience, ["https://cloud.google.com/iap"]);
|
|
2415
|
+
const payload = ticket.getPayload();
|
|
2416
|
+
if (!payload) {
|
|
2417
|
+
throw new TypeError("Token had no payload");
|
|
2418
|
+
}
|
|
2419
|
+
return payload;
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
async function parseRequestToken(jwtToken, tokenValidator) {
|
|
2423
|
+
if (typeof jwtToken !== "string" || !jwtToken) {
|
|
2424
|
+
throw new errors.AuthenticationError(`Missing Google IAP header: ${IAP_JWT_HEADER}`);
|
|
2193
2425
|
}
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
profile,
|
|
2200
|
-
providerInfo: {
|
|
2201
|
-
idToken: result.params.id_token,
|
|
2202
|
-
accessToken: result.accessToken,
|
|
2203
|
-
scope: result.params.scope,
|
|
2204
|
-
expiresInSeconds: result.params.expires_in
|
|
2205
|
-
}
|
|
2206
|
-
}),
|
|
2207
|
-
refreshToken: privateInfo.refreshToken
|
|
2208
|
-
};
|
|
2426
|
+
let payload;
|
|
2427
|
+
try {
|
|
2428
|
+
payload = await tokenValidator(jwtToken);
|
|
2429
|
+
} catch (e) {
|
|
2430
|
+
throw new errors.AuthenticationError(`Google IAP token verification failed, ${e}`);
|
|
2209
2431
|
}
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
2213
|
-
const profile = makeProfileInfo(fullProfile, params.id_token);
|
|
2214
|
-
return this.populateIdentity({
|
|
2215
|
-
providerInfo: {
|
|
2216
|
-
accessToken,
|
|
2217
|
-
idToken: params.id_token,
|
|
2218
|
-
expiresInSeconds: params.expires_in,
|
|
2219
|
-
scope: params.scope
|
|
2220
|
-
},
|
|
2221
|
-
profile
|
|
2222
|
-
});
|
|
2432
|
+
if (!payload.sub || !payload.email) {
|
|
2433
|
+
throw new errors.AuthenticationError("Google IAP token payload is missing sub and/or email claim");
|
|
2223
2434
|
}
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2435
|
+
return {
|
|
2436
|
+
iapToken: {
|
|
2437
|
+
...payload,
|
|
2438
|
+
sub: payload.sub,
|
|
2439
|
+
email: payload.email
|
|
2228
2440
|
}
|
|
2229
|
-
|
|
2230
|
-
return { ...response, backstageIdentity: { id, token: "" } };
|
|
2231
|
-
}
|
|
2441
|
+
};
|
|
2232
2442
|
}
|
|
2233
|
-
const
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
const clientSecret = envConfig.getString("clientSecret");
|
|
2237
|
-
const domain = envConfig.getString("domain");
|
|
2238
|
-
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
2239
|
-
const provider = new Auth0AuthProvider({
|
|
2240
|
-
clientId,
|
|
2241
|
-
clientSecret,
|
|
2242
|
-
callbackUrl,
|
|
2243
|
-
domain
|
|
2244
|
-
});
|
|
2245
|
-
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
2246
|
-
disableRefresh: true,
|
|
2247
|
-
providerId,
|
|
2248
|
-
tokenIssuer
|
|
2249
|
-
});
|
|
2250
|
-
});
|
|
2251
|
-
};
|
|
2443
|
+
const defaultAuthHandler = async ({
|
|
2444
|
+
iapToken
|
|
2445
|
+
}) => ({ profile: { email: iapToken.email } });
|
|
2252
2446
|
|
|
2253
|
-
class
|
|
2447
|
+
class GcpIapProvider {
|
|
2254
2448
|
constructor(options) {
|
|
2255
|
-
this.
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
2262
|
-
done(void 0, {
|
|
2263
|
-
accessToken,
|
|
2264
|
-
refreshToken,
|
|
2265
|
-
params,
|
|
2266
|
-
fullProfile
|
|
2267
|
-
}, {
|
|
2268
|
-
refreshToken
|
|
2269
|
-
});
|
|
2270
|
-
});
|
|
2449
|
+
this.authHandler = options.authHandler;
|
|
2450
|
+
this.signInResolver = options.signInResolver;
|
|
2451
|
+
this.tokenValidator = options.tokenValidator;
|
|
2452
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
2453
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2454
|
+
this.logger = options.logger;
|
|
2271
2455
|
}
|
|
2272
|
-
async start(
|
|
2273
|
-
return await executeRedirectStrategy(req, this._strategy, {
|
|
2274
|
-
accessType: "offline",
|
|
2275
|
-
prompt: "consent",
|
|
2276
|
-
scope: "openid",
|
|
2277
|
-
state: encodeState(req.state)
|
|
2278
|
-
});
|
|
2456
|
+
async start() {
|
|
2279
2457
|
}
|
|
2280
|
-
async
|
|
2281
|
-
const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
|
|
2282
|
-
const profile = makeProfileInfo(result.fullProfile, result.params.id_token);
|
|
2283
|
-
return {
|
|
2284
|
-
response: await this.populateIdentity({
|
|
2285
|
-
profile,
|
|
2286
|
-
providerInfo: {
|
|
2287
|
-
idToken: result.params.id_token,
|
|
2288
|
-
accessToken: result.accessToken,
|
|
2289
|
-
scope: result.params.scope,
|
|
2290
|
-
expiresInSeconds: result.params.expires_in
|
|
2291
|
-
}
|
|
2292
|
-
}),
|
|
2293
|
-
refreshToken: privateInfo.refreshToken
|
|
2294
|
-
};
|
|
2458
|
+
async frameHandler() {
|
|
2295
2459
|
}
|
|
2296
|
-
async refresh(req) {
|
|
2297
|
-
const
|
|
2298
|
-
const
|
|
2299
|
-
const
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
idToken: params.id_token,
|
|
2304
|
-
expiresInSeconds: params.expires_in,
|
|
2305
|
-
scope: params.scope
|
|
2306
|
-
},
|
|
2307
|
-
profile
|
|
2460
|
+
async refresh(req, res) {
|
|
2461
|
+
const result = await parseRequestToken(req.header(IAP_JWT_HEADER), this.tokenValidator);
|
|
2462
|
+
const { profile } = await this.authHandler(result);
|
|
2463
|
+
const backstageIdentity = await this.signInResolver({ profile, result }, {
|
|
2464
|
+
tokenIssuer: this.tokenIssuer,
|
|
2465
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
2466
|
+
logger: this.logger
|
|
2308
2467
|
});
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
const id = profile.email.split("@")[0];
|
|
2316
|
-
return { ...response, backstageIdentity: { id, token: "" } };
|
|
2468
|
+
const response = {
|
|
2469
|
+
providerInfo: { iapToken: result.iapToken },
|
|
2470
|
+
profile,
|
|
2471
|
+
backstageIdentity: prepareBackstageIdentityResponse(backstageIdentity)
|
|
2472
|
+
};
|
|
2473
|
+
res.json(response);
|
|
2317
2474
|
}
|
|
2318
2475
|
}
|
|
2319
|
-
|
|
2320
|
-
return ({
|
|
2321
|
-
|
|
2322
|
-
const
|
|
2323
|
-
const
|
|
2324
|
-
const
|
|
2325
|
-
const
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
callbackUrl,
|
|
2329
|
-
issuer
|
|
2330
|
-
});
|
|
2331
|
-
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
2332
|
-
disableRefresh: false,
|
|
2333
|
-
providerId,
|
|
2476
|
+
function createGcpIapProvider(options) {
|
|
2477
|
+
return ({ config, tokenIssuer, catalogApi, logger }) => {
|
|
2478
|
+
var _a;
|
|
2479
|
+
const audience = config.getString("audience");
|
|
2480
|
+
const authHandler = (_a = options.authHandler) != null ? _a : defaultAuthHandler;
|
|
2481
|
+
const signInResolver = options.signIn.resolver;
|
|
2482
|
+
const tokenValidator = createTokenValidator(audience);
|
|
2483
|
+
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2484
|
+
catalogApi,
|
|
2334
2485
|
tokenIssuer
|
|
2335
2486
|
});
|
|
2336
|
-
|
|
2337
|
-
|
|
2487
|
+
return new GcpIapProvider({
|
|
2488
|
+
authHandler,
|
|
2489
|
+
signInResolver,
|
|
2490
|
+
tokenValidator,
|
|
2491
|
+
tokenIssuer,
|
|
2492
|
+
catalogIdentityClient,
|
|
2493
|
+
logger
|
|
2494
|
+
});
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
2338
2497
|
|
|
2339
2498
|
const factories = {
|
|
2340
2499
|
google: createGoogleProvider(),
|
|
@@ -2709,7 +2868,13 @@ async function createRouter(options) {
|
|
|
2709
2868
|
const secret = config.getOptionalString("auth.session.secret");
|
|
2710
2869
|
if (secret) {
|
|
2711
2870
|
router.use(cookieParser__default["default"](secret));
|
|
2712
|
-
|
|
2871
|
+
const enforceCookieSSL = authUrl.startsWith("https");
|
|
2872
|
+
router.use(session__default["default"]({
|
|
2873
|
+
secret,
|
|
2874
|
+
saveUninitialized: false,
|
|
2875
|
+
resave: false,
|
|
2876
|
+
cookie: { secure: enforceCookieSSL ? "auto" : false }
|
|
2877
|
+
}));
|
|
2713
2878
|
router.use(passport__default["default"].initialize());
|
|
2714
2879
|
router.use(passport__default["default"].session());
|
|
2715
2880
|
} else {
|
|
@@ -2795,8 +2960,10 @@ exports.OAuthEnvironmentHandler = OAuthEnvironmentHandler;
|
|
|
2795
2960
|
exports.bitbucketUserIdSignInResolver = bitbucketUserIdSignInResolver;
|
|
2796
2961
|
exports.bitbucketUsernameSignInResolver = bitbucketUsernameSignInResolver;
|
|
2797
2962
|
exports.createAtlassianProvider = createAtlassianProvider;
|
|
2963
|
+
exports.createAuth0Provider = createAuth0Provider;
|
|
2798
2964
|
exports.createAwsAlbProvider = createAwsAlbProvider;
|
|
2799
2965
|
exports.createBitbucketProvider = createBitbucketProvider;
|
|
2966
|
+
exports.createGcpIapProvider = createGcpIapProvider;
|
|
2800
2967
|
exports.createGithubProvider = createGithubProvider;
|
|
2801
2968
|
exports.createGitlabProvider = createGitlabProvider;
|
|
2802
2969
|
exports.createGoogleProvider = createGoogleProvider;
|
|
@@ -2804,6 +2971,7 @@ exports.createMicrosoftProvider = createMicrosoftProvider;
|
|
|
2804
2971
|
exports.createOAuth2Provider = createOAuth2Provider;
|
|
2805
2972
|
exports.createOidcProvider = createOidcProvider;
|
|
2806
2973
|
exports.createOktaProvider = createOktaProvider;
|
|
2974
|
+
exports.createOneLoginProvider = createOneLoginProvider;
|
|
2807
2975
|
exports.createOriginFilter = createOriginFilter;
|
|
2808
2976
|
exports.createRouter = createRouter;
|
|
2809
2977
|
exports.createSamlProvider = createSamlProvider;
|