@backstage/plugin-auth-backend 0.9.0-next.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +129 -0
- package/dist/index.cjs.js +728 -751
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +31 -123
- package/migrations/20210326100300_timestamptz.js +2 -2
- package/package.json +8 -7
package/dist/index.cjs.js
CHANGED
|
@@ -20,17 +20,18 @@ var passportGithub2 = require('passport-github2');
|
|
|
20
20
|
var passportGitlab2 = require('passport-gitlab2');
|
|
21
21
|
var passportGoogleOauth20 = require('passport-google-oauth20');
|
|
22
22
|
var passportMicrosoft = require('passport-microsoft');
|
|
23
|
-
var
|
|
24
|
-
var luxon = require('luxon');
|
|
25
|
-
var backendCommon = require('@backstage/backend-common');
|
|
26
|
-
var firestore = require('@google-cloud/firestore');
|
|
27
|
-
var lodash = require('lodash');
|
|
23
|
+
var pluginAuthNode = require('@backstage/plugin-auth-node');
|
|
28
24
|
var openidClient = require('openid-client');
|
|
29
25
|
var passportOktaOauth = require('passport-okta-oauth');
|
|
30
26
|
var passportOneloginOauth = require('passport-onelogin-oauth');
|
|
31
27
|
var passportSaml = require('passport-saml');
|
|
32
28
|
var googleAuthLibrary = require('google-auth-library');
|
|
33
29
|
var catalogClient = require('@backstage/catalog-client');
|
|
30
|
+
var uuid = require('uuid');
|
|
31
|
+
var luxon = require('luxon');
|
|
32
|
+
var backendCommon = require('@backstage/backend-common');
|
|
33
|
+
var firestore = require('@google-cloud/firestore');
|
|
34
|
+
var lodash = require('lodash');
|
|
34
35
|
var session = require('express-session');
|
|
35
36
|
var passport = require('passport');
|
|
36
37
|
var minimatch = require('minimatch');
|
|
@@ -149,15 +150,14 @@ const verifyNonce = (req, providerId) => {
|
|
|
149
150
|
throw new Error("Invalid nonce");
|
|
150
151
|
}
|
|
151
152
|
};
|
|
152
|
-
const
|
|
153
|
-
|
|
153
|
+
const defaultCookieConfigurer = ({
|
|
154
|
+
callbackUrl,
|
|
155
|
+
providerId
|
|
156
|
+
}) => {
|
|
157
|
+
const { hostname: domain, pathname, protocol } = new URL(callbackUrl);
|
|
154
158
|
const secure = protocol === "https:";
|
|
155
|
-
const
|
|
156
|
-
return {
|
|
157
|
-
cookieDomain,
|
|
158
|
-
cookiePath,
|
|
159
|
-
secure
|
|
160
|
-
};
|
|
159
|
+
const path = pathname.endsWith(`${providerId}/handler/frame`) ? pathname.slice(0, -"/handler/frame".length) : `${pathname}/${providerId}`;
|
|
160
|
+
return { domain, path, secure };
|
|
161
161
|
};
|
|
162
162
|
|
|
163
163
|
class OAuthEnvironmentHandler {
|
|
@@ -281,58 +281,54 @@ class OAuthAdapter {
|
|
|
281
281
|
this.setNonceCookie = (res, nonce) => {
|
|
282
282
|
res.cookie(`${this.options.providerId}-nonce`, nonce, {
|
|
283
283
|
maxAge: TEN_MINUTES_MS,
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
domain: this.options.cookieDomain,
|
|
287
|
-
path: `${this.options.cookiePath}/handler`,
|
|
288
|
-
httpOnly: true
|
|
284
|
+
...this.baseCookieOptions,
|
|
285
|
+
path: `${this.options.cookiePath}/handler`
|
|
289
286
|
});
|
|
290
287
|
};
|
|
291
|
-
this.
|
|
292
|
-
res.cookie(`${this.options.providerId}-scope`, scope, {
|
|
293
|
-
maxAge:
|
|
294
|
-
|
|
295
|
-
sameSite: "lax",
|
|
296
|
-
domain: this.options.cookieDomain,
|
|
297
|
-
path: `${this.options.cookiePath}/handler`,
|
|
298
|
-
httpOnly: true
|
|
288
|
+
this.setGrantedScopeCookie = (res, scope) => {
|
|
289
|
+
res.cookie(`${this.options.providerId}-granted-scope`, scope, {
|
|
290
|
+
maxAge: THOUSAND_DAYS_MS,
|
|
291
|
+
...this.baseCookieOptions
|
|
299
292
|
});
|
|
300
293
|
};
|
|
301
|
-
this.
|
|
302
|
-
return req.cookies[`${providerId}-scope`];
|
|
294
|
+
this.getGrantedScopeFromCookie = (req) => {
|
|
295
|
+
return req.cookies[`${this.options.providerId}-granted-scope`];
|
|
303
296
|
};
|
|
304
297
|
this.setRefreshTokenCookie = (res, refreshToken) => {
|
|
305
298
|
res.cookie(`${this.options.providerId}-refresh-token`, refreshToken, {
|
|
306
299
|
maxAge: THOUSAND_DAYS_MS,
|
|
307
|
-
|
|
308
|
-
sameSite: "lax",
|
|
309
|
-
domain: this.options.cookieDomain,
|
|
310
|
-
path: this.options.cookiePath,
|
|
311
|
-
httpOnly: true
|
|
300
|
+
...this.baseCookieOptions
|
|
312
301
|
});
|
|
313
302
|
};
|
|
314
303
|
this.removeRefreshTokenCookie = (res) => {
|
|
315
304
|
res.cookie(`${this.options.providerId}-refresh-token`, "", {
|
|
316
305
|
maxAge: 0,
|
|
317
|
-
|
|
318
|
-
sameSite: "lax",
|
|
319
|
-
domain: this.options.cookieDomain,
|
|
320
|
-
path: this.options.cookiePath,
|
|
321
|
-
httpOnly: true
|
|
306
|
+
...this.baseCookieOptions
|
|
322
307
|
});
|
|
323
308
|
};
|
|
309
|
+
this.baseCookieOptions = {
|
|
310
|
+
httpOnly: true,
|
|
311
|
+
sameSite: "lax",
|
|
312
|
+
secure: this.options.secure,
|
|
313
|
+
path: this.options.cookiePath,
|
|
314
|
+
domain: this.options.cookieDomain
|
|
315
|
+
};
|
|
324
316
|
}
|
|
325
317
|
static fromConfig(config, handlers, options) {
|
|
326
318
|
var _a;
|
|
327
319
|
const { origin: appOrigin } = new url.URL(config.appUrl);
|
|
328
|
-
const
|
|
329
|
-
const
|
|
320
|
+
const cookieConfigurer = (_a = config.cookieConfigurer) != null ? _a : defaultCookieConfigurer;
|
|
321
|
+
const cookieConfig = cookieConfigurer({
|
|
322
|
+
providerId: options.providerId,
|
|
323
|
+
baseUrl: config.baseUrl,
|
|
324
|
+
callbackUrl: options.callbackUrl
|
|
325
|
+
});
|
|
330
326
|
return new OAuthAdapter(handlers, {
|
|
331
327
|
...options,
|
|
332
328
|
appOrigin,
|
|
333
|
-
cookieDomain,
|
|
334
|
-
cookiePath,
|
|
335
|
-
secure,
|
|
329
|
+
cookieDomain: cookieConfig.domain,
|
|
330
|
+
cookiePath: cookieConfig.path,
|
|
331
|
+
secure: cookieConfig.secure,
|
|
336
332
|
isOriginAllowed: config.isOriginAllowed
|
|
337
333
|
});
|
|
338
334
|
}
|
|
@@ -344,12 +340,12 @@ class OAuthAdapter {
|
|
|
344
340
|
if (!env) {
|
|
345
341
|
throw new errors.InputError("No env provided in request query parameters");
|
|
346
342
|
}
|
|
347
|
-
if (this.options.persistScopes) {
|
|
348
|
-
this.setScopesCookie(res, scope);
|
|
349
|
-
}
|
|
350
343
|
const nonce = crypto__default["default"].randomBytes(16).toString("base64");
|
|
351
344
|
this.setNonceCookie(res, nonce);
|
|
352
345
|
const state = { nonce, env, origin };
|
|
346
|
+
if (this.options.persistScopes) {
|
|
347
|
+
state.scope = scope;
|
|
348
|
+
}
|
|
353
349
|
const forwardReq = Object.assign(req, { scope, state });
|
|
354
350
|
const { url, status } = await this.handlers.start(forwardReq);
|
|
355
351
|
res.statusCode = status || 302;
|
|
@@ -374,9 +370,9 @@ class OAuthAdapter {
|
|
|
374
370
|
}
|
|
375
371
|
verifyNonce(req, this.options.providerId);
|
|
376
372
|
const { response, refreshToken } = await this.handlers.handler(req);
|
|
377
|
-
if (this.options.persistScopes) {
|
|
378
|
-
|
|
379
|
-
response.providerInfo.scope =
|
|
373
|
+
if (this.options.persistScopes && state.scope) {
|
|
374
|
+
this.setGrantedScopeCookie(res, state.scope);
|
|
375
|
+
response.providerInfo.scope = state.scope;
|
|
380
376
|
}
|
|
381
377
|
if (refreshToken && !this.options.disableRefresh) {
|
|
382
378
|
this.setRefreshTokenCookie(res, refreshToken);
|
|
@@ -414,7 +410,10 @@ class OAuthAdapter {
|
|
|
414
410
|
if (!refreshToken) {
|
|
415
411
|
throw new errors.InputError("Missing session cookie");
|
|
416
412
|
}
|
|
417
|
-
|
|
413
|
+
let scope = (_b = (_a = req.query.scope) == null ? void 0 : _a.toString()) != null ? _b : "";
|
|
414
|
+
if (this.options.persistScopes) {
|
|
415
|
+
scope = this.getGrantedScopeFromCookie(req);
|
|
416
|
+
}
|
|
418
417
|
const forwardReq = Object.assign(req, { scope, refreshToken });
|
|
419
418
|
const { response, refreshToken: newRefreshToken } = await this.handlers.refresh(forwardReq);
|
|
420
419
|
const backstageIdentity = await this.populateIdentity(response.backstageIdentity);
|
|
@@ -718,7 +717,8 @@ const createAtlassianProvider = (options) => {
|
|
|
718
717
|
const clientId = envConfig.getString("clientId");
|
|
719
718
|
const clientSecret = envConfig.getString("clientSecret");
|
|
720
719
|
const scopes = envConfig.getString("scopes");
|
|
721
|
-
const
|
|
720
|
+
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
721
|
+
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
722
722
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
723
723
|
catalogApi,
|
|
724
724
|
tokenManager
|
|
@@ -736,9 +736,9 @@ const createAtlassianProvider = (options) => {
|
|
|
736
736
|
tokenIssuer
|
|
737
737
|
});
|
|
738
738
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
739
|
-
disableRefresh: true,
|
|
740
739
|
providerId,
|
|
741
|
-
tokenIssuer
|
|
740
|
+
tokenIssuer,
|
|
741
|
+
callbackUrl
|
|
742
742
|
});
|
|
743
743
|
});
|
|
744
744
|
};
|
|
@@ -854,7 +854,8 @@ const createAuth0Provider = (options) => {
|
|
|
854
854
|
const clientId = envConfig.getString("clientId");
|
|
855
855
|
const clientSecret = envConfig.getString("clientSecret");
|
|
856
856
|
const domain = envConfig.getString("domain");
|
|
857
|
-
const
|
|
857
|
+
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
858
|
+
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
858
859
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
859
860
|
catalogApi,
|
|
860
861
|
tokenManager
|
|
@@ -877,7 +878,8 @@ const createAuth0Provider = (options) => {
|
|
|
877
878
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
878
879
|
disableRefresh: true,
|
|
879
880
|
providerId,
|
|
880
|
-
tokenIssuer
|
|
881
|
+
tokenIssuer,
|
|
882
|
+
callbackUrl
|
|
881
883
|
});
|
|
882
884
|
});
|
|
883
885
|
};
|
|
@@ -1128,7 +1130,8 @@ const createBitbucketProvider = (options) => {
|
|
|
1128
1130
|
var _a;
|
|
1129
1131
|
const clientId = envConfig.getString("clientId");
|
|
1130
1132
|
const clientSecret = envConfig.getString("clientSecret");
|
|
1131
|
-
const
|
|
1133
|
+
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
1134
|
+
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1132
1135
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
1133
1136
|
catalogApi,
|
|
1134
1137
|
tokenManager
|
|
@@ -1149,11 +1152,14 @@ const createBitbucketProvider = (options) => {
|
|
|
1149
1152
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
1150
1153
|
disableRefresh: false,
|
|
1151
1154
|
providerId,
|
|
1152
|
-
tokenIssuer
|
|
1155
|
+
tokenIssuer,
|
|
1156
|
+
callbackUrl
|
|
1153
1157
|
});
|
|
1154
1158
|
});
|
|
1155
1159
|
};
|
|
1156
1160
|
|
|
1161
|
+
const ACCESS_TOKEN_PREFIX = "access-token.";
|
|
1162
|
+
const BACKSTAGE_SESSION_EXPIRATION = 3600;
|
|
1157
1163
|
class GithubAuthProvider {
|
|
1158
1164
|
constructor(options) {
|
|
1159
1165
|
this.signInResolver = options.signInResolver;
|
|
@@ -1181,21 +1187,43 @@ class GithubAuthProvider {
|
|
|
1181
1187
|
}
|
|
1182
1188
|
async handler(req) {
|
|
1183
1189
|
const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
|
|
1190
|
+
let refreshToken = privateInfo.refreshToken;
|
|
1191
|
+
if (!refreshToken && !result.params.expires_in) {
|
|
1192
|
+
refreshToken = ACCESS_TOKEN_PREFIX + result.accessToken;
|
|
1193
|
+
}
|
|
1184
1194
|
return {
|
|
1185
1195
|
response: await this.handleResult(result),
|
|
1186
|
-
refreshToken
|
|
1196
|
+
refreshToken
|
|
1187
1197
|
};
|
|
1188
1198
|
}
|
|
1189
1199
|
async refresh(req) {
|
|
1190
|
-
const {
|
|
1191
|
-
|
|
1200
|
+
const { scope, refreshToken } = req;
|
|
1201
|
+
if (refreshToken == null ? void 0 : refreshToken.startsWith(ACCESS_TOKEN_PREFIX)) {
|
|
1202
|
+
const accessToken = refreshToken.slice(ACCESS_TOKEN_PREFIX.length);
|
|
1203
|
+
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken).catch((error) => {
|
|
1204
|
+
var _a;
|
|
1205
|
+
if (((_a = error.oauthError) == null ? void 0 : _a.statusCode) === 401) {
|
|
1206
|
+
throw new Error("Invalid access token");
|
|
1207
|
+
}
|
|
1208
|
+
throw error;
|
|
1209
|
+
});
|
|
1210
|
+
return {
|
|
1211
|
+
response: await this.handleResult({
|
|
1212
|
+
fullProfile,
|
|
1213
|
+
params: { scope },
|
|
1214
|
+
accessToken
|
|
1215
|
+
}),
|
|
1216
|
+
refreshToken
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
const result = await executeRefreshTokenStrategy(this._strategy, refreshToken, scope);
|
|
1192
1220
|
return {
|
|
1193
1221
|
response: await this.handleResult({
|
|
1194
|
-
fullProfile,
|
|
1195
|
-
params,
|
|
1196
|
-
accessToken
|
|
1222
|
+
fullProfile: await executeFetchUserProfileStrategy(this._strategy, result.accessToken),
|
|
1223
|
+
params: { ...result.params, scope },
|
|
1224
|
+
accessToken: result.accessToken
|
|
1197
1225
|
}),
|
|
1198
|
-
refreshToken
|
|
1226
|
+
refreshToken: result.refreshToken
|
|
1199
1227
|
};
|
|
1200
1228
|
}
|
|
1201
1229
|
async handleResult(result) {
|
|
@@ -1206,21 +1234,28 @@ class GithubAuthProvider {
|
|
|
1206
1234
|
};
|
|
1207
1235
|
const { profile } = await this.authHandler(result, context);
|
|
1208
1236
|
const expiresInStr = result.params.expires_in;
|
|
1209
|
-
|
|
1237
|
+
let expiresInSeconds = expiresInStr === void 0 ? void 0 : Number(expiresInStr);
|
|
1238
|
+
let backstageIdentity = void 0;
|
|
1239
|
+
if (this.signInResolver) {
|
|
1240
|
+
backstageIdentity = await this.signInResolver({
|
|
1241
|
+
result,
|
|
1242
|
+
profile
|
|
1243
|
+
}, context);
|
|
1244
|
+
if (expiresInSeconds) {
|
|
1245
|
+
expiresInSeconds = Math.min(expiresInSeconds, BACKSTAGE_SESSION_EXPIRATION);
|
|
1246
|
+
} else {
|
|
1247
|
+
expiresInSeconds = BACKSTAGE_SESSION_EXPIRATION;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return {
|
|
1251
|
+
backstageIdentity,
|
|
1210
1252
|
providerInfo: {
|
|
1211
1253
|
accessToken: result.accessToken,
|
|
1212
1254
|
scope: result.params.scope,
|
|
1213
|
-
expiresInSeconds
|
|
1255
|
+
expiresInSeconds
|
|
1214
1256
|
},
|
|
1215
1257
|
profile
|
|
1216
1258
|
};
|
|
1217
|
-
if (this.signInResolver) {
|
|
1218
|
-
response.backstageIdentity = await this.signInResolver({
|
|
1219
|
-
result,
|
|
1220
|
-
profile
|
|
1221
|
-
}, context);
|
|
1222
|
-
}
|
|
1223
|
-
return response;
|
|
1224
1259
|
}
|
|
1225
1260
|
}
|
|
1226
1261
|
const githubDefaultSignInResolver = async (info, ctx) => {
|
|
@@ -1392,7 +1427,8 @@ const createGitlabProvider = (options) => {
|
|
|
1392
1427
|
const clientSecret = envConfig.getString("clientSecret");
|
|
1393
1428
|
const audience = envConfig.getOptionalString("audience");
|
|
1394
1429
|
const baseUrl = audience || "https://gitlab.com";
|
|
1395
|
-
const
|
|
1430
|
+
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
1431
|
+
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1396
1432
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
1397
1433
|
catalogApi,
|
|
1398
1434
|
tokenManager
|
|
@@ -1418,7 +1454,8 @@ const createGitlabProvider = (options) => {
|
|
|
1418
1454
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
1419
1455
|
disableRefresh: false,
|
|
1420
1456
|
providerId,
|
|
1421
|
-
tokenIssuer
|
|
1457
|
+
tokenIssuer,
|
|
1458
|
+
callbackUrl
|
|
1422
1459
|
});
|
|
1423
1460
|
});
|
|
1424
1461
|
};
|
|
@@ -1547,7 +1584,8 @@ const createGoogleProvider = (options) => {
|
|
|
1547
1584
|
var _a, _b;
|
|
1548
1585
|
const clientId = envConfig.getString("clientId");
|
|
1549
1586
|
const clientSecret = envConfig.getString("clientSecret");
|
|
1550
|
-
const
|
|
1587
|
+
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
1588
|
+
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1551
1589
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
1552
1590
|
catalogApi,
|
|
1553
1591
|
tokenManager
|
|
@@ -1574,7 +1612,8 @@ const createGoogleProvider = (options) => {
|
|
|
1574
1612
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
1575
1613
|
disableRefresh: false,
|
|
1576
1614
|
providerId,
|
|
1577
|
-
tokenIssuer
|
|
1615
|
+
tokenIssuer,
|
|
1616
|
+
callbackUrl
|
|
1578
1617
|
});
|
|
1579
1618
|
});
|
|
1580
1619
|
};
|
|
@@ -1706,7 +1745,8 @@ const createMicrosoftProvider = (options) => {
|
|
|
1706
1745
|
const clientId = envConfig.getString("clientId");
|
|
1707
1746
|
const clientSecret = envConfig.getString("clientSecret");
|
|
1708
1747
|
const tenantId = envConfig.getString("tenantId");
|
|
1709
|
-
const
|
|
1748
|
+
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
1749
|
+
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1710
1750
|
const authorizationUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`;
|
|
1711
1751
|
const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
|
|
1712
1752
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
@@ -1737,7 +1777,8 @@ const createMicrosoftProvider = (options) => {
|
|
|
1737
1777
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
1738
1778
|
disableRefresh: false,
|
|
1739
1779
|
providerId,
|
|
1740
|
-
tokenIssuer
|
|
1780
|
+
tokenIssuer,
|
|
1781
|
+
callbackUrl
|
|
1741
1782
|
});
|
|
1742
1783
|
});
|
|
1743
1784
|
};
|
|
@@ -1851,7 +1892,8 @@ const createOAuth2Provider = (options) => {
|
|
|
1851
1892
|
var _a, _b, _c;
|
|
1852
1893
|
const clientId = envConfig.getString("clientId");
|
|
1853
1894
|
const clientSecret = envConfig.getString("clientSecret");
|
|
1854
|
-
const
|
|
1895
|
+
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
1896
|
+
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1855
1897
|
const authorizationUrl = envConfig.getString("authorizationUrl");
|
|
1856
1898
|
const tokenUrl = envConfig.getString("tokenUrl");
|
|
1857
1899
|
const scope = envConfig.getOptionalString("scope");
|
|
@@ -1887,447 +1929,108 @@ const createOAuth2Provider = (options) => {
|
|
|
1887
1929
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
1888
1930
|
disableRefresh,
|
|
1889
1931
|
providerId,
|
|
1890
|
-
tokenIssuer
|
|
1932
|
+
tokenIssuer,
|
|
1933
|
+
callbackUrl
|
|
1891
1934
|
});
|
|
1892
1935
|
});
|
|
1893
1936
|
};
|
|
1894
1937
|
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
const router = Router__default["default"]();
|
|
1898
|
-
const config = {
|
|
1899
|
-
issuer: baseUrl,
|
|
1900
|
-
token_endpoint: `${baseUrl}/v1/token`,
|
|
1901
|
-
userinfo_endpoint: `${baseUrl}/v1/userinfo`,
|
|
1902
|
-
jwks_uri: `${baseUrl}/.well-known/jwks.json`,
|
|
1903
|
-
response_types_supported: ["id_token"],
|
|
1904
|
-
subject_types_supported: ["public"],
|
|
1905
|
-
id_token_signing_alg_values_supported: ["RS256"],
|
|
1906
|
-
scopes_supported: ["openid"],
|
|
1907
|
-
token_endpoint_auth_methods_supported: [],
|
|
1908
|
-
claims_supported: ["sub"],
|
|
1909
|
-
grant_types_supported: []
|
|
1910
|
-
};
|
|
1911
|
-
router.get("/.well-known/openid-configuration", (_req, res) => {
|
|
1912
|
-
res.json(config);
|
|
1913
|
-
});
|
|
1914
|
-
router.get("/.well-known/jwks.json", async (_req, res) => {
|
|
1915
|
-
const { keys } = await tokenIssuer.listPublicKeys();
|
|
1916
|
-
res.json({ keys });
|
|
1917
|
-
});
|
|
1918
|
-
router.get("/v1/token", (_req, res) => {
|
|
1919
|
-
res.status(501).send("Not Implemented");
|
|
1920
|
-
});
|
|
1921
|
-
router.get("/v1/userinfo", (_req, res) => {
|
|
1922
|
-
res.status(501).send("Not Implemented");
|
|
1923
|
-
});
|
|
1924
|
-
return router;
|
|
1925
|
-
}
|
|
1926
|
-
|
|
1927
|
-
const CLOCK_MARGIN_S = 10;
|
|
1928
|
-
class IdentityClient {
|
|
1938
|
+
const OAUTH2_PROXY_JWT_HEADER = "X-OAUTH2-PROXY-ID-TOKEN";
|
|
1939
|
+
class Oauth2ProxyAuthProvider {
|
|
1929
1940
|
constructor(options) {
|
|
1930
|
-
this.
|
|
1931
|
-
this.
|
|
1932
|
-
this.
|
|
1933
|
-
this.
|
|
1941
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
1942
|
+
this.logger = options.logger;
|
|
1943
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
1944
|
+
this.signInResolver = options.signInResolver;
|
|
1945
|
+
this.authHandler = options.authHandler;
|
|
1934
1946
|
}
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
if (!token) {
|
|
1938
|
-
throw new errors.AuthenticationError("No token specified");
|
|
1939
|
-
}
|
|
1940
|
-
const key = await this.getKey(token);
|
|
1941
|
-
if (!key) {
|
|
1942
|
-
throw new errors.AuthenticationError("No signing key matching token found");
|
|
1943
|
-
}
|
|
1944
|
-
const decoded = jose.JWT.IdToken.verify(token, key, {
|
|
1945
|
-
algorithms: ["ES256"],
|
|
1946
|
-
audience: "backstage",
|
|
1947
|
-
issuer: this.issuer
|
|
1948
|
-
});
|
|
1949
|
-
if (!decoded.sub) {
|
|
1950
|
-
throw new errors.AuthenticationError("No user sub found in token");
|
|
1951
|
-
}
|
|
1952
|
-
const user = {
|
|
1953
|
-
id: decoded.sub,
|
|
1954
|
-
token,
|
|
1955
|
-
identity: {
|
|
1956
|
-
type: "user",
|
|
1957
|
-
userEntityRef: decoded.sub,
|
|
1958
|
-
ownershipEntityRefs: (_a = decoded.ent) != null ? _a : []
|
|
1959
|
-
}
|
|
1960
|
-
};
|
|
1961
|
-
return user;
|
|
1947
|
+
frameHandler() {
|
|
1948
|
+
return Promise.resolve(void 0);
|
|
1962
1949
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1950
|
+
async refresh(req, res) {
|
|
1951
|
+
try {
|
|
1952
|
+
const result = this.getResult(req);
|
|
1953
|
+
const response = await this.handleResult(result);
|
|
1954
|
+
res.json(response);
|
|
1955
|
+
} catch (e) {
|
|
1956
|
+
this.logger.error(`Exception occurred during ${OAUTH2_PROXY_JWT_HEADER} refresh`, e);
|
|
1957
|
+
res.status(401);
|
|
1958
|
+
res.end();
|
|
1966
1959
|
}
|
|
1967
|
-
const matches = authorizationHeader.match(/Bearer\s+(\S+)/i);
|
|
1968
|
-
return matches == null ? void 0 : matches[1];
|
|
1969
1960
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
complete: true
|
|
1973
|
-
});
|
|
1974
|
-
const keyStoreHasKey = !!this.keyStore.get({ kid: header.kid });
|
|
1975
|
-
const issuedAfterLastRefresh = (payload == null ? void 0 : payload.iat) && payload.iat > this.keyStoreUpdated - CLOCK_MARGIN_S;
|
|
1976
|
-
if (!keyStoreHasKey && issuedAfterLastRefresh) {
|
|
1977
|
-
await this.refreshKeyStore();
|
|
1978
|
-
}
|
|
1979
|
-
return this.keyStore.get({ kid: header.kid });
|
|
1961
|
+
start() {
|
|
1962
|
+
return Promise.resolve(void 0);
|
|
1980
1963
|
}
|
|
1981
|
-
async
|
|
1982
|
-
const
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1964
|
+
async handleResult(result) {
|
|
1965
|
+
const ctx = {
|
|
1966
|
+
logger: this.logger,
|
|
1967
|
+
tokenIssuer: this.tokenIssuer,
|
|
1968
|
+
catalogIdentityClient: this.catalogIdentityClient
|
|
1969
|
+
};
|
|
1970
|
+
const { profile } = await this.authHandler(result, ctx);
|
|
1971
|
+
const backstageSignInResult = await this.signInResolver({
|
|
1972
|
+
result,
|
|
1973
|
+
profile
|
|
1974
|
+
}, ctx);
|
|
1975
|
+
return {
|
|
1976
|
+
providerInfo: {
|
|
1977
|
+
accessToken: result.accessToken
|
|
1978
|
+
},
|
|
1979
|
+
backstageIdentity: prepareBackstageIdentityResponse(backstageSignInResult),
|
|
1980
|
+
profile
|
|
1981
|
+
};
|
|
1991
1982
|
}
|
|
1992
|
-
|
|
1993
|
-
const
|
|
1994
|
-
const
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1983
|
+
getResult(req) {
|
|
1984
|
+
const authHeader = req.header(OAUTH2_PROXY_JWT_HEADER);
|
|
1985
|
+
const jwt = pluginAuthNode.getBearerTokenFromAuthorizationHeader(authHeader);
|
|
1986
|
+
if (!jwt) {
|
|
1987
|
+
throw new errors.AuthenticationError(`Missing or in incorrect format - Oauth2Proxy OIDC header: ${OAUTH2_PROXY_JWT_HEADER}`);
|
|
1988
|
+
}
|
|
1989
|
+
const decodedJWT = jose.JWT.decode(jwt);
|
|
1990
|
+
return {
|
|
1991
|
+
fullProfile: decodedJWT,
|
|
1992
|
+
accessToken: jwt
|
|
1993
|
+
};
|
|
1999
1994
|
}
|
|
2000
1995
|
}
|
|
1996
|
+
const createOauth2ProxyProvider = (options) => ({ catalogApi, logger, tokenIssuer, tokenManager }) => {
|
|
1997
|
+
const signInResolver = options.signIn.resolver;
|
|
1998
|
+
const authHandler = options.authHandler;
|
|
1999
|
+
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2000
|
+
catalogApi,
|
|
2001
|
+
tokenManager
|
|
2002
|
+
});
|
|
2003
|
+
return new Oauth2ProxyAuthProvider({
|
|
2004
|
+
logger,
|
|
2005
|
+
signInResolver,
|
|
2006
|
+
authHandler,
|
|
2007
|
+
tokenIssuer,
|
|
2008
|
+
catalogIdentityClient
|
|
2009
|
+
});
|
|
2010
|
+
};
|
|
2001
2011
|
|
|
2002
|
-
|
|
2003
|
-
class TokenFactory {
|
|
2012
|
+
class OidcAuthProvider {
|
|
2004
2013
|
constructor(options) {
|
|
2005
|
-
this.
|
|
2014
|
+
this.implementation = this.setupStrategy(options);
|
|
2015
|
+
this.scope = options.scope;
|
|
2016
|
+
this.prompt = options.prompt;
|
|
2017
|
+
this.signInResolver = options.signInResolver;
|
|
2018
|
+
this.authHandler = options.authHandler;
|
|
2019
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
2020
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2006
2021
|
this.logger = options.logger;
|
|
2007
|
-
this.keyStore = options.keyStore;
|
|
2008
|
-
this.keyDurationSeconds = options.keyDurationSeconds;
|
|
2009
|
-
}
|
|
2010
|
-
async issueToken(params) {
|
|
2011
|
-
const key = await this.getKey();
|
|
2012
|
-
const iss = this.issuer;
|
|
2013
|
-
const sub = params.claims.sub;
|
|
2014
|
-
const ent = params.claims.ent;
|
|
2015
|
-
const aud = "backstage";
|
|
2016
|
-
const iat = Math.floor(Date.now() / MS_IN_S);
|
|
2017
|
-
const exp = iat + this.keyDurationSeconds;
|
|
2018
|
-
this.logger.info(`Issuing token for ${sub}, with entities ${ent != null ? ent : []}`);
|
|
2019
|
-
return jose.JWS.sign({ iss, sub, aud, iat, exp, ent }, key, {
|
|
2020
|
-
alg: key.alg,
|
|
2021
|
-
kid: key.kid
|
|
2022
|
-
});
|
|
2023
2022
|
}
|
|
2024
|
-
async
|
|
2025
|
-
const {
|
|
2026
|
-
const
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
expiredKeys.push(key);
|
|
2034
|
-
} else {
|
|
2035
|
-
validKeys.push(key);
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
if (expiredKeys.length > 0) {
|
|
2039
|
-
const kids = expiredKeys.map(({ key }) => key.kid);
|
|
2040
|
-
this.logger.info(`Removing expired signing keys, '${kids.join("', '")}'`);
|
|
2041
|
-
this.keyStore.removeKeys(kids).catch((error) => {
|
|
2042
|
-
this.logger.error(`Failed to remove expired keys, ${error}`);
|
|
2043
|
-
});
|
|
2023
|
+
async start(req) {
|
|
2024
|
+
const { strategy } = await this.implementation;
|
|
2025
|
+
const options = {
|
|
2026
|
+
scope: req.scope || this.scope || "openid profile email",
|
|
2027
|
+
state: encodeState(req.state)
|
|
2028
|
+
};
|
|
2029
|
+
const prompt = this.prompt || "none";
|
|
2030
|
+
if (prompt !== "auto") {
|
|
2031
|
+
options.prompt = prompt;
|
|
2044
2032
|
}
|
|
2045
|
-
return
|
|
2046
|
-
}
|
|
2047
|
-
async getKey() {
|
|
2048
|
-
if (this.privateKeyPromise) {
|
|
2049
|
-
if (this.keyExpiry && luxon.DateTime.fromJSDate(this.keyExpiry) > luxon.DateTime.local()) {
|
|
2050
|
-
return this.privateKeyPromise;
|
|
2051
|
-
}
|
|
2052
|
-
this.logger.info(`Signing key has expired, generating new key`);
|
|
2053
|
-
delete this.privateKeyPromise;
|
|
2054
|
-
}
|
|
2055
|
-
this.keyExpiry = luxon.DateTime.utc().plus({
|
|
2056
|
-
seconds: this.keyDurationSeconds
|
|
2057
|
-
}).toJSDate();
|
|
2058
|
-
const promise = (async () => {
|
|
2059
|
-
const key = await jose.JWK.generate("EC", "P-256", {
|
|
2060
|
-
use: "sig",
|
|
2061
|
-
kid: uuid.v4(),
|
|
2062
|
-
alg: "ES256"
|
|
2063
|
-
});
|
|
2064
|
-
this.logger.info(`Created new signing key ${key.kid}`);
|
|
2065
|
-
await this.keyStore.addKey(key.toJWK(false));
|
|
2066
|
-
return key;
|
|
2067
|
-
})();
|
|
2068
|
-
this.privateKeyPromise = promise;
|
|
2069
|
-
try {
|
|
2070
|
-
await promise;
|
|
2071
|
-
} catch (error) {
|
|
2072
|
-
this.logger.error(`Failed to generate new signing key, ${error}`);
|
|
2073
|
-
delete this.keyExpiry;
|
|
2074
|
-
delete this.privateKeyPromise;
|
|
2075
|
-
}
|
|
2076
|
-
return promise;
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
const migrationsDir = backendCommon.resolvePackagePath("@backstage/plugin-auth-backend", "migrations");
|
|
2081
|
-
const TABLE = "signing_keys";
|
|
2082
|
-
const parseDate = (date) => {
|
|
2083
|
-
const parsedDate = typeof date === "string" ? luxon.DateTime.fromSQL(date, { zone: "UTC" }) : luxon.DateTime.fromJSDate(date);
|
|
2084
|
-
if (!parsedDate.isValid) {
|
|
2085
|
-
throw new Error(`Failed to parse date, reason: ${parsedDate.invalidReason}, explanation: ${parsedDate.invalidExplanation}`);
|
|
2086
|
-
}
|
|
2087
|
-
return parsedDate.toJSDate();
|
|
2088
|
-
};
|
|
2089
|
-
class DatabaseKeyStore {
|
|
2090
|
-
static async create(options) {
|
|
2091
|
-
const { database } = options;
|
|
2092
|
-
await database.migrate.latest({
|
|
2093
|
-
directory: migrationsDir
|
|
2094
|
-
});
|
|
2095
|
-
return new DatabaseKeyStore(options);
|
|
2096
|
-
}
|
|
2097
|
-
constructor(options) {
|
|
2098
|
-
this.database = options.database;
|
|
2099
|
-
}
|
|
2100
|
-
async addKey(key) {
|
|
2101
|
-
await this.database(TABLE).insert({
|
|
2102
|
-
kid: key.kid,
|
|
2103
|
-
key: JSON.stringify(key)
|
|
2104
|
-
});
|
|
2105
|
-
}
|
|
2106
|
-
async listKeys() {
|
|
2107
|
-
const rows = await this.database(TABLE).select();
|
|
2108
|
-
return {
|
|
2109
|
-
items: rows.map((row) => ({
|
|
2110
|
-
key: JSON.parse(row.key),
|
|
2111
|
-
createdAt: parseDate(row.created_at)
|
|
2112
|
-
}))
|
|
2113
|
-
};
|
|
2114
|
-
}
|
|
2115
|
-
async removeKeys(kids) {
|
|
2116
|
-
await this.database(TABLE).delete().whereIn("kid", kids);
|
|
2117
|
-
}
|
|
2118
|
-
}
|
|
2119
|
-
|
|
2120
|
-
class MemoryKeyStore {
|
|
2121
|
-
constructor() {
|
|
2122
|
-
this.keys = /* @__PURE__ */ new Map();
|
|
2123
|
-
}
|
|
2124
|
-
async addKey(key) {
|
|
2125
|
-
this.keys.set(key.kid, {
|
|
2126
|
-
createdAt: luxon.DateTime.utc().toJSDate(),
|
|
2127
|
-
key: JSON.stringify(key)
|
|
2128
|
-
});
|
|
2129
|
-
}
|
|
2130
|
-
async removeKeys(kids) {
|
|
2131
|
-
for (const kid of kids) {
|
|
2132
|
-
this.keys.delete(kid);
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
async listKeys() {
|
|
2136
|
-
return {
|
|
2137
|
-
items: Array.from(this.keys).map(([, { createdAt, key: keyStr }]) => ({
|
|
2138
|
-
createdAt,
|
|
2139
|
-
key: JSON.parse(keyStr)
|
|
2140
|
-
}))
|
|
2141
|
-
};
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
|
|
2145
|
-
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
2146
|
-
const DEFAULT_DOCUMENT_PATH = "sessions";
|
|
2147
|
-
class FirestoreKeyStore {
|
|
2148
|
-
constructor(database, path, timeout) {
|
|
2149
|
-
this.database = database;
|
|
2150
|
-
this.path = path;
|
|
2151
|
-
this.timeout = timeout;
|
|
2152
|
-
}
|
|
2153
|
-
static async create(settings) {
|
|
2154
|
-
const { path, timeout, ...firestoreSettings } = settings != null ? settings : {};
|
|
2155
|
-
const database = new firestore.Firestore(firestoreSettings);
|
|
2156
|
-
return new FirestoreKeyStore(database, path != null ? path : DEFAULT_DOCUMENT_PATH, timeout != null ? timeout : DEFAULT_TIMEOUT_MS);
|
|
2157
|
-
}
|
|
2158
|
-
static async verifyConnection(keyStore, logger) {
|
|
2159
|
-
try {
|
|
2160
|
-
await keyStore.verify();
|
|
2161
|
-
} catch (error) {
|
|
2162
|
-
if (process.env.NODE_ENV !== "development") {
|
|
2163
|
-
throw new Error(`Failed to connect to database: ${error.message}`);
|
|
2164
|
-
}
|
|
2165
|
-
logger == null ? void 0 : logger.warn(`Failed to connect to database: ${error.message}`);
|
|
2166
|
-
}
|
|
2167
|
-
}
|
|
2168
|
-
async addKey(key) {
|
|
2169
|
-
await this.withTimeout(this.database.collection(this.path).doc(key.kid).set({
|
|
2170
|
-
kid: key.kid,
|
|
2171
|
-
key: JSON.stringify(key)
|
|
2172
|
-
}));
|
|
2173
|
-
}
|
|
2174
|
-
async listKeys() {
|
|
2175
|
-
const keys = await this.withTimeout(this.database.collection(this.path).get());
|
|
2176
|
-
return {
|
|
2177
|
-
items: keys.docs.map((key) => ({
|
|
2178
|
-
key: key.data(),
|
|
2179
|
-
createdAt: key.createTime.toDate()
|
|
2180
|
-
}))
|
|
2181
|
-
};
|
|
2182
|
-
}
|
|
2183
|
-
async removeKeys(kids) {
|
|
2184
|
-
for (const kid of kids) {
|
|
2185
|
-
await this.withTimeout(this.database.collection(this.path).doc(kid).delete());
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
async withTimeout(operation) {
|
|
2189
|
-
const timer = new Promise((_, reject) => setTimeout(() => {
|
|
2190
|
-
reject(new Error(`Operation timed out after ${this.timeout}ms`));
|
|
2191
|
-
}, this.timeout));
|
|
2192
|
-
return Promise.race([operation, timer]);
|
|
2193
|
-
}
|
|
2194
|
-
async verify() {
|
|
2195
|
-
await this.withTimeout(this.database.collection(this.path).limit(1).get());
|
|
2196
|
-
}
|
|
2197
|
-
}
|
|
2198
|
-
|
|
2199
|
-
class KeyStores {
|
|
2200
|
-
static async fromConfig(config, options) {
|
|
2201
|
-
var _a;
|
|
2202
|
-
const { logger, database } = options != null ? options : {};
|
|
2203
|
-
const ks = config.getOptionalConfig("auth.keyStore");
|
|
2204
|
-
const provider = (_a = ks == null ? void 0 : ks.getOptionalString("provider")) != null ? _a : "database";
|
|
2205
|
-
logger == null ? void 0 : logger.info(`Configuring "${provider}" as KeyStore provider`);
|
|
2206
|
-
if (provider === "database") {
|
|
2207
|
-
if (!database) {
|
|
2208
|
-
throw new Error("This KeyStore provider requires a database");
|
|
2209
|
-
}
|
|
2210
|
-
return await DatabaseKeyStore.create({
|
|
2211
|
-
database: await database.getClient()
|
|
2212
|
-
});
|
|
2213
|
-
}
|
|
2214
|
-
if (provider === "memory") {
|
|
2215
|
-
return new MemoryKeyStore();
|
|
2216
|
-
}
|
|
2217
|
-
if (provider === "firestore") {
|
|
2218
|
-
const settings = ks == null ? void 0 : ks.getConfig(provider);
|
|
2219
|
-
const keyStore = await FirestoreKeyStore.create(lodash.pickBy({
|
|
2220
|
-
projectId: settings == null ? void 0 : settings.getOptionalString("projectId"),
|
|
2221
|
-
keyFilename: settings == null ? void 0 : settings.getOptionalString("keyFilename"),
|
|
2222
|
-
host: settings == null ? void 0 : settings.getOptionalString("host"),
|
|
2223
|
-
port: settings == null ? void 0 : settings.getOptionalNumber("port"),
|
|
2224
|
-
ssl: settings == null ? void 0 : settings.getOptionalBoolean("ssl"),
|
|
2225
|
-
path: settings == null ? void 0 : settings.getOptionalString("path"),
|
|
2226
|
-
timeout: settings == null ? void 0 : settings.getOptionalNumber("timeout")
|
|
2227
|
-
}, (value) => value !== void 0));
|
|
2228
|
-
await FirestoreKeyStore.verifyConnection(keyStore, logger);
|
|
2229
|
-
return keyStore;
|
|
2230
|
-
}
|
|
2231
|
-
throw new Error(`Unknown KeyStore provider: ${provider}`);
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
|
|
2235
|
-
const OAUTH2_PROXY_JWT_HEADER = "X-OAUTH2-PROXY-ID-TOKEN";
|
|
2236
|
-
class Oauth2ProxyAuthProvider {
|
|
2237
|
-
constructor(options) {
|
|
2238
|
-
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2239
|
-
this.logger = options.logger;
|
|
2240
|
-
this.tokenIssuer = options.tokenIssuer;
|
|
2241
|
-
this.signInResolver = options.signInResolver;
|
|
2242
|
-
this.authHandler = options.authHandler;
|
|
2243
|
-
}
|
|
2244
|
-
frameHandler() {
|
|
2245
|
-
return Promise.resolve(void 0);
|
|
2246
|
-
}
|
|
2247
|
-
async refresh(req, res) {
|
|
2248
|
-
try {
|
|
2249
|
-
const result = this.getResult(req);
|
|
2250
|
-
const response = await this.handleResult(result);
|
|
2251
|
-
res.json(response);
|
|
2252
|
-
} catch (e) {
|
|
2253
|
-
this.logger.error(`Exception occurred during ${OAUTH2_PROXY_JWT_HEADER} refresh`, e);
|
|
2254
|
-
res.status(401);
|
|
2255
|
-
res.end();
|
|
2256
|
-
}
|
|
2257
|
-
}
|
|
2258
|
-
start() {
|
|
2259
|
-
return Promise.resolve(void 0);
|
|
2260
|
-
}
|
|
2261
|
-
async handleResult(result) {
|
|
2262
|
-
const ctx = {
|
|
2263
|
-
logger: this.logger,
|
|
2264
|
-
tokenIssuer: this.tokenIssuer,
|
|
2265
|
-
catalogIdentityClient: this.catalogIdentityClient
|
|
2266
|
-
};
|
|
2267
|
-
const { profile } = await this.authHandler(result, ctx);
|
|
2268
|
-
const backstageSignInResult = await this.signInResolver({
|
|
2269
|
-
result,
|
|
2270
|
-
profile
|
|
2271
|
-
}, ctx);
|
|
2272
|
-
return {
|
|
2273
|
-
providerInfo: {
|
|
2274
|
-
accessToken: result.accessToken
|
|
2275
|
-
},
|
|
2276
|
-
backstageIdentity: prepareBackstageIdentityResponse(backstageSignInResult),
|
|
2277
|
-
profile
|
|
2278
|
-
};
|
|
2279
|
-
}
|
|
2280
|
-
getResult(req) {
|
|
2281
|
-
const authHeader = req.header(OAUTH2_PROXY_JWT_HEADER);
|
|
2282
|
-
const jwt = IdentityClient.getBearerToken(authHeader);
|
|
2283
|
-
if (!jwt) {
|
|
2284
|
-
throw new errors.AuthenticationError(`Missing or in incorrect format - Oauth2Proxy OIDC header: ${OAUTH2_PROXY_JWT_HEADER}`);
|
|
2285
|
-
}
|
|
2286
|
-
const decodedJWT = jose.JWT.decode(jwt);
|
|
2287
|
-
return {
|
|
2288
|
-
fullProfile: decodedJWT,
|
|
2289
|
-
accessToken: jwt
|
|
2290
|
-
};
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
|
-
const createOauth2ProxyProvider = (options) => ({ catalogApi, logger, tokenIssuer, tokenManager }) => {
|
|
2294
|
-
const signInResolver = options.signIn.resolver;
|
|
2295
|
-
const authHandler = options.authHandler;
|
|
2296
|
-
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2297
|
-
catalogApi,
|
|
2298
|
-
tokenManager
|
|
2299
|
-
});
|
|
2300
|
-
return new Oauth2ProxyAuthProvider({
|
|
2301
|
-
logger,
|
|
2302
|
-
signInResolver,
|
|
2303
|
-
authHandler,
|
|
2304
|
-
tokenIssuer,
|
|
2305
|
-
catalogIdentityClient
|
|
2306
|
-
});
|
|
2307
|
-
};
|
|
2308
|
-
|
|
2309
|
-
class OidcAuthProvider {
|
|
2310
|
-
constructor(options) {
|
|
2311
|
-
this.implementation = this.setupStrategy(options);
|
|
2312
|
-
this.scope = options.scope;
|
|
2313
|
-
this.prompt = options.prompt;
|
|
2314
|
-
this.signInResolver = options.signInResolver;
|
|
2315
|
-
this.authHandler = options.authHandler;
|
|
2316
|
-
this.tokenIssuer = options.tokenIssuer;
|
|
2317
|
-
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2318
|
-
this.logger = options.logger;
|
|
2319
|
-
}
|
|
2320
|
-
async start(req) {
|
|
2321
|
-
const { strategy } = await this.implementation;
|
|
2322
|
-
const options = {
|
|
2323
|
-
scope: req.scope || this.scope || "openid profile email",
|
|
2324
|
-
state: encodeState(req.state)
|
|
2325
|
-
};
|
|
2326
|
-
const prompt = this.prompt || "none";
|
|
2327
|
-
if (prompt !== "auto") {
|
|
2328
|
-
options.prompt = prompt;
|
|
2329
|
-
}
|
|
2330
|
-
return await executeRedirectStrategy(req, strategy, options);
|
|
2033
|
+
return await executeRedirectStrategy(req, strategy, options);
|
|
2331
2034
|
}
|
|
2332
2035
|
async handler(req) {
|
|
2333
2036
|
const { strategy } = await this.implementation;
|
|
@@ -2426,7 +2129,8 @@ const createOidcProvider = (options) => {
|
|
|
2426
2129
|
var _a, _b;
|
|
2427
2130
|
const clientId = envConfig.getString("clientId");
|
|
2428
2131
|
const clientSecret = envConfig.getString("clientSecret");
|
|
2429
|
-
const
|
|
2132
|
+
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
2133
|
+
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
2430
2134
|
const metadataUrl = envConfig.getString("metadataUrl");
|
|
2431
2135
|
const tokenSignedResponseAlg = envConfig.getOptionalString("tokenSignedResponseAlg");
|
|
2432
2136
|
const scope = envConfig.getOptionalString("scope");
|
|
@@ -2465,7 +2169,8 @@ const createOidcProvider = (options) => {
|
|
|
2465
2169
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
2466
2170
|
disableRefresh: false,
|
|
2467
2171
|
providerId,
|
|
2468
|
-
tokenIssuer
|
|
2172
|
+
tokenIssuer,
|
|
2173
|
+
callbackUrl
|
|
2469
2174
|
});
|
|
2470
2175
|
});
|
|
2471
2176
|
};
|
|
@@ -2595,7 +2300,8 @@ const createOktaProvider = (_options) => {
|
|
|
2595
2300
|
const clientId = envConfig.getString("clientId");
|
|
2596
2301
|
const clientSecret = envConfig.getString("clientSecret");
|
|
2597
2302
|
const audience = envConfig.getString("audience");
|
|
2598
|
-
const
|
|
2303
|
+
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
2304
|
+
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
2599
2305
|
if (!audience.startsWith("https://")) {
|
|
2600
2306
|
throw new Error("URL for 'audience' must start with 'https://'.");
|
|
2601
2307
|
}
|
|
@@ -2626,7 +2332,8 @@ const createOktaProvider = (_options) => {
|
|
|
2626
2332
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
2627
2333
|
disableRefresh: false,
|
|
2628
2334
|
providerId,
|
|
2629
|
-
tokenIssuer
|
|
2335
|
+
tokenIssuer,
|
|
2336
|
+
callbackUrl
|
|
2630
2337
|
});
|
|
2631
2338
|
});
|
|
2632
2339
|
};
|
|
@@ -2690,290 +2397,557 @@ class OneLoginProvider {
|
|
|
2690
2397
|
};
|
|
2691
2398
|
const { profile } = await this.authHandler(result, context);
|
|
2692
2399
|
const response = {
|
|
2693
|
-
providerInfo: {
|
|
2694
|
-
idToken: result.params.id_token,
|
|
2695
|
-
accessToken: result.accessToken,
|
|
2696
|
-
scope: result.params.scope,
|
|
2697
|
-
expiresInSeconds: result.params.expires_in
|
|
2698
|
-
},
|
|
2699
|
-
profile
|
|
2400
|
+
providerInfo: {
|
|
2401
|
+
idToken: result.params.id_token,
|
|
2402
|
+
accessToken: result.accessToken,
|
|
2403
|
+
scope: result.params.scope,
|
|
2404
|
+
expiresInSeconds: result.params.expires_in
|
|
2405
|
+
},
|
|
2406
|
+
profile
|
|
2407
|
+
};
|
|
2408
|
+
if (this.signInResolver) {
|
|
2409
|
+
response.backstageIdentity = await this.signInResolver({
|
|
2410
|
+
result,
|
|
2411
|
+
profile
|
|
2412
|
+
}, context);
|
|
2413
|
+
}
|
|
2414
|
+
return response;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
const defaultSignInResolver = async (info) => {
|
|
2418
|
+
const { profile } = info;
|
|
2419
|
+
if (!profile.email) {
|
|
2420
|
+
throw new Error("OIDC profile contained no email");
|
|
2421
|
+
}
|
|
2422
|
+
const id = profile.email.split("@")[0];
|
|
2423
|
+
return { id, token: "" };
|
|
2424
|
+
};
|
|
2425
|
+
const createOneLoginProvider = (options) => {
|
|
2426
|
+
return ({
|
|
2427
|
+
providerId,
|
|
2428
|
+
globalConfig,
|
|
2429
|
+
config,
|
|
2430
|
+
tokenIssuer,
|
|
2431
|
+
tokenManager,
|
|
2432
|
+
catalogApi,
|
|
2433
|
+
logger
|
|
2434
|
+
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
2435
|
+
var _a, _b;
|
|
2436
|
+
const clientId = envConfig.getString("clientId");
|
|
2437
|
+
const clientSecret = envConfig.getString("clientSecret");
|
|
2438
|
+
const issuer = envConfig.getString("issuer");
|
|
2439
|
+
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
2440
|
+
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
2441
|
+
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2442
|
+
catalogApi,
|
|
2443
|
+
tokenManager
|
|
2444
|
+
});
|
|
2445
|
+
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
|
|
2446
|
+
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
2447
|
+
});
|
|
2448
|
+
const signInResolver = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : defaultSignInResolver;
|
|
2449
|
+
const provider = new OneLoginProvider({
|
|
2450
|
+
clientId,
|
|
2451
|
+
clientSecret,
|
|
2452
|
+
callbackUrl,
|
|
2453
|
+
issuer,
|
|
2454
|
+
authHandler,
|
|
2455
|
+
signInResolver,
|
|
2456
|
+
tokenIssuer,
|
|
2457
|
+
catalogIdentityClient,
|
|
2458
|
+
logger
|
|
2459
|
+
});
|
|
2460
|
+
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
2461
|
+
disableRefresh: false,
|
|
2462
|
+
providerId,
|
|
2463
|
+
tokenIssuer,
|
|
2464
|
+
callbackUrl
|
|
2465
|
+
});
|
|
2466
|
+
});
|
|
2467
|
+
};
|
|
2468
|
+
|
|
2469
|
+
class SamlAuthProvider {
|
|
2470
|
+
constructor(options) {
|
|
2471
|
+
this.appUrl = options.appUrl;
|
|
2472
|
+
this.signInResolver = options.signInResolver;
|
|
2473
|
+
this.authHandler = options.authHandler;
|
|
2474
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
2475
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2476
|
+
this.logger = options.logger;
|
|
2477
|
+
this.strategy = new passportSaml.Strategy({ ...options }, (fullProfile, done) => {
|
|
2478
|
+
done(void 0, { fullProfile });
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
async start(req, res) {
|
|
2482
|
+
const { url } = await executeRedirectStrategy(req, this.strategy, {});
|
|
2483
|
+
res.redirect(url);
|
|
2484
|
+
}
|
|
2485
|
+
async frameHandler(req, res) {
|
|
2486
|
+
try {
|
|
2487
|
+
const context = {
|
|
2488
|
+
logger: this.logger,
|
|
2489
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
2490
|
+
tokenIssuer: this.tokenIssuer
|
|
2491
|
+
};
|
|
2492
|
+
const { result } = await executeFrameHandlerStrategy(req, this.strategy);
|
|
2493
|
+
const { profile } = await this.authHandler(result, context);
|
|
2494
|
+
const response = {
|
|
2495
|
+
profile,
|
|
2496
|
+
providerInfo: {}
|
|
2497
|
+
};
|
|
2498
|
+
if (this.signInResolver) {
|
|
2499
|
+
const signInResponse = await this.signInResolver({
|
|
2500
|
+
result,
|
|
2501
|
+
profile
|
|
2502
|
+
}, context);
|
|
2503
|
+
response.backstageIdentity = prepareBackstageIdentityResponse(signInResponse);
|
|
2504
|
+
}
|
|
2505
|
+
return postMessageResponse(res, this.appUrl, {
|
|
2506
|
+
type: "authorization_response",
|
|
2507
|
+
response
|
|
2508
|
+
});
|
|
2509
|
+
} catch (error) {
|
|
2510
|
+
const { name, message } = errors.isError(error) ? error : new Error("Encountered invalid error");
|
|
2511
|
+
return postMessageResponse(res, this.appUrl, {
|
|
2512
|
+
type: "authorization_response",
|
|
2513
|
+
error: { name, message }
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
async logout(_req, res) {
|
|
2518
|
+
res.end();
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
const samlDefaultSignInResolver = async (info, ctx) => {
|
|
2522
|
+
const id = info.result.fullProfile.nameID;
|
|
2523
|
+
const token = await ctx.tokenIssuer.issueToken({
|
|
2524
|
+
claims: { sub: id }
|
|
2525
|
+
});
|
|
2526
|
+
return { id, token };
|
|
2527
|
+
};
|
|
2528
|
+
const createSamlProvider = (options) => {
|
|
2529
|
+
return ({
|
|
2530
|
+
providerId,
|
|
2531
|
+
globalConfig,
|
|
2532
|
+
config,
|
|
2533
|
+
tokenIssuer,
|
|
2534
|
+
tokenManager,
|
|
2535
|
+
catalogApi,
|
|
2536
|
+
logger
|
|
2537
|
+
}) => {
|
|
2538
|
+
var _a, _b;
|
|
2539
|
+
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2540
|
+
catalogApi,
|
|
2541
|
+
tokenManager
|
|
2542
|
+
});
|
|
2543
|
+
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
|
|
2544
|
+
profile: {
|
|
2545
|
+
email: fullProfile.email,
|
|
2546
|
+
displayName: fullProfile.displayName
|
|
2547
|
+
}
|
|
2548
|
+
});
|
|
2549
|
+
const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : samlDefaultSignInResolver;
|
|
2550
|
+
const signInResolver = (info) => signInResolverFn(info, {
|
|
2551
|
+
catalogIdentityClient,
|
|
2552
|
+
tokenIssuer,
|
|
2553
|
+
logger
|
|
2554
|
+
});
|
|
2555
|
+
return new SamlAuthProvider({
|
|
2556
|
+
callbackUrl: `${globalConfig.baseUrl}/${providerId}/handler/frame`,
|
|
2557
|
+
entryPoint: config.getString("entryPoint"),
|
|
2558
|
+
logoutUrl: config.getOptionalString("logoutUrl"),
|
|
2559
|
+
audience: config.getOptionalString("audience"),
|
|
2560
|
+
issuer: config.getString("issuer"),
|
|
2561
|
+
cert: config.getString("cert"),
|
|
2562
|
+
privateKey: config.getOptionalString("privateKey"),
|
|
2563
|
+
authnContext: config.getOptionalStringArray("authnContext"),
|
|
2564
|
+
identifierFormat: config.getOptionalString("identifierFormat"),
|
|
2565
|
+
decryptionPvk: config.getOptionalString("decryptionPvk"),
|
|
2566
|
+
signatureAlgorithm: config.getOptionalString("signatureAlgorithm"),
|
|
2567
|
+
digestAlgorithm: config.getOptionalString("digestAlgorithm"),
|
|
2568
|
+
acceptedClockSkewMs: config.getOptionalNumber("acceptedClockSkewMs"),
|
|
2569
|
+
tokenIssuer,
|
|
2570
|
+
appUrl: globalConfig.appUrl,
|
|
2571
|
+
authHandler,
|
|
2572
|
+
signInResolver,
|
|
2573
|
+
logger,
|
|
2574
|
+
catalogIdentityClient
|
|
2575
|
+
});
|
|
2576
|
+
};
|
|
2577
|
+
};
|
|
2578
|
+
|
|
2579
|
+
const IAP_JWT_HEADER = "x-goog-iap-jwt-assertion";
|
|
2580
|
+
|
|
2581
|
+
function createTokenValidator(audience, mockClient) {
|
|
2582
|
+
const client = mockClient != null ? mockClient : new googleAuthLibrary.OAuth2Client();
|
|
2583
|
+
return async function tokenValidator(token) {
|
|
2584
|
+
const response = await client.getIapPublicKeys();
|
|
2585
|
+
const ticket = await client.verifySignedJwtWithCertsAsync(token, response.pubkeys, audience, ["https://cloud.google.com/iap"]);
|
|
2586
|
+
const payload = ticket.getPayload();
|
|
2587
|
+
if (!payload) {
|
|
2588
|
+
throw new TypeError("Token had no payload");
|
|
2589
|
+
}
|
|
2590
|
+
return payload;
|
|
2591
|
+
};
|
|
2592
|
+
}
|
|
2593
|
+
async function parseRequestToken(jwtToken, tokenValidator) {
|
|
2594
|
+
if (typeof jwtToken !== "string" || !jwtToken) {
|
|
2595
|
+
throw new errors.AuthenticationError(`Missing Google IAP header: ${IAP_JWT_HEADER}`);
|
|
2596
|
+
}
|
|
2597
|
+
let payload;
|
|
2598
|
+
try {
|
|
2599
|
+
payload = await tokenValidator(jwtToken);
|
|
2600
|
+
} catch (e) {
|
|
2601
|
+
throw new errors.AuthenticationError(`Google IAP token verification failed, ${e}`);
|
|
2602
|
+
}
|
|
2603
|
+
if (!payload.sub || !payload.email) {
|
|
2604
|
+
throw new errors.AuthenticationError("Google IAP token payload is missing sub and/or email claim");
|
|
2605
|
+
}
|
|
2606
|
+
return {
|
|
2607
|
+
iapToken: {
|
|
2608
|
+
...payload,
|
|
2609
|
+
sub: payload.sub,
|
|
2610
|
+
email: payload.email
|
|
2611
|
+
}
|
|
2612
|
+
};
|
|
2613
|
+
}
|
|
2614
|
+
const defaultAuthHandler = async ({
|
|
2615
|
+
iapToken
|
|
2616
|
+
}) => ({ profile: { email: iapToken.email } });
|
|
2617
|
+
|
|
2618
|
+
class GcpIapProvider {
|
|
2619
|
+
constructor(options) {
|
|
2620
|
+
this.authHandler = options.authHandler;
|
|
2621
|
+
this.signInResolver = options.signInResolver;
|
|
2622
|
+
this.tokenValidator = options.tokenValidator;
|
|
2623
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
2624
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2625
|
+
this.logger = options.logger;
|
|
2626
|
+
}
|
|
2627
|
+
async start() {
|
|
2628
|
+
}
|
|
2629
|
+
async frameHandler() {
|
|
2630
|
+
}
|
|
2631
|
+
async refresh(req, res) {
|
|
2632
|
+
const result = await parseRequestToken(req.header(IAP_JWT_HEADER), this.tokenValidator);
|
|
2633
|
+
const context = {
|
|
2634
|
+
logger: this.logger,
|
|
2635
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
2636
|
+
tokenIssuer: this.tokenIssuer
|
|
2637
|
+
};
|
|
2638
|
+
const { profile } = await this.authHandler(result, context);
|
|
2639
|
+
const backstageIdentity = await this.signInResolver({ profile, result }, context);
|
|
2640
|
+
const response = {
|
|
2641
|
+
providerInfo: { iapToken: result.iapToken },
|
|
2642
|
+
profile,
|
|
2643
|
+
backstageIdentity: prepareBackstageIdentityResponse(backstageIdentity)
|
|
2700
2644
|
};
|
|
2701
|
-
|
|
2702
|
-
response.backstageIdentity = await this.signInResolver({
|
|
2703
|
-
result,
|
|
2704
|
-
profile
|
|
2705
|
-
}, context);
|
|
2706
|
-
}
|
|
2707
|
-
return response;
|
|
2645
|
+
res.json(response);
|
|
2708
2646
|
}
|
|
2709
2647
|
}
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
};
|
|
2718
|
-
const createOneLoginProvider = (options) => {
|
|
2719
|
-
return ({
|
|
2720
|
-
providerId,
|
|
2721
|
-
globalConfig,
|
|
2722
|
-
config,
|
|
2723
|
-
tokenIssuer,
|
|
2724
|
-
tokenManager,
|
|
2725
|
-
catalogApi,
|
|
2726
|
-
logger
|
|
2727
|
-
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
2728
|
-
var _a, _b;
|
|
2729
|
-
const clientId = envConfig.getString("clientId");
|
|
2730
|
-
const clientSecret = envConfig.getString("clientSecret");
|
|
2731
|
-
const issuer = envConfig.getString("issuer");
|
|
2732
|
-
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
2648
|
+
function createGcpIapProvider(options) {
|
|
2649
|
+
return ({ config, tokenIssuer, catalogApi, logger, tokenManager }) => {
|
|
2650
|
+
var _a;
|
|
2651
|
+
const audience = config.getString("audience");
|
|
2652
|
+
const authHandler = (_a = options.authHandler) != null ? _a : defaultAuthHandler;
|
|
2653
|
+
const signInResolver = options.signIn.resolver;
|
|
2654
|
+
const tokenValidator = createTokenValidator(audience);
|
|
2733
2655
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2734
2656
|
catalogApi,
|
|
2735
2657
|
tokenManager
|
|
2736
2658
|
});
|
|
2737
|
-
|
|
2738
|
-
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
2739
|
-
});
|
|
2740
|
-
const signInResolver = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : defaultSignInResolver;
|
|
2741
|
-
const provider = new OneLoginProvider({
|
|
2742
|
-
clientId,
|
|
2743
|
-
clientSecret,
|
|
2744
|
-
callbackUrl,
|
|
2745
|
-
issuer,
|
|
2659
|
+
return new GcpIapProvider({
|
|
2746
2660
|
authHandler,
|
|
2747
2661
|
signInResolver,
|
|
2662
|
+
tokenValidator,
|
|
2748
2663
|
tokenIssuer,
|
|
2749
2664
|
catalogIdentityClient,
|
|
2750
2665
|
logger
|
|
2751
2666
|
});
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
const factories = {
|
|
2671
|
+
google: createGoogleProvider(),
|
|
2672
|
+
github: createGithubProvider(),
|
|
2673
|
+
gitlab: createGitlabProvider(),
|
|
2674
|
+
saml: createSamlProvider(),
|
|
2675
|
+
okta: createOktaProvider(),
|
|
2676
|
+
auth0: createAuth0Provider(),
|
|
2677
|
+
microsoft: createMicrosoftProvider(),
|
|
2678
|
+
oauth2: createOAuth2Provider(),
|
|
2679
|
+
oidc: createOidcProvider(),
|
|
2680
|
+
onelogin: createOneLoginProvider(),
|
|
2681
|
+
awsalb: createAwsAlbProvider(),
|
|
2682
|
+
bitbucket: createBitbucketProvider(),
|
|
2683
|
+
atlassian: createAtlassianProvider()
|
|
2758
2684
|
};
|
|
2759
2685
|
|
|
2760
|
-
|
|
2686
|
+
function createOidcRouter(options) {
|
|
2687
|
+
const { baseUrl, tokenIssuer } = options;
|
|
2688
|
+
const router = Router__default["default"]();
|
|
2689
|
+
const config = {
|
|
2690
|
+
issuer: baseUrl,
|
|
2691
|
+
token_endpoint: `${baseUrl}/v1/token`,
|
|
2692
|
+
userinfo_endpoint: `${baseUrl}/v1/userinfo`,
|
|
2693
|
+
jwks_uri: `${baseUrl}/.well-known/jwks.json`,
|
|
2694
|
+
response_types_supported: ["id_token"],
|
|
2695
|
+
subject_types_supported: ["public"],
|
|
2696
|
+
id_token_signing_alg_values_supported: ["RS256"],
|
|
2697
|
+
scopes_supported: ["openid"],
|
|
2698
|
+
token_endpoint_auth_methods_supported: [],
|
|
2699
|
+
claims_supported: ["sub"],
|
|
2700
|
+
grant_types_supported: []
|
|
2701
|
+
};
|
|
2702
|
+
router.get("/.well-known/openid-configuration", (_req, res) => {
|
|
2703
|
+
res.json(config);
|
|
2704
|
+
});
|
|
2705
|
+
router.get("/.well-known/jwks.json", async (_req, res) => {
|
|
2706
|
+
const { keys } = await tokenIssuer.listPublicKeys();
|
|
2707
|
+
res.json({ keys });
|
|
2708
|
+
});
|
|
2709
|
+
router.get("/v1/token", (_req, res) => {
|
|
2710
|
+
res.status(501).send("Not Implemented");
|
|
2711
|
+
});
|
|
2712
|
+
router.get("/v1/userinfo", (_req, res) => {
|
|
2713
|
+
res.status(501).send("Not Implemented");
|
|
2714
|
+
});
|
|
2715
|
+
return router;
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
const MS_IN_S = 1e3;
|
|
2719
|
+
class TokenFactory {
|
|
2761
2720
|
constructor(options) {
|
|
2762
|
-
this.
|
|
2763
|
-
this.signInResolver = options.signInResolver;
|
|
2764
|
-
this.authHandler = options.authHandler;
|
|
2765
|
-
this.tokenIssuer = options.tokenIssuer;
|
|
2766
|
-
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2721
|
+
this.issuer = options.issuer;
|
|
2767
2722
|
this.logger = options.logger;
|
|
2768
|
-
this.
|
|
2769
|
-
|
|
2723
|
+
this.keyStore = options.keyStore;
|
|
2724
|
+
this.keyDurationSeconds = options.keyDurationSeconds;
|
|
2725
|
+
}
|
|
2726
|
+
async issueToken(params) {
|
|
2727
|
+
const key = await this.getKey();
|
|
2728
|
+
const iss = this.issuer;
|
|
2729
|
+
const sub = params.claims.sub;
|
|
2730
|
+
const ent = params.claims.ent;
|
|
2731
|
+
const aud = "backstage";
|
|
2732
|
+
const iat = Math.floor(Date.now() / MS_IN_S);
|
|
2733
|
+
const exp = iat + this.keyDurationSeconds;
|
|
2734
|
+
this.logger.info(`Issuing token for ${sub}, with entities ${ent != null ? ent : []}`);
|
|
2735
|
+
return jose.JWS.sign({ iss, sub, aud, iat, exp, ent }, key, {
|
|
2736
|
+
alg: key.alg,
|
|
2737
|
+
kid: key.kid
|
|
2770
2738
|
});
|
|
2771
2739
|
}
|
|
2772
|
-
async
|
|
2773
|
-
const {
|
|
2774
|
-
|
|
2740
|
+
async listPublicKeys() {
|
|
2741
|
+
const { items: keys } = await this.keyStore.listKeys();
|
|
2742
|
+
const validKeys = [];
|
|
2743
|
+
const expiredKeys = [];
|
|
2744
|
+
for (const key of keys) {
|
|
2745
|
+
const expireAt = luxon.DateTime.fromJSDate(key.createdAt).plus({
|
|
2746
|
+
seconds: 3 * this.keyDurationSeconds
|
|
2747
|
+
});
|
|
2748
|
+
if (expireAt < luxon.DateTime.local()) {
|
|
2749
|
+
expiredKeys.push(key);
|
|
2750
|
+
} else {
|
|
2751
|
+
validKeys.push(key);
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
if (expiredKeys.length > 0) {
|
|
2755
|
+
const kids = expiredKeys.map(({ key }) => key.kid);
|
|
2756
|
+
this.logger.info(`Removing expired signing keys, '${kids.join("', '")}'`);
|
|
2757
|
+
this.keyStore.removeKeys(kids).catch((error) => {
|
|
2758
|
+
this.logger.error(`Failed to remove expired keys, ${error}`);
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
return { keys: validKeys.map(({ key }) => key) };
|
|
2775
2762
|
}
|
|
2776
|
-
async
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
catalogIdentityClient: this.catalogIdentityClient,
|
|
2781
|
-
tokenIssuer: this.tokenIssuer
|
|
2782
|
-
};
|
|
2783
|
-
const { result } = await executeFrameHandlerStrategy(req, this.strategy);
|
|
2784
|
-
const { profile } = await this.authHandler(result, context);
|
|
2785
|
-
const response = {
|
|
2786
|
-
profile,
|
|
2787
|
-
providerInfo: {}
|
|
2788
|
-
};
|
|
2789
|
-
if (this.signInResolver) {
|
|
2790
|
-
const signInResponse = await this.signInResolver({
|
|
2791
|
-
result,
|
|
2792
|
-
profile
|
|
2793
|
-
}, context);
|
|
2794
|
-
response.backstageIdentity = prepareBackstageIdentityResponse(signInResponse);
|
|
2763
|
+
async getKey() {
|
|
2764
|
+
if (this.privateKeyPromise) {
|
|
2765
|
+
if (this.keyExpiry && luxon.DateTime.fromJSDate(this.keyExpiry) > luxon.DateTime.local()) {
|
|
2766
|
+
return this.privateKeyPromise;
|
|
2795
2767
|
}
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2768
|
+
this.logger.info(`Signing key has expired, generating new key`);
|
|
2769
|
+
delete this.privateKeyPromise;
|
|
2770
|
+
}
|
|
2771
|
+
this.keyExpiry = luxon.DateTime.utc().plus({
|
|
2772
|
+
seconds: this.keyDurationSeconds
|
|
2773
|
+
}).toJSDate();
|
|
2774
|
+
const promise = (async () => {
|
|
2775
|
+
const key = await jose.JWK.generate("EC", "P-256", {
|
|
2776
|
+
use: "sig",
|
|
2777
|
+
kid: uuid.v4(),
|
|
2778
|
+
alg: "ES256"
|
|
2799
2779
|
});
|
|
2780
|
+
this.logger.info(`Created new signing key ${key.kid}`);
|
|
2781
|
+
await this.keyStore.addKey(key.toJWK(false));
|
|
2782
|
+
return key;
|
|
2783
|
+
})();
|
|
2784
|
+
this.privateKeyPromise = promise;
|
|
2785
|
+
try {
|
|
2786
|
+
await promise;
|
|
2800
2787
|
} catch (error) {
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
error: { name, message }
|
|
2805
|
-
});
|
|
2788
|
+
this.logger.error(`Failed to generate new signing key, ${error}`);
|
|
2789
|
+
delete this.keyExpiry;
|
|
2790
|
+
delete this.privateKeyPromise;
|
|
2806
2791
|
}
|
|
2792
|
+
return promise;
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
const migrationsDir = backendCommon.resolvePackagePath("@backstage/plugin-auth-backend", "migrations");
|
|
2797
|
+
const TABLE = "signing_keys";
|
|
2798
|
+
const parseDate = (date) => {
|
|
2799
|
+
const parsedDate = typeof date === "string" ? luxon.DateTime.fromSQL(date, { zone: "UTC" }) : luxon.DateTime.fromJSDate(date);
|
|
2800
|
+
if (!parsedDate.isValid) {
|
|
2801
|
+
throw new Error(`Failed to parse date, reason: ${parsedDate.invalidReason}, explanation: ${parsedDate.invalidExplanation}`);
|
|
2802
|
+
}
|
|
2803
|
+
return parsedDate.toJSDate();
|
|
2804
|
+
};
|
|
2805
|
+
class DatabaseKeyStore {
|
|
2806
|
+
static async create(options) {
|
|
2807
|
+
const { database } = options;
|
|
2808
|
+
await database.migrate.latest({
|
|
2809
|
+
directory: migrationsDir
|
|
2810
|
+
});
|
|
2811
|
+
return new DatabaseKeyStore(options);
|
|
2812
|
+
}
|
|
2813
|
+
constructor(options) {
|
|
2814
|
+
this.database = options.database;
|
|
2815
|
+
}
|
|
2816
|
+
async addKey(key) {
|
|
2817
|
+
await this.database(TABLE).insert({
|
|
2818
|
+
kid: key.kid,
|
|
2819
|
+
key: JSON.stringify(key)
|
|
2820
|
+
});
|
|
2821
|
+
}
|
|
2822
|
+
async listKeys() {
|
|
2823
|
+
const rows = await this.database(TABLE).select();
|
|
2824
|
+
return {
|
|
2825
|
+
items: rows.map((row) => ({
|
|
2826
|
+
key: JSON.parse(row.key),
|
|
2827
|
+
createdAt: parseDate(row.created_at)
|
|
2828
|
+
}))
|
|
2829
|
+
};
|
|
2807
2830
|
}
|
|
2808
|
-
async
|
|
2809
|
-
|
|
2831
|
+
async removeKeys(kids) {
|
|
2832
|
+
await this.database(TABLE).delete().whereIn("kid", kids);
|
|
2810
2833
|
}
|
|
2811
2834
|
}
|
|
2812
|
-
const samlDefaultSignInResolver = async (info, ctx) => {
|
|
2813
|
-
const id = info.result.fullProfile.nameID;
|
|
2814
|
-
const token = await ctx.tokenIssuer.issueToken({
|
|
2815
|
-
claims: { sub: id }
|
|
2816
|
-
});
|
|
2817
|
-
return { id, token };
|
|
2818
|
-
};
|
|
2819
|
-
const createSamlProvider = (options) => {
|
|
2820
|
-
return ({
|
|
2821
|
-
providerId,
|
|
2822
|
-
globalConfig,
|
|
2823
|
-
config,
|
|
2824
|
-
tokenIssuer,
|
|
2825
|
-
tokenManager,
|
|
2826
|
-
catalogApi,
|
|
2827
|
-
logger
|
|
2828
|
-
}) => {
|
|
2829
|
-
var _a, _b;
|
|
2830
|
-
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2831
|
-
catalogApi,
|
|
2832
|
-
tokenManager
|
|
2833
|
-
});
|
|
2834
|
-
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
|
|
2835
|
-
profile: {
|
|
2836
|
-
email: fullProfile.email,
|
|
2837
|
-
displayName: fullProfile.displayName
|
|
2838
|
-
}
|
|
2839
|
-
});
|
|
2840
|
-
const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : samlDefaultSignInResolver;
|
|
2841
|
-
const signInResolver = (info) => signInResolverFn(info, {
|
|
2842
|
-
catalogIdentityClient,
|
|
2843
|
-
tokenIssuer,
|
|
2844
|
-
logger
|
|
2845
|
-
});
|
|
2846
|
-
return new SamlAuthProvider({
|
|
2847
|
-
callbackUrl: `${globalConfig.baseUrl}/${providerId}/handler/frame`,
|
|
2848
|
-
entryPoint: config.getString("entryPoint"),
|
|
2849
|
-
logoutUrl: config.getOptionalString("logoutUrl"),
|
|
2850
|
-
audience: config.getOptionalString("audience"),
|
|
2851
|
-
issuer: config.getString("issuer"),
|
|
2852
|
-
cert: config.getString("cert"),
|
|
2853
|
-
privateKey: config.getOptionalString("privateKey"),
|
|
2854
|
-
authnContext: config.getOptionalStringArray("authnContext"),
|
|
2855
|
-
identifierFormat: config.getOptionalString("identifierFormat"),
|
|
2856
|
-
decryptionPvk: config.getOptionalString("decryptionPvk"),
|
|
2857
|
-
signatureAlgorithm: config.getOptionalString("signatureAlgorithm"),
|
|
2858
|
-
digestAlgorithm: config.getOptionalString("digestAlgorithm"),
|
|
2859
|
-
acceptedClockSkewMs: config.getOptionalNumber("acceptedClockSkewMs"),
|
|
2860
|
-
tokenIssuer,
|
|
2861
|
-
appUrl: globalConfig.appUrl,
|
|
2862
|
-
authHandler,
|
|
2863
|
-
signInResolver,
|
|
2864
|
-
logger,
|
|
2865
|
-
catalogIdentityClient
|
|
2866
|
-
});
|
|
2867
|
-
};
|
|
2868
|
-
};
|
|
2869
|
-
|
|
2870
|
-
const IAP_JWT_HEADER = "x-goog-iap-jwt-assertion";
|
|
2871
2835
|
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
const response = await client.getIapPublicKeys();
|
|
2876
|
-
const ticket = await client.verifySignedJwtWithCertsAsync(token, response.pubkeys, audience, ["https://cloud.google.com/iap"]);
|
|
2877
|
-
const payload = ticket.getPayload();
|
|
2878
|
-
if (!payload) {
|
|
2879
|
-
throw new TypeError("Token had no payload");
|
|
2880
|
-
}
|
|
2881
|
-
return payload;
|
|
2882
|
-
};
|
|
2883
|
-
}
|
|
2884
|
-
async function parseRequestToken(jwtToken, tokenValidator) {
|
|
2885
|
-
if (typeof jwtToken !== "string" || !jwtToken) {
|
|
2886
|
-
throw new errors.AuthenticationError(`Missing Google IAP header: ${IAP_JWT_HEADER}`);
|
|
2887
|
-
}
|
|
2888
|
-
let payload;
|
|
2889
|
-
try {
|
|
2890
|
-
payload = await tokenValidator(jwtToken);
|
|
2891
|
-
} catch (e) {
|
|
2892
|
-
throw new errors.AuthenticationError(`Google IAP token verification failed, ${e}`);
|
|
2836
|
+
class MemoryKeyStore {
|
|
2837
|
+
constructor() {
|
|
2838
|
+
this.keys = /* @__PURE__ */ new Map();
|
|
2893
2839
|
}
|
|
2894
|
-
|
|
2895
|
-
|
|
2840
|
+
async addKey(key) {
|
|
2841
|
+
this.keys.set(key.kid, {
|
|
2842
|
+
createdAt: luxon.DateTime.utc().toJSDate(),
|
|
2843
|
+
key: JSON.stringify(key)
|
|
2844
|
+
});
|
|
2896
2845
|
}
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
sub: payload.sub,
|
|
2901
|
-
email: payload.email
|
|
2846
|
+
async removeKeys(kids) {
|
|
2847
|
+
for (const kid of kids) {
|
|
2848
|
+
this.keys.delete(kid);
|
|
2902
2849
|
}
|
|
2903
|
-
}
|
|
2850
|
+
}
|
|
2851
|
+
async listKeys() {
|
|
2852
|
+
return {
|
|
2853
|
+
items: Array.from(this.keys).map(([, { createdAt, key: keyStr }]) => ({
|
|
2854
|
+
createdAt,
|
|
2855
|
+
key: JSON.parse(keyStr)
|
|
2856
|
+
}))
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2904
2859
|
}
|
|
2905
|
-
const defaultAuthHandler = async ({
|
|
2906
|
-
iapToken
|
|
2907
|
-
}) => ({ profile: { email: iapToken.email } });
|
|
2908
2860
|
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
this.
|
|
2914
|
-
this.
|
|
2915
|
-
this.
|
|
2916
|
-
this.logger = options.logger;
|
|
2861
|
+
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
2862
|
+
const DEFAULT_DOCUMENT_PATH = "sessions";
|
|
2863
|
+
class FirestoreKeyStore {
|
|
2864
|
+
constructor(database, path, timeout) {
|
|
2865
|
+
this.database = database;
|
|
2866
|
+
this.path = path;
|
|
2867
|
+
this.timeout = timeout;
|
|
2917
2868
|
}
|
|
2918
|
-
async
|
|
2869
|
+
static async create(settings) {
|
|
2870
|
+
const { path, timeout, ...firestoreSettings } = settings != null ? settings : {};
|
|
2871
|
+
const database = new firestore.Firestore(firestoreSettings);
|
|
2872
|
+
return new FirestoreKeyStore(database, path != null ? path : DEFAULT_DOCUMENT_PATH, timeout != null ? timeout : DEFAULT_TIMEOUT_MS);
|
|
2919
2873
|
}
|
|
2920
|
-
async
|
|
2874
|
+
static async verifyConnection(keyStore, logger) {
|
|
2875
|
+
try {
|
|
2876
|
+
await keyStore.verify();
|
|
2877
|
+
} catch (error) {
|
|
2878
|
+
if (process.env.NODE_ENV !== "development") {
|
|
2879
|
+
throw new Error(`Failed to connect to database: ${error.message}`);
|
|
2880
|
+
}
|
|
2881
|
+
logger == null ? void 0 : logger.warn(`Failed to connect to database: ${error.message}`);
|
|
2882
|
+
}
|
|
2921
2883
|
}
|
|
2922
|
-
async
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
const
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2884
|
+
async addKey(key) {
|
|
2885
|
+
await this.withTimeout(this.database.collection(this.path).doc(key.kid).set({
|
|
2886
|
+
kid: key.kid,
|
|
2887
|
+
key: JSON.stringify(key)
|
|
2888
|
+
}));
|
|
2889
|
+
}
|
|
2890
|
+
async listKeys() {
|
|
2891
|
+
const keys = await this.withTimeout(this.database.collection(this.path).get());
|
|
2892
|
+
return {
|
|
2893
|
+
items: keys.docs.map((key) => ({
|
|
2894
|
+
key: key.data(),
|
|
2895
|
+
createdAt: key.createTime.toDate()
|
|
2896
|
+
}))
|
|
2935
2897
|
};
|
|
2936
|
-
|
|
2898
|
+
}
|
|
2899
|
+
async removeKeys(kids) {
|
|
2900
|
+
for (const kid of kids) {
|
|
2901
|
+
await this.withTimeout(this.database.collection(this.path).doc(kid).delete());
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
async withTimeout(operation) {
|
|
2905
|
+
const timer = new Promise((_, reject) => setTimeout(() => {
|
|
2906
|
+
reject(new Error(`Operation timed out after ${this.timeout}ms`));
|
|
2907
|
+
}, this.timeout));
|
|
2908
|
+
return Promise.race([operation, timer]);
|
|
2909
|
+
}
|
|
2910
|
+
async verify() {
|
|
2911
|
+
await this.withTimeout(this.database.collection(this.path).limit(1).get());
|
|
2937
2912
|
}
|
|
2938
2913
|
}
|
|
2939
|
-
|
|
2940
|
-
|
|
2914
|
+
|
|
2915
|
+
class KeyStores {
|
|
2916
|
+
static async fromConfig(config, options) {
|
|
2941
2917
|
var _a;
|
|
2942
|
-
const
|
|
2943
|
-
const
|
|
2944
|
-
const
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2918
|
+
const { logger, database } = options != null ? options : {};
|
|
2919
|
+
const ks = config.getOptionalConfig("auth.keyStore");
|
|
2920
|
+
const provider = (_a = ks == null ? void 0 : ks.getOptionalString("provider")) != null ? _a : "database";
|
|
2921
|
+
logger == null ? void 0 : logger.info(`Configuring "${provider}" as KeyStore provider`);
|
|
2922
|
+
if (provider === "database") {
|
|
2923
|
+
if (!database) {
|
|
2924
|
+
throw new Error("This KeyStore provider requires a database");
|
|
2925
|
+
}
|
|
2926
|
+
return await DatabaseKeyStore.create({
|
|
2927
|
+
database: await database.getClient()
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
if (provider === "memory") {
|
|
2931
|
+
return new MemoryKeyStore();
|
|
2932
|
+
}
|
|
2933
|
+
if (provider === "firestore") {
|
|
2934
|
+
const settings = ks == null ? void 0 : ks.getConfig(provider);
|
|
2935
|
+
const keyStore = await FirestoreKeyStore.create(lodash.pickBy({
|
|
2936
|
+
projectId: settings == null ? void 0 : settings.getOptionalString("projectId"),
|
|
2937
|
+
keyFilename: settings == null ? void 0 : settings.getOptionalString("keyFilename"),
|
|
2938
|
+
host: settings == null ? void 0 : settings.getOptionalString("host"),
|
|
2939
|
+
port: settings == null ? void 0 : settings.getOptionalNumber("port"),
|
|
2940
|
+
ssl: settings == null ? void 0 : settings.getOptionalBoolean("ssl"),
|
|
2941
|
+
path: settings == null ? void 0 : settings.getOptionalString("path"),
|
|
2942
|
+
timeout: settings == null ? void 0 : settings.getOptionalNumber("timeout")
|
|
2943
|
+
}, (value) => value !== void 0));
|
|
2944
|
+
await FirestoreKeyStore.verifyConnection(keyStore, logger);
|
|
2945
|
+
return keyStore;
|
|
2946
|
+
}
|
|
2947
|
+
throw new Error(`Unknown KeyStore provider: ${provider}`);
|
|
2948
|
+
}
|
|
2959
2949
|
}
|
|
2960
2950
|
|
|
2961
|
-
const factories = {
|
|
2962
|
-
google: createGoogleProvider(),
|
|
2963
|
-
github: createGithubProvider(),
|
|
2964
|
-
gitlab: createGitlabProvider(),
|
|
2965
|
-
saml: createSamlProvider(),
|
|
2966
|
-
okta: createOktaProvider(),
|
|
2967
|
-
auth0: createAuth0Provider(),
|
|
2968
|
-
microsoft: createMicrosoftProvider(),
|
|
2969
|
-
oauth2: createOAuth2Provider(),
|
|
2970
|
-
oidc: createOidcProvider(),
|
|
2971
|
-
onelogin: createOneLoginProvider(),
|
|
2972
|
-
awsalb: createAwsAlbProvider(),
|
|
2973
|
-
bitbucket: createBitbucketProvider(),
|
|
2974
|
-
atlassian: createAtlassianProvider()
|
|
2975
|
-
};
|
|
2976
|
-
|
|
2977
2951
|
async function createRouter(options) {
|
|
2978
2952
|
const {
|
|
2979
2953
|
logger,
|
|
@@ -3025,7 +2999,11 @@ async function createRouter(options) {
|
|
|
3025
2999
|
try {
|
|
3026
3000
|
const provider = providerFactory({
|
|
3027
3001
|
providerId,
|
|
3028
|
-
globalConfig: {
|
|
3002
|
+
globalConfig: {
|
|
3003
|
+
baseUrl: authUrl,
|
|
3004
|
+
appUrl,
|
|
3005
|
+
isOriginAllowed
|
|
3006
|
+
},
|
|
3029
3007
|
config: providersConfig.getConfig(providerId),
|
|
3030
3008
|
logger,
|
|
3031
3009
|
tokenManager,
|
|
@@ -3085,7 +3063,6 @@ function createOriginFilter(config) {
|
|
|
3085
3063
|
}
|
|
3086
3064
|
|
|
3087
3065
|
exports.CatalogIdentityClient = CatalogIdentityClient;
|
|
3088
|
-
exports.IdentityClient = IdentityClient;
|
|
3089
3066
|
exports.OAuthAdapter = OAuthAdapter;
|
|
3090
3067
|
exports.OAuthEnvironmentHandler = OAuthEnvironmentHandler;
|
|
3091
3068
|
exports.bitbucketUserIdSignInResolver = bitbucketUserIdSignInResolver;
|