@backstage/plugin-auth-backend 0.0.0-nightly-20220923026 → 0.0.0-nightly-202202022734
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 +54 -0
- package/dist/index.cjs.js +937 -842
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +65 -31
- package/package.json +10 -10
package/dist/index.cjs.js
CHANGED
|
@@ -20,17 +20,17 @@ 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 uuid = require('uuid');
|
|
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
28
|
var openidClient = require('openid-client');
|
|
24
29
|
var passportOktaOauth = require('passport-okta-oauth');
|
|
25
30
|
var passportOneloginOauth = require('passport-onelogin-oauth');
|
|
26
31
|
var passportSaml = require('passport-saml');
|
|
27
32
|
var googleAuthLibrary = require('google-auth-library');
|
|
28
33
|
var catalogClient = require('@backstage/catalog-client');
|
|
29
|
-
var uuid = require('uuid');
|
|
30
|
-
var luxon = require('luxon');
|
|
31
|
-
var backendCommon = require('@backstage/backend-common');
|
|
32
|
-
var firestore = require('@google-cloud/firestore');
|
|
33
|
-
var lodash = require('lodash');
|
|
34
34
|
var session = require('express-session');
|
|
35
35
|
var passport = require('passport');
|
|
36
36
|
var minimatch = require('minimatch');
|
|
@@ -655,7 +655,12 @@ class AtlassianAuthProvider {
|
|
|
655
655
|
};
|
|
656
656
|
}
|
|
657
657
|
async handleResult(result) {
|
|
658
|
-
const
|
|
658
|
+
const context = {
|
|
659
|
+
logger: this.logger,
|
|
660
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
661
|
+
tokenIssuer: this.tokenIssuer
|
|
662
|
+
};
|
|
663
|
+
const { profile } = await this.authHandler(result, context);
|
|
659
664
|
const response = {
|
|
660
665
|
providerInfo: {
|
|
661
666
|
idToken: result.params.id_token,
|
|
@@ -669,11 +674,7 @@ class AtlassianAuthProvider {
|
|
|
669
674
|
response.backstageIdentity = await this.signInResolver({
|
|
670
675
|
result,
|
|
671
676
|
profile
|
|
672
|
-
},
|
|
673
|
-
tokenIssuer: this.tokenIssuer,
|
|
674
|
-
catalogIdentityClient: this.catalogIdentityClient,
|
|
675
|
-
logger: this.logger
|
|
676
|
-
});
|
|
677
|
+
}, context);
|
|
677
678
|
}
|
|
678
679
|
return response;
|
|
679
680
|
}
|
|
@@ -793,7 +794,12 @@ class Auth0AuthProvider {
|
|
|
793
794
|
};
|
|
794
795
|
}
|
|
795
796
|
async handleResult(result) {
|
|
796
|
-
const
|
|
797
|
+
const context = {
|
|
798
|
+
logger: this.logger,
|
|
799
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
800
|
+
tokenIssuer: this.tokenIssuer
|
|
801
|
+
};
|
|
802
|
+
const { profile } = await this.authHandler(result, context);
|
|
797
803
|
const response = {
|
|
798
804
|
providerInfo: {
|
|
799
805
|
idToken: result.params.id_token,
|
|
@@ -807,11 +813,7 @@ class Auth0AuthProvider {
|
|
|
807
813
|
response.backstageIdentity = await this.signInResolver({
|
|
808
814
|
result,
|
|
809
815
|
profile
|
|
810
|
-
},
|
|
811
|
-
tokenIssuer: this.tokenIssuer,
|
|
812
|
-
catalogIdentityClient: this.catalogIdentityClient,
|
|
813
|
-
logger: this.logger
|
|
814
|
-
});
|
|
816
|
+
}, context);
|
|
815
817
|
}
|
|
816
818
|
return response;
|
|
817
819
|
}
|
|
@@ -937,15 +939,16 @@ class AwsAlbAuthProvider {
|
|
|
937
939
|
}
|
|
938
940
|
}
|
|
939
941
|
async handleResult(result) {
|
|
940
|
-
const
|
|
941
|
-
const backstageIdentity = await this.signInResolver({
|
|
942
|
-
result,
|
|
943
|
-
profile
|
|
944
|
-
}, {
|
|
942
|
+
const context = {
|
|
945
943
|
tokenIssuer: this.tokenIssuer,
|
|
946
944
|
catalogIdentityClient: this.catalogIdentityClient,
|
|
947
945
|
logger: this.logger
|
|
948
|
-
}
|
|
946
|
+
};
|
|
947
|
+
const { profile } = await this.authHandler(result, context);
|
|
948
|
+
const backstageIdentity = await this.signInResolver({
|
|
949
|
+
result,
|
|
950
|
+
profile
|
|
951
|
+
}, context);
|
|
949
952
|
return {
|
|
950
953
|
providerInfo: {
|
|
951
954
|
accessToken: result.accessToken,
|
|
@@ -1045,7 +1048,12 @@ class BitbucketAuthProvider {
|
|
|
1045
1048
|
}
|
|
1046
1049
|
async handleResult(result) {
|
|
1047
1050
|
result.fullProfile.avatarUrl = result.fullProfile._json.links.avatar.href;
|
|
1048
|
-
const
|
|
1051
|
+
const context = {
|
|
1052
|
+
logger: this.logger,
|
|
1053
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
1054
|
+
tokenIssuer: this.tokenIssuer
|
|
1055
|
+
};
|
|
1056
|
+
const { profile } = await this.authHandler(result, context);
|
|
1049
1057
|
const response = {
|
|
1050
1058
|
providerInfo: {
|
|
1051
1059
|
idToken: result.params.id_token,
|
|
@@ -1059,11 +1067,7 @@ class BitbucketAuthProvider {
|
|
|
1059
1067
|
response.backstageIdentity = await this.signInResolver({
|
|
1060
1068
|
result,
|
|
1061
1069
|
profile
|
|
1062
|
-
},
|
|
1063
|
-
tokenIssuer: this.tokenIssuer,
|
|
1064
|
-
catalogIdentityClient: this.catalogIdentityClient,
|
|
1065
|
-
logger: this.logger
|
|
1066
|
-
});
|
|
1070
|
+
}, context);
|
|
1067
1071
|
}
|
|
1068
1072
|
return response;
|
|
1069
1073
|
}
|
|
@@ -1179,7 +1183,12 @@ class GithubAuthProvider {
|
|
|
1179
1183
|
};
|
|
1180
1184
|
}
|
|
1181
1185
|
async handleResult(result) {
|
|
1182
|
-
const
|
|
1186
|
+
const context = {
|
|
1187
|
+
logger: this.logger,
|
|
1188
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
1189
|
+
tokenIssuer: this.tokenIssuer
|
|
1190
|
+
};
|
|
1191
|
+
const { profile } = await this.authHandler(result, context);
|
|
1183
1192
|
const expiresInStr = result.params.expires_in;
|
|
1184
1193
|
const response = {
|
|
1185
1194
|
providerInfo: {
|
|
@@ -1193,11 +1202,7 @@ class GithubAuthProvider {
|
|
|
1193
1202
|
response.backstageIdentity = await this.signInResolver({
|
|
1194
1203
|
result,
|
|
1195
1204
|
profile
|
|
1196
|
-
},
|
|
1197
|
-
tokenIssuer: this.tokenIssuer,
|
|
1198
|
-
catalogIdentityClient: this.catalogIdentityClient,
|
|
1199
|
-
logger: this.logger
|
|
1200
|
-
});
|
|
1205
|
+
}, context);
|
|
1201
1206
|
}
|
|
1202
1207
|
return response;
|
|
1203
1208
|
}
|
|
@@ -1206,7 +1211,10 @@ const githubDefaultSignInResolver = async (info, ctx) => {
|
|
|
1206
1211
|
const { fullProfile } = info.result;
|
|
1207
1212
|
const userId = fullProfile.username || fullProfile.id;
|
|
1208
1213
|
const token = await ctx.tokenIssuer.issueToken({
|
|
1209
|
-
claims: {
|
|
1214
|
+
claims: {
|
|
1215
|
+
sub: `user:default/${userId}`,
|
|
1216
|
+
ent: [`user:default/${userId}`]
|
|
1217
|
+
}
|
|
1210
1218
|
});
|
|
1211
1219
|
return { id: userId, token };
|
|
1212
1220
|
};
|
|
@@ -1273,7 +1281,7 @@ const gitlabDefaultSignInResolver = async (info, ctx) => {
|
|
|
1273
1281
|
id = profile.email.split("@")[0];
|
|
1274
1282
|
}
|
|
1275
1283
|
const token = await ctx.tokenIssuer.issueToken({
|
|
1276
|
-
claims: { sub: id
|
|
1284
|
+
claims: { sub: `user:default/${id}`, ent: [`user:default/${id}`] }
|
|
1277
1285
|
});
|
|
1278
1286
|
return { id, token };
|
|
1279
1287
|
};
|
|
@@ -1327,7 +1335,12 @@ class GitlabAuthProvider {
|
|
|
1327
1335
|
};
|
|
1328
1336
|
}
|
|
1329
1337
|
async handleResult(result) {
|
|
1330
|
-
const
|
|
1338
|
+
const context = {
|
|
1339
|
+
logger: this.logger,
|
|
1340
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
1341
|
+
tokenIssuer: this.tokenIssuer
|
|
1342
|
+
};
|
|
1343
|
+
const { profile } = await this.authHandler(result, context);
|
|
1331
1344
|
const response = {
|
|
1332
1345
|
providerInfo: {
|
|
1333
1346
|
idToken: result.params.id_token,
|
|
@@ -1341,11 +1354,7 @@ class GitlabAuthProvider {
|
|
|
1341
1354
|
response.backstageIdentity = await this.signInResolver({
|
|
1342
1355
|
result,
|
|
1343
1356
|
profile
|
|
1344
|
-
},
|
|
1345
|
-
tokenIssuer: this.tokenIssuer,
|
|
1346
|
-
catalogIdentityClient: this.catalogIdentityClient,
|
|
1347
|
-
logger: this.logger
|
|
1348
|
-
});
|
|
1357
|
+
}, context);
|
|
1349
1358
|
}
|
|
1350
1359
|
return response;
|
|
1351
1360
|
}
|
|
@@ -1446,7 +1455,12 @@ class GoogleAuthProvider {
|
|
|
1446
1455
|
};
|
|
1447
1456
|
}
|
|
1448
1457
|
async handleResult(result) {
|
|
1449
|
-
const
|
|
1458
|
+
const context = {
|
|
1459
|
+
logger: this.logger,
|
|
1460
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
1461
|
+
tokenIssuer: this.tokenIssuer
|
|
1462
|
+
};
|
|
1463
|
+
const { profile } = await this.authHandler(result, context);
|
|
1450
1464
|
const response = {
|
|
1451
1465
|
providerInfo: {
|
|
1452
1466
|
idToken: result.params.id_token,
|
|
@@ -1460,11 +1474,7 @@ class GoogleAuthProvider {
|
|
|
1460
1474
|
response.backstageIdentity = await this.signInResolver({
|
|
1461
1475
|
result,
|
|
1462
1476
|
profile
|
|
1463
|
-
},
|
|
1464
|
-
tokenIssuer: this.tokenIssuer,
|
|
1465
|
-
catalogIdentityClient: this.catalogIdentityClient,
|
|
1466
|
-
logger: this.logger
|
|
1467
|
-
});
|
|
1477
|
+
}, context);
|
|
1468
1478
|
}
|
|
1469
1479
|
return response;
|
|
1470
1480
|
}
|
|
@@ -1595,7 +1605,12 @@ class MicrosoftAuthProvider {
|
|
|
1595
1605
|
async handleResult(result) {
|
|
1596
1606
|
const photo = await this.getUserPhoto(result.accessToken);
|
|
1597
1607
|
result.fullProfile.photos = photo ? [{ value: photo }] : void 0;
|
|
1598
|
-
const
|
|
1608
|
+
const context = {
|
|
1609
|
+
logger: this.logger,
|
|
1610
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
1611
|
+
tokenIssuer: this.tokenIssuer
|
|
1612
|
+
};
|
|
1613
|
+
const { profile } = await this.authHandler(result, context);
|
|
1599
1614
|
const response = {
|
|
1600
1615
|
providerInfo: {
|
|
1601
1616
|
idToken: result.params.id_token,
|
|
@@ -1609,11 +1624,7 @@ class MicrosoftAuthProvider {
|
|
|
1609
1624
|
response.backstageIdentity = await this.signInResolver({
|
|
1610
1625
|
result,
|
|
1611
1626
|
profile
|
|
1612
|
-
},
|
|
1613
|
-
tokenIssuer: this.tokenIssuer,
|
|
1614
|
-
catalogIdentityClient: this.catalogIdentityClient,
|
|
1615
|
-
logger: this.logger
|
|
1616
|
-
});
|
|
1627
|
+
}, context);
|
|
1617
1628
|
}
|
|
1618
1629
|
return response;
|
|
1619
1630
|
}
|
|
@@ -1654,7 +1665,10 @@ const microsoftDefaultSignInResolver = async (info, ctx) => {
|
|
|
1654
1665
|
}
|
|
1655
1666
|
const userId = profile.email.split("@")[0];
|
|
1656
1667
|
const token = await ctx.tokenIssuer.issueToken({
|
|
1657
|
-
claims: {
|
|
1668
|
+
claims: {
|
|
1669
|
+
sub: `user:default/${userId}`,
|
|
1670
|
+
ent: [`user:default/${userId}`]
|
|
1671
|
+
}
|
|
1658
1672
|
});
|
|
1659
1673
|
return { id: userId, token };
|
|
1660
1674
|
};
|
|
@@ -1765,7 +1779,12 @@ class OAuth2AuthProvider {
|
|
|
1765
1779
|
};
|
|
1766
1780
|
}
|
|
1767
1781
|
async handleResult(result) {
|
|
1768
|
-
const
|
|
1782
|
+
const context = {
|
|
1783
|
+
logger: this.logger,
|
|
1784
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
1785
|
+
tokenIssuer: this.tokenIssuer
|
|
1786
|
+
};
|
|
1787
|
+
const { profile } = await this.authHandler(result, context);
|
|
1769
1788
|
const response = {
|
|
1770
1789
|
providerInfo: {
|
|
1771
1790
|
idToken: result.params.id_token,
|
|
@@ -1779,11 +1798,7 @@ class OAuth2AuthProvider {
|
|
|
1779
1798
|
response.backstageIdentity = await this.signInResolver({
|
|
1780
1799
|
result,
|
|
1781
1800
|
profile
|
|
1782
|
-
},
|
|
1783
|
-
tokenIssuer: this.tokenIssuer,
|
|
1784
|
-
catalogIdentityClient: this.catalogIdentityClient,
|
|
1785
|
-
logger: this.logger
|
|
1786
|
-
});
|
|
1801
|
+
}, context);
|
|
1787
1802
|
}
|
|
1788
1803
|
return response;
|
|
1789
1804
|
}
|
|
@@ -1798,7 +1813,7 @@ const oAuth2DefaultSignInResolver$1 = async (info, ctx) => {
|
|
|
1798
1813
|
}
|
|
1799
1814
|
const userId = profile.email.split("@")[0];
|
|
1800
1815
|
const token = await ctx.tokenIssuer.issueToken({
|
|
1801
|
-
claims: { sub: userId
|
|
1816
|
+
claims: { sub: `user:default/${userId}`, ent: [`user:default/${userId}`] }
|
|
1802
1817
|
});
|
|
1803
1818
|
return { id: userId, token };
|
|
1804
1819
|
};
|
|
@@ -1855,113 +1870,528 @@ const createOAuth2Provider = (options) => {
|
|
|
1855
1870
|
});
|
|
1856
1871
|
};
|
|
1857
1872
|
|
|
1858
|
-
|
|
1873
|
+
function createOidcRouter(options) {
|
|
1874
|
+
const { baseUrl, tokenIssuer } = options;
|
|
1875
|
+
const router = Router__default["default"]();
|
|
1876
|
+
const config = {
|
|
1877
|
+
issuer: baseUrl,
|
|
1878
|
+
token_endpoint: `${baseUrl}/v1/token`,
|
|
1879
|
+
userinfo_endpoint: `${baseUrl}/v1/userinfo`,
|
|
1880
|
+
jwks_uri: `${baseUrl}/.well-known/jwks.json`,
|
|
1881
|
+
response_types_supported: ["id_token"],
|
|
1882
|
+
subject_types_supported: ["public"],
|
|
1883
|
+
id_token_signing_alg_values_supported: ["RS256"],
|
|
1884
|
+
scopes_supported: ["openid"],
|
|
1885
|
+
token_endpoint_auth_methods_supported: [],
|
|
1886
|
+
claims_supported: ["sub"],
|
|
1887
|
+
grant_types_supported: []
|
|
1888
|
+
};
|
|
1889
|
+
router.get("/.well-known/openid-configuration", (_req, res) => {
|
|
1890
|
+
res.json(config);
|
|
1891
|
+
});
|
|
1892
|
+
router.get("/.well-known/jwks.json", async (_req, res) => {
|
|
1893
|
+
const { keys } = await tokenIssuer.listPublicKeys();
|
|
1894
|
+
res.json({ keys });
|
|
1895
|
+
});
|
|
1896
|
+
router.get("/v1/token", (_req, res) => {
|
|
1897
|
+
res.status(501).send("Not Implemented");
|
|
1898
|
+
});
|
|
1899
|
+
router.get("/v1/userinfo", (_req, res) => {
|
|
1900
|
+
res.status(501).send("Not Implemented");
|
|
1901
|
+
});
|
|
1902
|
+
return router;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
const CLOCK_MARGIN_S = 10;
|
|
1906
|
+
class IdentityClient {
|
|
1859
1907
|
constructor(options) {
|
|
1860
|
-
this.
|
|
1861
|
-
this.
|
|
1862
|
-
this.
|
|
1863
|
-
this.
|
|
1864
|
-
this.authHandler = options.authHandler;
|
|
1865
|
-
this.tokenIssuer = options.tokenIssuer;
|
|
1866
|
-
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
1867
|
-
this.logger = options.logger;
|
|
1908
|
+
this.discovery = options.discovery;
|
|
1909
|
+
this.issuer = options.issuer;
|
|
1910
|
+
this.keyStore = new jose.JWKS.KeyStore();
|
|
1911
|
+
this.keyStoreUpdated = 0;
|
|
1868
1912
|
}
|
|
1869
|
-
async
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
state: encodeState(req.state)
|
|
1874
|
-
};
|
|
1875
|
-
const prompt = this.prompt || "none";
|
|
1876
|
-
if (prompt !== "auto") {
|
|
1877
|
-
options.prompt = prompt;
|
|
1913
|
+
async authenticate(token) {
|
|
1914
|
+
var _a;
|
|
1915
|
+
if (!token) {
|
|
1916
|
+
throw new errors.AuthenticationError("No token specified");
|
|
1878
1917
|
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
const
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1918
|
+
const key = await this.getKey(token);
|
|
1919
|
+
if (!key) {
|
|
1920
|
+
throw new errors.AuthenticationError("No signing key matching token found");
|
|
1921
|
+
}
|
|
1922
|
+
const decoded = jose.JWT.IdToken.verify(token, key, {
|
|
1923
|
+
algorithms: ["ES256"],
|
|
1924
|
+
audience: "backstage",
|
|
1925
|
+
issuer: this.issuer
|
|
1926
|
+
});
|
|
1927
|
+
if (!decoded.sub) {
|
|
1928
|
+
throw new errors.AuthenticationError("No user sub found in token");
|
|
1929
|
+
}
|
|
1930
|
+
const user = {
|
|
1931
|
+
id: decoded.sub,
|
|
1932
|
+
token,
|
|
1933
|
+
identity: {
|
|
1934
|
+
type: "user",
|
|
1935
|
+
userEntityRef: decoded.sub,
|
|
1936
|
+
ownershipEntityRefs: (_a = decoded.ent) != null ? _a : []
|
|
1937
|
+
}
|
|
1887
1938
|
};
|
|
1939
|
+
return user;
|
|
1888
1940
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
if (!tokenset.access_token) {
|
|
1893
|
-
throw new Error("Refresh failed");
|
|
1941
|
+
static getBearerToken(authorizationHeader) {
|
|
1942
|
+
if (typeof authorizationHeader !== "string") {
|
|
1943
|
+
return void 0;
|
|
1894
1944
|
}
|
|
1895
|
-
const
|
|
1896
|
-
return
|
|
1897
|
-
response: await this.handleResult({ tokenset, userinfo }),
|
|
1898
|
-
refreshToken: tokenset.refresh_token
|
|
1899
|
-
};
|
|
1945
|
+
const matches = authorizationHeader.match(/Bearer\s+(\S+)/i);
|
|
1946
|
+
return matches == null ? void 0 : matches[1];
|
|
1900
1947
|
}
|
|
1901
|
-
async
|
|
1902
|
-
const
|
|
1903
|
-
|
|
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
|
-
});
|
|
1948
|
+
async getKey(rawJwtToken) {
|
|
1949
|
+
const { header, payload } = jose.JWT.decode(rawJwtToken, {
|
|
1950
|
+
complete: true
|
|
1922
1951
|
});
|
|
1923
|
-
|
|
1924
|
-
|
|
1952
|
+
const keyStoreHasKey = !!this.keyStore.get({ kid: header.kid });
|
|
1953
|
+
const issuedAfterLastRefresh = (payload == null ? void 0 : payload.iat) && payload.iat > this.keyStoreUpdated - CLOCK_MARGIN_S;
|
|
1954
|
+
if (!keyStoreHasKey && issuedAfterLastRefresh) {
|
|
1955
|
+
await this.refreshKeyStore();
|
|
1956
|
+
}
|
|
1957
|
+
return this.keyStore.get({ kid: header.kid });
|
|
1925
1958
|
}
|
|
1926
|
-
async
|
|
1927
|
-
const
|
|
1928
|
-
const response =
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
expiresInSeconds: result.tokenset.expires_in
|
|
1934
|
-
},
|
|
1935
|
-
profile
|
|
1936
|
-
};
|
|
1937
|
-
if (this.signInResolver) {
|
|
1938
|
-
response.backstageIdentity = await this.signInResolver({
|
|
1939
|
-
result,
|
|
1940
|
-
profile
|
|
1941
|
-
}, {
|
|
1942
|
-
tokenIssuer: this.tokenIssuer,
|
|
1943
|
-
catalogIdentityClient: this.catalogIdentityClient,
|
|
1944
|
-
logger: this.logger
|
|
1945
|
-
});
|
|
1959
|
+
async listPublicKeys() {
|
|
1960
|
+
const url = `${await this.discovery.getBaseUrl("auth")}/.well-known/jwks.json`;
|
|
1961
|
+
const response = await fetch__default["default"](url);
|
|
1962
|
+
if (!response.ok) {
|
|
1963
|
+
const payload = await response.text();
|
|
1964
|
+
const message = `Request failed with ${response.status} ${response.statusText}, ${payload}`;
|
|
1965
|
+
throw new Error(message);
|
|
1946
1966
|
}
|
|
1947
|
-
|
|
1967
|
+
const publicKeys = await response.json();
|
|
1968
|
+
return publicKeys;
|
|
1969
|
+
}
|
|
1970
|
+
async refreshKeyStore() {
|
|
1971
|
+
const now = Date.now() / 1e3;
|
|
1972
|
+
const publicKeys = await this.listPublicKeys();
|
|
1973
|
+
this.keyStore = jose.JWKS.asKeyStore({
|
|
1974
|
+
keys: publicKeys.keys.map((key) => key)
|
|
1975
|
+
});
|
|
1976
|
+
this.keyStoreUpdated = now;
|
|
1948
1977
|
}
|
|
1949
1978
|
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1979
|
+
|
|
1980
|
+
const MS_IN_S = 1e3;
|
|
1981
|
+
class TokenFactory {
|
|
1982
|
+
constructor(options) {
|
|
1983
|
+
this.issuer = options.issuer;
|
|
1984
|
+
this.logger = options.logger;
|
|
1985
|
+
this.keyStore = options.keyStore;
|
|
1986
|
+
this.keyDurationSeconds = options.keyDurationSeconds;
|
|
1954
1987
|
}
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
const
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1988
|
+
async issueToken(params) {
|
|
1989
|
+
const key = await this.getKey();
|
|
1990
|
+
const iss = this.issuer;
|
|
1991
|
+
const sub = params.claims.sub;
|
|
1992
|
+
const ent = params.claims.ent;
|
|
1993
|
+
const aud = "backstage";
|
|
1994
|
+
const iat = Math.floor(Date.now() / MS_IN_S);
|
|
1995
|
+
const exp = iat + this.keyDurationSeconds;
|
|
1996
|
+
this.logger.info(`Issuing token for ${sub}, with entities ${ent != null ? ent : []}`);
|
|
1997
|
+
return jose.JWS.sign({ iss, sub, aud, iat, exp, ent }, key, {
|
|
1998
|
+
alg: key.alg,
|
|
1999
|
+
kid: key.kid
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
async listPublicKeys() {
|
|
2003
|
+
const { items: keys } = await this.keyStore.listKeys();
|
|
2004
|
+
const validKeys = [];
|
|
2005
|
+
const expiredKeys = [];
|
|
2006
|
+
for (const key of keys) {
|
|
2007
|
+
const expireAt = luxon.DateTime.fromJSDate(key.createdAt).plus({
|
|
2008
|
+
seconds: 3 * this.keyDurationSeconds
|
|
2009
|
+
});
|
|
2010
|
+
if (expireAt < luxon.DateTime.local()) {
|
|
2011
|
+
expiredKeys.push(key);
|
|
2012
|
+
} else {
|
|
2013
|
+
validKeys.push(key);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
if (expiredKeys.length > 0) {
|
|
2017
|
+
const kids = expiredKeys.map(({ key }) => key.kid);
|
|
2018
|
+
this.logger.info(`Removing expired signing keys, '${kids.join("', '")}'`);
|
|
2019
|
+
this.keyStore.removeKeys(kids).catch((error) => {
|
|
2020
|
+
this.logger.error(`Failed to remove expired keys, ${error}`);
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
return { keys: validKeys.map(({ key }) => key) };
|
|
2024
|
+
}
|
|
2025
|
+
async getKey() {
|
|
2026
|
+
if (this.privateKeyPromise) {
|
|
2027
|
+
if (this.keyExpiry && luxon.DateTime.fromJSDate(this.keyExpiry) > luxon.DateTime.local()) {
|
|
2028
|
+
return this.privateKeyPromise;
|
|
2029
|
+
}
|
|
2030
|
+
this.logger.info(`Signing key has expired, generating new key`);
|
|
2031
|
+
delete this.privateKeyPromise;
|
|
2032
|
+
}
|
|
2033
|
+
this.keyExpiry = luxon.DateTime.utc().plus({
|
|
2034
|
+
seconds: this.keyDurationSeconds
|
|
2035
|
+
}).toJSDate();
|
|
2036
|
+
const promise = (async () => {
|
|
2037
|
+
const key = await jose.JWK.generate("EC", "P-256", {
|
|
2038
|
+
use: "sig",
|
|
2039
|
+
kid: uuid.v4(),
|
|
2040
|
+
alg: "ES256"
|
|
2041
|
+
});
|
|
2042
|
+
this.logger.info(`Created new signing key ${key.kid}`);
|
|
2043
|
+
await this.keyStore.addKey(key.toJWK(false));
|
|
2044
|
+
return key;
|
|
2045
|
+
})();
|
|
2046
|
+
this.privateKeyPromise = promise;
|
|
2047
|
+
try {
|
|
2048
|
+
await promise;
|
|
2049
|
+
} catch (error) {
|
|
2050
|
+
this.logger.error(`Failed to generate new signing key, ${error}`);
|
|
2051
|
+
delete this.keyExpiry;
|
|
2052
|
+
delete this.privateKeyPromise;
|
|
2053
|
+
}
|
|
2054
|
+
return promise;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
const migrationsDir = backendCommon.resolvePackagePath("@backstage/plugin-auth-backend", "migrations");
|
|
2059
|
+
const TABLE = "signing_keys";
|
|
2060
|
+
const parseDate = (date) => {
|
|
2061
|
+
const parsedDate = typeof date === "string" ? luxon.DateTime.fromSQL(date, { zone: "UTC" }) : luxon.DateTime.fromJSDate(date);
|
|
2062
|
+
if (!parsedDate.isValid) {
|
|
2063
|
+
throw new Error(`Failed to parse date, reason: ${parsedDate.invalidReason}, explanation: ${parsedDate.invalidExplanation}`);
|
|
2064
|
+
}
|
|
2065
|
+
return parsedDate.toJSDate();
|
|
2066
|
+
};
|
|
2067
|
+
class DatabaseKeyStore {
|
|
2068
|
+
static async create(options) {
|
|
2069
|
+
const { database } = options;
|
|
2070
|
+
await database.migrate.latest({
|
|
2071
|
+
directory: migrationsDir
|
|
2072
|
+
});
|
|
2073
|
+
return new DatabaseKeyStore(options);
|
|
2074
|
+
}
|
|
2075
|
+
constructor(options) {
|
|
2076
|
+
this.database = options.database;
|
|
2077
|
+
}
|
|
2078
|
+
async addKey(key) {
|
|
2079
|
+
await this.database(TABLE).insert({
|
|
2080
|
+
kid: key.kid,
|
|
2081
|
+
key: JSON.stringify(key)
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
async listKeys() {
|
|
2085
|
+
const rows = await this.database(TABLE).select();
|
|
2086
|
+
return {
|
|
2087
|
+
items: rows.map((row) => ({
|
|
2088
|
+
key: JSON.parse(row.key),
|
|
2089
|
+
createdAt: parseDate(row.created_at)
|
|
2090
|
+
}))
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
async removeKeys(kids) {
|
|
2094
|
+
await this.database(TABLE).delete().whereIn("kid", kids);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
class MemoryKeyStore {
|
|
2099
|
+
constructor() {
|
|
2100
|
+
this.keys = /* @__PURE__ */ new Map();
|
|
2101
|
+
}
|
|
2102
|
+
async addKey(key) {
|
|
2103
|
+
this.keys.set(key.kid, {
|
|
2104
|
+
createdAt: luxon.DateTime.utc().toJSDate(),
|
|
2105
|
+
key: JSON.stringify(key)
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
async removeKeys(kids) {
|
|
2109
|
+
for (const kid of kids) {
|
|
2110
|
+
this.keys.delete(kid);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
async listKeys() {
|
|
2114
|
+
return {
|
|
2115
|
+
items: Array.from(this.keys).map(([, { createdAt, key: keyStr }]) => ({
|
|
2116
|
+
createdAt,
|
|
2117
|
+
key: JSON.parse(keyStr)
|
|
2118
|
+
}))
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
2124
|
+
const DEFAULT_DOCUMENT_PATH = "sessions";
|
|
2125
|
+
class FirestoreKeyStore {
|
|
2126
|
+
constructor(database, path, timeout) {
|
|
2127
|
+
this.database = database;
|
|
2128
|
+
this.path = path;
|
|
2129
|
+
this.timeout = timeout;
|
|
2130
|
+
}
|
|
2131
|
+
static async create(settings) {
|
|
2132
|
+
const { path, timeout, ...firestoreSettings } = settings != null ? settings : {};
|
|
2133
|
+
const database = new firestore.Firestore(firestoreSettings);
|
|
2134
|
+
return new FirestoreKeyStore(database, path != null ? path : DEFAULT_DOCUMENT_PATH, timeout != null ? timeout : DEFAULT_TIMEOUT_MS);
|
|
2135
|
+
}
|
|
2136
|
+
static async verifyConnection(keyStore, logger) {
|
|
2137
|
+
try {
|
|
2138
|
+
await keyStore.verify();
|
|
2139
|
+
} catch (error) {
|
|
2140
|
+
if (process.env.NODE_ENV !== "development") {
|
|
2141
|
+
throw new Error(`Failed to connect to database: ${error.message}`);
|
|
2142
|
+
}
|
|
2143
|
+
logger == null ? void 0 : logger.warn(`Failed to connect to database: ${error.message}`);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
async addKey(key) {
|
|
2147
|
+
await this.withTimeout(this.database.collection(this.path).doc(key.kid).set({
|
|
2148
|
+
kid: key.kid,
|
|
2149
|
+
key: JSON.stringify(key)
|
|
2150
|
+
}));
|
|
2151
|
+
}
|
|
2152
|
+
async listKeys() {
|
|
2153
|
+
const keys = await this.withTimeout(this.database.collection(this.path).get());
|
|
2154
|
+
return {
|
|
2155
|
+
items: keys.docs.map((key) => ({
|
|
2156
|
+
key: key.data(),
|
|
2157
|
+
createdAt: key.createTime.toDate()
|
|
2158
|
+
}))
|
|
2159
|
+
};
|
|
2160
|
+
}
|
|
2161
|
+
async removeKeys(kids) {
|
|
2162
|
+
for (const kid of kids) {
|
|
2163
|
+
await this.withTimeout(this.database.collection(this.path).doc(kid).delete());
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
async withTimeout(operation) {
|
|
2167
|
+
const timer = new Promise((_, reject) => setTimeout(() => {
|
|
2168
|
+
reject(new Error(`Operation timed out after ${this.timeout}ms`));
|
|
2169
|
+
}, this.timeout));
|
|
2170
|
+
return Promise.race([operation, timer]);
|
|
2171
|
+
}
|
|
2172
|
+
async verify() {
|
|
2173
|
+
await this.withTimeout(this.database.collection(this.path).limit(1).get());
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
class KeyStores {
|
|
2178
|
+
static async fromConfig(config, options) {
|
|
2179
|
+
var _a;
|
|
2180
|
+
const { logger, database } = options != null ? options : {};
|
|
2181
|
+
const ks = config.getOptionalConfig("auth.keyStore");
|
|
2182
|
+
const provider = (_a = ks == null ? void 0 : ks.getOptionalString("provider")) != null ? _a : "database";
|
|
2183
|
+
logger == null ? void 0 : logger.info(`Configuring "${provider}" as KeyStore provider`);
|
|
2184
|
+
if (provider === "database") {
|
|
2185
|
+
if (!database) {
|
|
2186
|
+
throw new Error("This KeyStore provider requires a database");
|
|
2187
|
+
}
|
|
2188
|
+
return await DatabaseKeyStore.create({
|
|
2189
|
+
database: await database.getClient()
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
if (provider === "memory") {
|
|
2193
|
+
return new MemoryKeyStore();
|
|
2194
|
+
}
|
|
2195
|
+
if (provider === "firestore") {
|
|
2196
|
+
const settings = ks == null ? void 0 : ks.getConfig(provider);
|
|
2197
|
+
const keyStore = await FirestoreKeyStore.create(lodash.pickBy({
|
|
2198
|
+
projectId: settings == null ? void 0 : settings.getOptionalString("projectId"),
|
|
2199
|
+
keyFilename: settings == null ? void 0 : settings.getOptionalString("keyFilename"),
|
|
2200
|
+
host: settings == null ? void 0 : settings.getOptionalString("host"),
|
|
2201
|
+
port: settings == null ? void 0 : settings.getOptionalNumber("port"),
|
|
2202
|
+
ssl: settings == null ? void 0 : settings.getOptionalBoolean("ssl"),
|
|
2203
|
+
path: settings == null ? void 0 : settings.getOptionalString("path"),
|
|
2204
|
+
timeout: settings == null ? void 0 : settings.getOptionalNumber("timeout")
|
|
2205
|
+
}, (value) => value !== void 0));
|
|
2206
|
+
await FirestoreKeyStore.verifyConnection(keyStore, logger);
|
|
2207
|
+
return keyStore;
|
|
2208
|
+
}
|
|
2209
|
+
throw new Error(`Unknown KeyStore provider: ${provider}`);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
const OAUTH2_PROXY_JWT_HEADER = "X-OAUTH2-PROXY-ID-TOKEN";
|
|
2214
|
+
class Oauth2ProxyAuthProvider {
|
|
2215
|
+
constructor(options) {
|
|
2216
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2217
|
+
this.logger = options.logger;
|
|
2218
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
2219
|
+
this.signInResolver = options.signInResolver;
|
|
2220
|
+
this.authHandler = options.authHandler;
|
|
2221
|
+
}
|
|
2222
|
+
frameHandler() {
|
|
2223
|
+
return Promise.resolve(void 0);
|
|
2224
|
+
}
|
|
2225
|
+
async refresh(req, res) {
|
|
2226
|
+
try {
|
|
2227
|
+
const result = this.getResult(req);
|
|
2228
|
+
const response = await this.handleResult(result);
|
|
2229
|
+
res.json(response);
|
|
2230
|
+
} catch (e) {
|
|
2231
|
+
this.logger.error(`Exception occurred during ${OAUTH2_PROXY_JWT_HEADER} refresh`, e);
|
|
2232
|
+
res.status(401);
|
|
2233
|
+
res.end();
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
start() {
|
|
2237
|
+
return Promise.resolve(void 0);
|
|
2238
|
+
}
|
|
2239
|
+
async handleResult(result) {
|
|
2240
|
+
const ctx = {
|
|
2241
|
+
logger: this.logger,
|
|
2242
|
+
tokenIssuer: this.tokenIssuer,
|
|
2243
|
+
catalogIdentityClient: this.catalogIdentityClient
|
|
2244
|
+
};
|
|
2245
|
+
const { profile } = await this.authHandler(result, ctx);
|
|
2246
|
+
const backstageSignInResult = await this.signInResolver({
|
|
2247
|
+
result,
|
|
2248
|
+
profile
|
|
2249
|
+
}, ctx);
|
|
2250
|
+
return {
|
|
2251
|
+
providerInfo: {
|
|
2252
|
+
accessToken: result.accessToken
|
|
2253
|
+
},
|
|
2254
|
+
backstageIdentity: prepareBackstageIdentityResponse(backstageSignInResult),
|
|
2255
|
+
profile
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2258
|
+
getResult(req) {
|
|
2259
|
+
const authHeader = req.header(OAUTH2_PROXY_JWT_HEADER);
|
|
2260
|
+
const jwt = IdentityClient.getBearerToken(authHeader);
|
|
2261
|
+
if (!jwt) {
|
|
2262
|
+
throw new errors.AuthenticationError(`Missing or in incorrect format - Oauth2Proxy OIDC header: ${OAUTH2_PROXY_JWT_HEADER}`);
|
|
2263
|
+
}
|
|
2264
|
+
const decodedJWT = jose.JWT.decode(jwt);
|
|
2265
|
+
return {
|
|
2266
|
+
fullProfile: decodedJWT,
|
|
2267
|
+
accessToken: jwt
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
const createOauth2ProxyProvider = (options) => ({ catalogApi, logger, tokenIssuer }) => {
|
|
2272
|
+
const signInResolver = options.signIn.resolver;
|
|
2273
|
+
const authHandler = options.authHandler;
|
|
2274
|
+
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2275
|
+
catalogApi,
|
|
2276
|
+
tokenIssuer
|
|
2277
|
+
});
|
|
2278
|
+
return new Oauth2ProxyAuthProvider({
|
|
2279
|
+
logger,
|
|
2280
|
+
signInResolver,
|
|
2281
|
+
authHandler,
|
|
2282
|
+
tokenIssuer,
|
|
2283
|
+
catalogIdentityClient
|
|
2284
|
+
});
|
|
2285
|
+
};
|
|
2286
|
+
|
|
2287
|
+
class OidcAuthProvider {
|
|
2288
|
+
constructor(options) {
|
|
2289
|
+
this.implementation = this.setupStrategy(options);
|
|
2290
|
+
this.scope = options.scope;
|
|
2291
|
+
this.prompt = options.prompt;
|
|
2292
|
+
this.signInResolver = options.signInResolver;
|
|
2293
|
+
this.authHandler = options.authHandler;
|
|
2294
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
2295
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2296
|
+
this.logger = options.logger;
|
|
2297
|
+
}
|
|
2298
|
+
async start(req) {
|
|
2299
|
+
const { strategy } = await this.implementation;
|
|
2300
|
+
const options = {
|
|
2301
|
+
scope: req.scope || this.scope || "openid profile email",
|
|
2302
|
+
state: encodeState(req.state)
|
|
2303
|
+
};
|
|
2304
|
+
const prompt = this.prompt || "none";
|
|
2305
|
+
if (prompt !== "auto") {
|
|
2306
|
+
options.prompt = prompt;
|
|
2307
|
+
}
|
|
2308
|
+
return await executeRedirectStrategy(req, strategy, options);
|
|
2309
|
+
}
|
|
2310
|
+
async handler(req) {
|
|
2311
|
+
const { strategy } = await this.implementation;
|
|
2312
|
+
const { result, privateInfo } = await executeFrameHandlerStrategy(req, strategy);
|
|
2313
|
+
return {
|
|
2314
|
+
response: await this.handleResult(result),
|
|
2315
|
+
refreshToken: privateInfo.refreshToken
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
async refresh(req) {
|
|
2319
|
+
const { client } = await this.implementation;
|
|
2320
|
+
const tokenset = await client.refresh(req.refreshToken);
|
|
2321
|
+
if (!tokenset.access_token) {
|
|
2322
|
+
throw new Error("Refresh failed");
|
|
2323
|
+
}
|
|
2324
|
+
const userinfo = await client.userinfo(tokenset.access_token);
|
|
2325
|
+
return {
|
|
2326
|
+
response: await this.handleResult({ tokenset, userinfo }),
|
|
2327
|
+
refreshToken: tokenset.refresh_token
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
async setupStrategy(options) {
|
|
2331
|
+
const issuer = await openidClient.Issuer.discover(options.metadataUrl);
|
|
2332
|
+
const client = new issuer.Client({
|
|
2333
|
+
access_type: "offline",
|
|
2334
|
+
client_id: options.clientId,
|
|
2335
|
+
client_secret: options.clientSecret,
|
|
2336
|
+
redirect_uris: [options.callbackUrl],
|
|
2337
|
+
response_types: ["code"],
|
|
2338
|
+
id_token_signed_response_alg: options.tokenSignedResponseAlg || "RS256",
|
|
2339
|
+
scope: options.scope || ""
|
|
2340
|
+
});
|
|
2341
|
+
const strategy = new openidClient.Strategy({
|
|
2342
|
+
client,
|
|
2343
|
+
passReqToCallback: false
|
|
2344
|
+
}, (tokenset, userinfo, done) => {
|
|
2345
|
+
if (typeof done !== "function") {
|
|
2346
|
+
throw new Error("OIDC IdP must provide a userinfo_endpoint in the metadata response");
|
|
2347
|
+
}
|
|
2348
|
+
done(void 0, { tokenset, userinfo }, {
|
|
2349
|
+
refreshToken: tokenset.refresh_token
|
|
2350
|
+
});
|
|
2351
|
+
});
|
|
2352
|
+
strategy.error = console.error;
|
|
2353
|
+
return { strategy, client };
|
|
2354
|
+
}
|
|
2355
|
+
async handleResult(result) {
|
|
2356
|
+
const context = {
|
|
2357
|
+
logger: this.logger,
|
|
2358
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
2359
|
+
tokenIssuer: this.tokenIssuer
|
|
2360
|
+
};
|
|
2361
|
+
const { profile } = await this.authHandler(result, context);
|
|
2362
|
+
const response = {
|
|
2363
|
+
providerInfo: {
|
|
2364
|
+
idToken: result.tokenset.id_token,
|
|
2365
|
+
accessToken: result.tokenset.access_token,
|
|
2366
|
+
scope: result.tokenset.scope,
|
|
2367
|
+
expiresInSeconds: result.tokenset.expires_in
|
|
2368
|
+
},
|
|
2369
|
+
profile
|
|
2370
|
+
};
|
|
2371
|
+
if (this.signInResolver) {
|
|
2372
|
+
response.backstageIdentity = await this.signInResolver({
|
|
2373
|
+
result,
|
|
2374
|
+
profile
|
|
2375
|
+
}, context);
|
|
2376
|
+
}
|
|
2377
|
+
return response;
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
const oAuth2DefaultSignInResolver = async (info, ctx) => {
|
|
2381
|
+
const { profile } = info;
|
|
2382
|
+
if (!profile.email) {
|
|
2383
|
+
throw new Error("Profile contained no email");
|
|
2384
|
+
}
|
|
2385
|
+
const userId = profile.email.split("@")[0];
|
|
2386
|
+
const token = await ctx.tokenIssuer.issueToken({
|
|
2387
|
+
claims: { sub: userId, ent: [`user:default/${userId}`] }
|
|
2388
|
+
});
|
|
2389
|
+
return { id: userId, token };
|
|
2390
|
+
};
|
|
2391
|
+
const createOidcProvider = (options) => {
|
|
2392
|
+
return ({
|
|
2393
|
+
providerId,
|
|
2394
|
+
globalConfig,
|
|
1965
2395
|
config,
|
|
1966
2396
|
tokenIssuer,
|
|
1967
2397
|
catalogApi,
|
|
@@ -2076,7 +2506,12 @@ class OktaAuthProvider {
|
|
|
2076
2506
|
};
|
|
2077
2507
|
}
|
|
2078
2508
|
async handleResult(result) {
|
|
2079
|
-
const
|
|
2509
|
+
const context = {
|
|
2510
|
+
logger: this._logger,
|
|
2511
|
+
catalogIdentityClient: this._catalogIdentityClient,
|
|
2512
|
+
tokenIssuer: this._tokenIssuer
|
|
2513
|
+
};
|
|
2514
|
+
const { profile } = await this._authHandler(result, context);
|
|
2080
2515
|
const response = {
|
|
2081
2516
|
providerInfo: {
|
|
2082
2517
|
idToken: result.params.id_token,
|
|
@@ -2090,11 +2525,7 @@ class OktaAuthProvider {
|
|
|
2090
2525
|
response.backstageIdentity = await this._signInResolver({
|
|
2091
2526
|
result,
|
|
2092
2527
|
profile
|
|
2093
|
-
},
|
|
2094
|
-
tokenIssuer: this._tokenIssuer,
|
|
2095
|
-
catalogIdentityClient: this._catalogIdentityClient,
|
|
2096
|
-
logger: this._logger
|
|
2097
|
-
});
|
|
2528
|
+
}, context);
|
|
2098
2529
|
}
|
|
2099
2530
|
return response;
|
|
2100
2531
|
}
|
|
@@ -2117,245 +2548,14 @@ const oktaDefaultSignInResolver = async (info, ctx) => {
|
|
|
2117
2548
|
const { profile } = info;
|
|
2118
2549
|
if (!profile.email) {
|
|
2119
2550
|
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) => {
|
|
2128
|
-
return ({
|
|
2129
|
-
providerId,
|
|
2130
|
-
globalConfig,
|
|
2131
|
-
config,
|
|
2132
|
-
tokenIssuer,
|
|
2133
|
-
catalogApi,
|
|
2134
|
-
logger
|
|
2135
|
-
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
2136
|
-
var _a, _b;
|
|
2137
|
-
const clientId = envConfig.getString("clientId");
|
|
2138
|
-
const clientSecret = envConfig.getString("clientSecret");
|
|
2139
|
-
const audience = envConfig.getString("audience");
|
|
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
|
-
}
|
|
2144
|
-
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2145
|
-
catalogApi,
|
|
2146
|
-
tokenIssuer
|
|
2147
|
-
});
|
|
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,
|
|
2159
|
-
clientId,
|
|
2160
|
-
clientSecret,
|
|
2161
|
-
callbackUrl,
|
|
2162
|
-
authHandler,
|
|
2163
|
-
signInResolver,
|
|
2164
|
-
tokenIssuer,
|
|
2165
|
-
catalogIdentityClient,
|
|
2166
|
-
logger
|
|
2167
|
-
});
|
|
2168
|
-
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
2169
|
-
disableRefresh: false,
|
|
2170
|
-
providerId,
|
|
2171
|
-
tokenIssuer
|
|
2172
|
-
});
|
|
2173
|
-
});
|
|
2174
|
-
};
|
|
2175
|
-
|
|
2176
|
-
class OneLoginProvider {
|
|
2177
|
-
constructor(options) {
|
|
2178
|
-
this.signInResolver = options.signInResolver;
|
|
2179
|
-
this.authHandler = options.authHandler;
|
|
2180
|
-
this.tokenIssuer = options.tokenIssuer;
|
|
2181
|
-
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2182
|
-
this.logger = options.logger;
|
|
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
|
-
});
|
|
2199
|
-
}
|
|
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({
|
|
2220
|
-
fullProfile,
|
|
2221
|
-
params,
|
|
2222
|
-
accessToken
|
|
2223
|
-
}),
|
|
2224
|
-
refreshToken
|
|
2225
|
-
};
|
|
2226
|
-
}
|
|
2227
|
-
async handleResult(result) {
|
|
2228
|
-
const { profile } = await this.authHandler(result);
|
|
2229
|
-
const response = {
|
|
2230
|
-
providerInfo: {
|
|
2231
|
-
idToken: result.params.id_token,
|
|
2232
|
-
accessToken: result.accessToken,
|
|
2233
|
-
scope: result.params.scope,
|
|
2234
|
-
expiresInSeconds: result.params.expires_in
|
|
2235
|
-
},
|
|
2236
|
-
profile
|
|
2237
|
-
};
|
|
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
|
-
});
|
|
2247
|
-
}
|
|
2248
|
-
return response;
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
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`;
|
|
2273
|
-
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2274
|
-
catalogApi,
|
|
2275
|
-
tokenIssuer
|
|
2276
|
-
});
|
|
2277
|
-
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
|
|
2278
|
-
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
2279
|
-
});
|
|
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,
|
|
2285
|
-
issuer,
|
|
2286
|
-
authHandler,
|
|
2287
|
-
signInResolver,
|
|
2288
|
-
tokenIssuer,
|
|
2289
|
-
catalogIdentityClient,
|
|
2290
|
-
logger
|
|
2291
|
-
});
|
|
2292
|
-
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
2293
|
-
disableRefresh: false,
|
|
2294
|
-
providerId,
|
|
2295
|
-
tokenIssuer
|
|
2296
|
-
});
|
|
2297
|
-
});
|
|
2298
|
-
};
|
|
2299
|
-
|
|
2300
|
-
class SamlAuthProvider {
|
|
2301
|
-
constructor(options) {
|
|
2302
|
-
this.appUrl = options.appUrl;
|
|
2303
|
-
this.signInResolver = options.signInResolver;
|
|
2304
|
-
this.authHandler = options.authHandler;
|
|
2305
|
-
this.tokenIssuer = options.tokenIssuer;
|
|
2306
|
-
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2307
|
-
this.logger = options.logger;
|
|
2308
|
-
this.strategy = new passportSaml.Strategy({ ...options }, (fullProfile, done) => {
|
|
2309
|
-
done(void 0, { fullProfile });
|
|
2310
|
-
});
|
|
2311
|
-
}
|
|
2312
|
-
async start(req, res) {
|
|
2313
|
-
const { url } = await executeRedirectStrategy(req, this.strategy, {});
|
|
2314
|
-
res.redirect(url);
|
|
2315
|
-
}
|
|
2316
|
-
async frameHandler(req, res) {
|
|
2317
|
-
try {
|
|
2318
|
-
const { result } = await executeFrameHandlerStrategy(req, this.strategy);
|
|
2319
|
-
const { profile } = await this.authHandler(result);
|
|
2320
|
-
const response = {
|
|
2321
|
-
profile,
|
|
2322
|
-
providerInfo: {}
|
|
2323
|
-
};
|
|
2324
|
-
if (this.signInResolver) {
|
|
2325
|
-
const signInResponse = await this.signInResolver({
|
|
2326
|
-
result,
|
|
2327
|
-
profile
|
|
2328
|
-
}, {
|
|
2329
|
-
tokenIssuer: this.tokenIssuer,
|
|
2330
|
-
catalogIdentityClient: this.catalogIdentityClient,
|
|
2331
|
-
logger: this.logger
|
|
2332
|
-
});
|
|
2333
|
-
response.backstageIdentity = prepareBackstageIdentityResponse(signInResponse);
|
|
2334
|
-
}
|
|
2335
|
-
return postMessageResponse(res, this.appUrl, {
|
|
2336
|
-
type: "authorization_response",
|
|
2337
|
-
response
|
|
2338
|
-
});
|
|
2339
|
-
} catch (error) {
|
|
2340
|
-
const { name, message } = errors.isError(error) ? error : new Error("Encountered invalid error");
|
|
2341
|
-
return postMessageResponse(res, this.appUrl, {
|
|
2342
|
-
type: "authorization_response",
|
|
2343
|
-
error: { name, message }
|
|
2344
|
-
});
|
|
2345
|
-
}
|
|
2346
|
-
}
|
|
2347
|
-
async logout(_req, res) {
|
|
2348
|
-
res.end();
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
const samlDefaultSignInResolver = async (info, ctx) => {
|
|
2352
|
-
const id = info.result.fullProfile.nameID;
|
|
2551
|
+
}
|
|
2552
|
+
const userId = profile.email.split("@")[0];
|
|
2353
2553
|
const token = await ctx.tokenIssuer.issueToken({
|
|
2354
|
-
claims: { sub:
|
|
2554
|
+
claims: { sub: `user:default/${userId}`, ent: [`user:default/${userId}`] }
|
|
2355
2555
|
});
|
|
2356
|
-
return { id, token };
|
|
2556
|
+
return { id: userId, token };
|
|
2357
2557
|
};
|
|
2358
|
-
const
|
|
2558
|
+
const createOktaProvider = (_options) => {
|
|
2359
2559
|
return ({
|
|
2360
2560
|
providerId,
|
|
2361
2561
|
globalConfig,
|
|
@@ -2363,494 +2563,388 @@ const createSamlProvider = (options) => {
|
|
|
2363
2563
|
tokenIssuer,
|
|
2364
2564
|
catalogApi,
|
|
2365
2565
|
logger
|
|
2366
|
-
}) => {
|
|
2566
|
+
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
2367
2567
|
var _a, _b;
|
|
2568
|
+
const clientId = envConfig.getString("clientId");
|
|
2569
|
+
const clientSecret = envConfig.getString("clientSecret");
|
|
2570
|
+
const audience = envConfig.getString("audience");
|
|
2571
|
+
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
2572
|
+
if (!audience.startsWith("https://")) {
|
|
2573
|
+
throw new Error("URL for 'audience' must start with 'https://'.");
|
|
2574
|
+
}
|
|
2368
2575
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2369
2576
|
catalogApi,
|
|
2370
2577
|
tokenIssuer
|
|
2371
2578
|
});
|
|
2372
|
-
const authHandler = (
|
|
2373
|
-
profile:
|
|
2374
|
-
email: fullProfile.email,
|
|
2375
|
-
displayName: fullProfile.displayName
|
|
2376
|
-
}
|
|
2579
|
+
const authHandler = (_options == null ? void 0 : _options.authHandler) ? _options.authHandler : async ({ fullProfile, params }) => ({
|
|
2580
|
+
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
2377
2581
|
});
|
|
2378
|
-
const signInResolverFn = (_b = (_a =
|
|
2582
|
+
const signInResolverFn = (_b = (_a = _options == null ? void 0 : _options.signIn) == null ? void 0 : _a.resolver) != null ? _b : oktaDefaultSignInResolver;
|
|
2379
2583
|
const signInResolver = (info) => signInResolverFn(info, {
|
|
2380
2584
|
catalogIdentityClient,
|
|
2381
2585
|
tokenIssuer,
|
|
2382
2586
|
logger
|
|
2383
2587
|
});
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
decryptionPvk: config.getOptionalString("decryptionPvk"),
|
|
2395
|
-
signatureAlgorithm: config.getOptionalString("signatureAlgorithm"),
|
|
2396
|
-
digestAlgorithm: config.getOptionalString("digestAlgorithm"),
|
|
2397
|
-
acceptedClockSkewMs: config.getOptionalNumber("acceptedClockSkewMs"),
|
|
2398
|
-
tokenIssuer,
|
|
2399
|
-
appUrl: globalConfig.appUrl,
|
|
2400
|
-
authHandler,
|
|
2401
|
-
signInResolver,
|
|
2402
|
-
logger,
|
|
2403
|
-
catalogIdentityClient
|
|
2404
|
-
});
|
|
2405
|
-
};
|
|
2406
|
-
};
|
|
2407
|
-
|
|
2408
|
-
const IAP_JWT_HEADER = "x-goog-iap-jwt-assertion";
|
|
2409
|
-
|
|
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}`);
|
|
2425
|
-
}
|
|
2426
|
-
let payload;
|
|
2427
|
-
try {
|
|
2428
|
-
payload = await tokenValidator(jwtToken);
|
|
2429
|
-
} catch (e) {
|
|
2430
|
-
throw new errors.AuthenticationError(`Google IAP token verification failed, ${e}`);
|
|
2431
|
-
}
|
|
2432
|
-
if (!payload.sub || !payload.email) {
|
|
2433
|
-
throw new errors.AuthenticationError("Google IAP token payload is missing sub and/or email claim");
|
|
2434
|
-
}
|
|
2435
|
-
return {
|
|
2436
|
-
iapToken: {
|
|
2437
|
-
...payload,
|
|
2438
|
-
sub: payload.sub,
|
|
2439
|
-
email: payload.email
|
|
2440
|
-
}
|
|
2441
|
-
};
|
|
2442
|
-
}
|
|
2443
|
-
const defaultAuthHandler = async ({
|
|
2444
|
-
iapToken
|
|
2445
|
-
}) => ({ profile: { email: iapToken.email } });
|
|
2446
|
-
|
|
2447
|
-
class GcpIapProvider {
|
|
2448
|
-
constructor(options) {
|
|
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;
|
|
2455
|
-
}
|
|
2456
|
-
async start() {
|
|
2457
|
-
}
|
|
2458
|
-
async frameHandler() {
|
|
2459
|
-
}
|
|
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
|
|
2467
|
-
});
|
|
2468
|
-
const response = {
|
|
2469
|
-
providerInfo: { iapToken: result.iapToken },
|
|
2470
|
-
profile,
|
|
2471
|
-
backstageIdentity: prepareBackstageIdentityResponse(backstageIdentity)
|
|
2472
|
-
};
|
|
2473
|
-
res.json(response);
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
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,
|
|
2485
|
-
tokenIssuer
|
|
2486
|
-
});
|
|
2487
|
-
return new GcpIapProvider({
|
|
2488
|
-
authHandler,
|
|
2489
|
-
signInResolver,
|
|
2490
|
-
tokenValidator,
|
|
2491
|
-
tokenIssuer,
|
|
2492
|
-
catalogIdentityClient,
|
|
2493
|
-
logger
|
|
2494
|
-
});
|
|
2495
|
-
};
|
|
2496
|
-
}
|
|
2497
|
-
|
|
2498
|
-
const factories = {
|
|
2499
|
-
google: createGoogleProvider(),
|
|
2500
|
-
github: createGithubProvider(),
|
|
2501
|
-
gitlab: createGitlabProvider(),
|
|
2502
|
-
saml: createSamlProvider(),
|
|
2503
|
-
okta: createOktaProvider(),
|
|
2504
|
-
auth0: createAuth0Provider(),
|
|
2505
|
-
microsoft: createMicrosoftProvider(),
|
|
2506
|
-
oauth2: createOAuth2Provider(),
|
|
2507
|
-
oidc: createOidcProvider(),
|
|
2508
|
-
onelogin: createOneLoginProvider(),
|
|
2509
|
-
awsalb: createAwsAlbProvider(),
|
|
2510
|
-
bitbucket: createBitbucketProvider(),
|
|
2511
|
-
atlassian: createAtlassianProvider()
|
|
2512
|
-
};
|
|
2513
|
-
|
|
2514
|
-
function createOidcRouter(options) {
|
|
2515
|
-
const { baseUrl, tokenIssuer } = options;
|
|
2516
|
-
const router = Router__default["default"]();
|
|
2517
|
-
const config = {
|
|
2518
|
-
issuer: baseUrl,
|
|
2519
|
-
token_endpoint: `${baseUrl}/v1/token`,
|
|
2520
|
-
userinfo_endpoint: `${baseUrl}/v1/userinfo`,
|
|
2521
|
-
jwks_uri: `${baseUrl}/.well-known/jwks.json`,
|
|
2522
|
-
response_types_supported: ["id_token"],
|
|
2523
|
-
subject_types_supported: ["public"],
|
|
2524
|
-
id_token_signing_alg_values_supported: ["RS256"],
|
|
2525
|
-
scopes_supported: ["openid"],
|
|
2526
|
-
token_endpoint_auth_methods_supported: [],
|
|
2527
|
-
claims_supported: ["sub"],
|
|
2528
|
-
grant_types_supported: []
|
|
2529
|
-
};
|
|
2530
|
-
router.get("/.well-known/openid-configuration", (_req, res) => {
|
|
2531
|
-
res.json(config);
|
|
2532
|
-
});
|
|
2533
|
-
router.get("/.well-known/jwks.json", async (_req, res) => {
|
|
2534
|
-
const { keys } = await tokenIssuer.listPublicKeys();
|
|
2535
|
-
res.json({ keys });
|
|
2536
|
-
});
|
|
2537
|
-
router.get("/v1/token", (_req, res) => {
|
|
2538
|
-
res.status(501).send("Not Implemented");
|
|
2539
|
-
});
|
|
2540
|
-
router.get("/v1/userinfo", (_req, res) => {
|
|
2541
|
-
res.status(501).send("Not Implemented");
|
|
2542
|
-
});
|
|
2543
|
-
return router;
|
|
2544
|
-
}
|
|
2545
|
-
|
|
2546
|
-
const CLOCK_MARGIN_S = 10;
|
|
2547
|
-
class IdentityClient {
|
|
2548
|
-
constructor(options) {
|
|
2549
|
-
this.discovery = options.discovery;
|
|
2550
|
-
this.issuer = options.issuer;
|
|
2551
|
-
this.keyStore = new jose.JWKS.KeyStore();
|
|
2552
|
-
this.keyStoreUpdated = 0;
|
|
2553
|
-
}
|
|
2554
|
-
async authenticate(token) {
|
|
2555
|
-
var _a;
|
|
2556
|
-
if (!token) {
|
|
2557
|
-
throw new errors.AuthenticationError("No token specified");
|
|
2558
|
-
}
|
|
2559
|
-
const key = await this.getKey(token);
|
|
2560
|
-
if (!key) {
|
|
2561
|
-
throw new errors.AuthenticationError("No signing key matching token found");
|
|
2562
|
-
}
|
|
2563
|
-
const decoded = jose.JWT.IdToken.verify(token, key, {
|
|
2564
|
-
algorithms: ["ES256"],
|
|
2565
|
-
audience: "backstage",
|
|
2566
|
-
issuer: this.issuer
|
|
2567
|
-
});
|
|
2568
|
-
if (!decoded.sub) {
|
|
2569
|
-
throw new errors.AuthenticationError("No user sub found in token");
|
|
2570
|
-
}
|
|
2571
|
-
const user = {
|
|
2572
|
-
id: decoded.sub,
|
|
2573
|
-
token,
|
|
2574
|
-
identity: {
|
|
2575
|
-
type: "user",
|
|
2576
|
-
userEntityRef: decoded.sub,
|
|
2577
|
-
ownershipEntityRefs: (_a = decoded.ent) != null ? _a : []
|
|
2578
|
-
}
|
|
2579
|
-
};
|
|
2580
|
-
return user;
|
|
2581
|
-
}
|
|
2582
|
-
static getBearerToken(authorizationHeader) {
|
|
2583
|
-
if (typeof authorizationHeader !== "string") {
|
|
2584
|
-
return void 0;
|
|
2585
|
-
}
|
|
2586
|
-
const matches = authorizationHeader.match(/Bearer\s+(\S+)/i);
|
|
2587
|
-
return matches == null ? void 0 : matches[1];
|
|
2588
|
-
}
|
|
2589
|
-
async getKey(rawJwtToken) {
|
|
2590
|
-
const { header, payload } = jose.JWT.decode(rawJwtToken, {
|
|
2591
|
-
complete: true
|
|
2592
|
-
});
|
|
2593
|
-
const keyStoreHasKey = !!this.keyStore.get({ kid: header.kid });
|
|
2594
|
-
const issuedAfterLastRefresh = (payload == null ? void 0 : payload.iat) && payload.iat > this.keyStoreUpdated - CLOCK_MARGIN_S;
|
|
2595
|
-
if (!keyStoreHasKey && issuedAfterLastRefresh) {
|
|
2596
|
-
await this.refreshKeyStore();
|
|
2597
|
-
}
|
|
2598
|
-
return this.keyStore.get({ kid: header.kid });
|
|
2599
|
-
}
|
|
2600
|
-
async listPublicKeys() {
|
|
2601
|
-
const url = `${await this.discovery.getBaseUrl("auth")}/.well-known/jwks.json`;
|
|
2602
|
-
const response = await fetch__default["default"](url);
|
|
2603
|
-
if (!response.ok) {
|
|
2604
|
-
const payload = await response.text();
|
|
2605
|
-
const message = `Request failed with ${response.status} ${response.statusText}, ${payload}`;
|
|
2606
|
-
throw new Error(message);
|
|
2607
|
-
}
|
|
2608
|
-
const publicKeys = await response.json();
|
|
2609
|
-
return publicKeys;
|
|
2610
|
-
}
|
|
2611
|
-
async refreshKeyStore() {
|
|
2612
|
-
const now = Date.now() / 1e3;
|
|
2613
|
-
const publicKeys = await this.listPublicKeys();
|
|
2614
|
-
this.keyStore = jose.JWKS.asKeyStore({
|
|
2615
|
-
keys: publicKeys.keys.map((key) => key)
|
|
2588
|
+
const provider = new OktaAuthProvider({
|
|
2589
|
+
audience,
|
|
2590
|
+
clientId,
|
|
2591
|
+
clientSecret,
|
|
2592
|
+
callbackUrl,
|
|
2593
|
+
authHandler,
|
|
2594
|
+
signInResolver,
|
|
2595
|
+
tokenIssuer,
|
|
2596
|
+
catalogIdentityClient,
|
|
2597
|
+
logger
|
|
2616
2598
|
});
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2599
|
+
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
2600
|
+
disableRefresh: false,
|
|
2601
|
+
providerId,
|
|
2602
|
+
tokenIssuer
|
|
2603
|
+
});
|
|
2604
|
+
});
|
|
2605
|
+
};
|
|
2620
2606
|
|
|
2621
|
-
|
|
2622
|
-
class TokenFactory {
|
|
2607
|
+
class OneLoginProvider {
|
|
2623
2608
|
constructor(options) {
|
|
2624
|
-
this.
|
|
2609
|
+
this.signInResolver = options.signInResolver;
|
|
2610
|
+
this.authHandler = options.authHandler;
|
|
2611
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
2612
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2625
2613
|
this.logger = options.logger;
|
|
2626
|
-
this.
|
|
2627
|
-
|
|
2614
|
+
this._strategy = new passportOneloginOauth.Strategy({
|
|
2615
|
+
issuer: options.issuer,
|
|
2616
|
+
clientID: options.clientId,
|
|
2617
|
+
clientSecret: options.clientSecret,
|
|
2618
|
+
callbackURL: options.callbackUrl,
|
|
2619
|
+
passReqToCallback: false
|
|
2620
|
+
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
2621
|
+
done(void 0, {
|
|
2622
|
+
accessToken,
|
|
2623
|
+
refreshToken,
|
|
2624
|
+
params,
|
|
2625
|
+
fullProfile
|
|
2626
|
+
}, {
|
|
2627
|
+
refreshToken
|
|
2628
|
+
});
|
|
2629
|
+
});
|
|
2628
2630
|
}
|
|
2629
|
-
async
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
const iat = Math.floor(Date.now() / MS_IN_S);
|
|
2636
|
-
const exp = iat + this.keyDurationSeconds;
|
|
2637
|
-
this.logger.info(`Issuing token for ${sub}, with entities ${ent != null ? ent : []}`);
|
|
2638
|
-
return jose.JWS.sign({ iss, sub, aud, iat, exp, ent }, key, {
|
|
2639
|
-
alg: key.alg,
|
|
2640
|
-
kid: key.kid
|
|
2631
|
+
async start(req) {
|
|
2632
|
+
return await executeRedirectStrategy(req, this._strategy, {
|
|
2633
|
+
accessType: "offline",
|
|
2634
|
+
prompt: "consent",
|
|
2635
|
+
scope: "openid",
|
|
2636
|
+
state: encodeState(req.state)
|
|
2641
2637
|
});
|
|
2642
2638
|
}
|
|
2643
|
-
async
|
|
2644
|
-
const {
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
seconds: 3 * this.keyDurationSeconds
|
|
2650
|
-
});
|
|
2651
|
-
if (expireAt < luxon.DateTime.local()) {
|
|
2652
|
-
expiredKeys.push(key);
|
|
2653
|
-
} else {
|
|
2654
|
-
validKeys.push(key);
|
|
2655
|
-
}
|
|
2656
|
-
}
|
|
2657
|
-
if (expiredKeys.length > 0) {
|
|
2658
|
-
const kids = expiredKeys.map(({ key }) => key.kid);
|
|
2659
|
-
this.logger.info(`Removing expired signing keys, '${kids.join("', '")}'`);
|
|
2660
|
-
this.keyStore.removeKeys(kids).catch((error) => {
|
|
2661
|
-
this.logger.error(`Failed to remove expired keys, ${error}`);
|
|
2662
|
-
});
|
|
2663
|
-
}
|
|
2664
|
-
return { keys: validKeys.map(({ key }) => key) };
|
|
2639
|
+
async handler(req) {
|
|
2640
|
+
const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
|
|
2641
|
+
return {
|
|
2642
|
+
response: await this.handleResult(result),
|
|
2643
|
+
refreshToken: privateInfo.refreshToken
|
|
2644
|
+
};
|
|
2665
2645
|
}
|
|
2666
|
-
async
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
}
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2646
|
+
async refresh(req) {
|
|
2647
|
+
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
2648
|
+
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
2649
|
+
return {
|
|
2650
|
+
response: await this.handleResult({
|
|
2651
|
+
fullProfile,
|
|
2652
|
+
params,
|
|
2653
|
+
accessToken
|
|
2654
|
+
}),
|
|
2655
|
+
refreshToken
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
async handleResult(result) {
|
|
2659
|
+
const context = {
|
|
2660
|
+
logger: this.logger,
|
|
2661
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
2662
|
+
tokenIssuer: this.tokenIssuer
|
|
2663
|
+
};
|
|
2664
|
+
const { profile } = await this.authHandler(result, context);
|
|
2665
|
+
const response = {
|
|
2666
|
+
providerInfo: {
|
|
2667
|
+
idToken: result.params.id_token,
|
|
2668
|
+
accessToken: result.accessToken,
|
|
2669
|
+
scope: result.params.scope,
|
|
2670
|
+
expiresInSeconds: result.params.expires_in
|
|
2671
|
+
},
|
|
2672
|
+
profile
|
|
2673
|
+
};
|
|
2674
|
+
if (this.signInResolver) {
|
|
2675
|
+
response.backstageIdentity = await this.signInResolver({
|
|
2676
|
+
result,
|
|
2677
|
+
profile
|
|
2678
|
+
}, context);
|
|
2694
2679
|
}
|
|
2695
|
-
return
|
|
2680
|
+
return response;
|
|
2696
2681
|
}
|
|
2697
2682
|
}
|
|
2698
|
-
|
|
2699
|
-
const
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
const parsedDate = typeof date === "string" ? luxon.DateTime.fromSQL(date, { zone: "UTC" }) : luxon.DateTime.fromJSDate(date);
|
|
2703
|
-
if (!parsedDate.isValid) {
|
|
2704
|
-
throw new Error(`Failed to parse date, reason: ${parsedDate.invalidReason}, explanation: ${parsedDate.invalidExplanation}`);
|
|
2683
|
+
const defaultSignInResolver = async (info) => {
|
|
2684
|
+
const { profile } = info;
|
|
2685
|
+
if (!profile.email) {
|
|
2686
|
+
throw new Error("OIDC profile contained no email");
|
|
2705
2687
|
}
|
|
2706
|
-
|
|
2688
|
+
const id = profile.email.split("@")[0];
|
|
2689
|
+
return { id, token: "" };
|
|
2707
2690
|
};
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2691
|
+
const createOneLoginProvider = (options) => {
|
|
2692
|
+
return ({
|
|
2693
|
+
providerId,
|
|
2694
|
+
globalConfig,
|
|
2695
|
+
config,
|
|
2696
|
+
tokenIssuer,
|
|
2697
|
+
catalogApi,
|
|
2698
|
+
logger
|
|
2699
|
+
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
2700
|
+
var _a, _b;
|
|
2701
|
+
const clientId = envConfig.getString("clientId");
|
|
2702
|
+
const clientSecret = envConfig.getString("clientSecret");
|
|
2703
|
+
const issuer = envConfig.getString("issuer");
|
|
2704
|
+
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
2705
|
+
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2706
|
+
catalogApi,
|
|
2707
|
+
tokenIssuer
|
|
2713
2708
|
});
|
|
2714
|
-
|
|
2715
|
-
|
|
2709
|
+
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
|
|
2710
|
+
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
2711
|
+
});
|
|
2712
|
+
const signInResolver = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : defaultSignInResolver;
|
|
2713
|
+
const provider = new OneLoginProvider({
|
|
2714
|
+
clientId,
|
|
2715
|
+
clientSecret,
|
|
2716
|
+
callbackUrl,
|
|
2717
|
+
issuer,
|
|
2718
|
+
authHandler,
|
|
2719
|
+
signInResolver,
|
|
2720
|
+
tokenIssuer,
|
|
2721
|
+
catalogIdentityClient,
|
|
2722
|
+
logger
|
|
2723
|
+
});
|
|
2724
|
+
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
2725
|
+
disableRefresh: false,
|
|
2726
|
+
providerId,
|
|
2727
|
+
tokenIssuer
|
|
2728
|
+
});
|
|
2729
|
+
});
|
|
2730
|
+
};
|
|
2731
|
+
|
|
2732
|
+
class SamlAuthProvider {
|
|
2716
2733
|
constructor(options) {
|
|
2717
|
-
this.
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2734
|
+
this.appUrl = options.appUrl;
|
|
2735
|
+
this.signInResolver = options.signInResolver;
|
|
2736
|
+
this.authHandler = options.authHandler;
|
|
2737
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
2738
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2739
|
+
this.logger = options.logger;
|
|
2740
|
+
this.strategy = new passportSaml.Strategy({ ...options }, (fullProfile, done) => {
|
|
2741
|
+
done(void 0, { fullProfile });
|
|
2723
2742
|
});
|
|
2724
2743
|
}
|
|
2725
|
-
async
|
|
2726
|
-
const
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2744
|
+
async start(req, res) {
|
|
2745
|
+
const { url } = await executeRedirectStrategy(req, this.strategy, {});
|
|
2746
|
+
res.redirect(url);
|
|
2747
|
+
}
|
|
2748
|
+
async frameHandler(req, res) {
|
|
2749
|
+
try {
|
|
2750
|
+
const context = {
|
|
2751
|
+
logger: this.logger,
|
|
2752
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
2753
|
+
tokenIssuer: this.tokenIssuer
|
|
2754
|
+
};
|
|
2755
|
+
const { result } = await executeFrameHandlerStrategy(req, this.strategy);
|
|
2756
|
+
const { profile } = await this.authHandler(result, context);
|
|
2757
|
+
const response = {
|
|
2758
|
+
profile,
|
|
2759
|
+
providerInfo: {}
|
|
2760
|
+
};
|
|
2761
|
+
if (this.signInResolver) {
|
|
2762
|
+
const signInResponse = await this.signInResolver({
|
|
2763
|
+
result,
|
|
2764
|
+
profile
|
|
2765
|
+
}, context);
|
|
2766
|
+
response.backstageIdentity = prepareBackstageIdentityResponse(signInResponse);
|
|
2767
|
+
}
|
|
2768
|
+
return postMessageResponse(res, this.appUrl, {
|
|
2769
|
+
type: "authorization_response",
|
|
2770
|
+
response
|
|
2771
|
+
});
|
|
2772
|
+
} catch (error) {
|
|
2773
|
+
const { name, message } = errors.isError(error) ? error : new Error("Encountered invalid error");
|
|
2774
|
+
return postMessageResponse(res, this.appUrl, {
|
|
2775
|
+
type: "authorization_response",
|
|
2776
|
+
error: { name, message }
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2733
2779
|
}
|
|
2734
|
-
async
|
|
2735
|
-
|
|
2780
|
+
async logout(_req, res) {
|
|
2781
|
+
res.end();
|
|
2736
2782
|
}
|
|
2737
2783
|
}
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
}
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2784
|
+
const samlDefaultSignInResolver = async (info, ctx) => {
|
|
2785
|
+
const id = info.result.fullProfile.nameID;
|
|
2786
|
+
const token = await ctx.tokenIssuer.issueToken({
|
|
2787
|
+
claims: { sub: id }
|
|
2788
|
+
});
|
|
2789
|
+
return { id, token };
|
|
2790
|
+
};
|
|
2791
|
+
const createSamlProvider = (options) => {
|
|
2792
|
+
return ({
|
|
2793
|
+
providerId,
|
|
2794
|
+
globalConfig,
|
|
2795
|
+
config,
|
|
2796
|
+
tokenIssuer,
|
|
2797
|
+
catalogApi,
|
|
2798
|
+
logger
|
|
2799
|
+
}) => {
|
|
2800
|
+
var _a, _b;
|
|
2801
|
+
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2802
|
+
catalogApi,
|
|
2803
|
+
tokenIssuer
|
|
2747
2804
|
});
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2805
|
+
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
|
|
2806
|
+
profile: {
|
|
2807
|
+
email: fullProfile.email,
|
|
2808
|
+
displayName: fullProfile.displayName
|
|
2809
|
+
}
|
|
2810
|
+
});
|
|
2811
|
+
const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : samlDefaultSignInResolver;
|
|
2812
|
+
const signInResolver = (info) => signInResolverFn(info, {
|
|
2813
|
+
catalogIdentityClient,
|
|
2814
|
+
tokenIssuer,
|
|
2815
|
+
logger
|
|
2816
|
+
});
|
|
2817
|
+
return new SamlAuthProvider({
|
|
2818
|
+
callbackUrl: `${globalConfig.baseUrl}/${providerId}/handler/frame`,
|
|
2819
|
+
entryPoint: config.getString("entryPoint"),
|
|
2820
|
+
logoutUrl: config.getOptionalString("logoutUrl"),
|
|
2821
|
+
audience: config.getOptionalString("audience"),
|
|
2822
|
+
issuer: config.getString("issuer"),
|
|
2823
|
+
cert: config.getString("cert"),
|
|
2824
|
+
privateKey: config.getOptionalString("privateKey"),
|
|
2825
|
+
authnContext: config.getOptionalStringArray("authnContext"),
|
|
2826
|
+
identifierFormat: config.getOptionalString("identifierFormat"),
|
|
2827
|
+
decryptionPvk: config.getOptionalString("decryptionPvk"),
|
|
2828
|
+
signatureAlgorithm: config.getOptionalString("signatureAlgorithm"),
|
|
2829
|
+
digestAlgorithm: config.getOptionalString("digestAlgorithm"),
|
|
2830
|
+
acceptedClockSkewMs: config.getOptionalNumber("acceptedClockSkewMs"),
|
|
2831
|
+
tokenIssuer,
|
|
2832
|
+
appUrl: globalConfig.appUrl,
|
|
2833
|
+
authHandler,
|
|
2834
|
+
signInResolver,
|
|
2835
|
+
logger,
|
|
2836
|
+
catalogIdentityClient
|
|
2837
|
+
});
|
|
2838
|
+
};
|
|
2839
|
+
};
|
|
2840
|
+
|
|
2841
|
+
const IAP_JWT_HEADER = "x-goog-iap-jwt-assertion";
|
|
2842
|
+
|
|
2843
|
+
function createTokenValidator(audience, mockClient) {
|
|
2844
|
+
const client = mockClient != null ? mockClient : new googleAuthLibrary.OAuth2Client();
|
|
2845
|
+
return async function tokenValidator(token) {
|
|
2846
|
+
const response = await client.getIapPublicKeys();
|
|
2847
|
+
const ticket = await client.verifySignedJwtWithCertsAsync(token, response.pubkeys, audience, ["https://cloud.google.com/iap"]);
|
|
2848
|
+
const payload = ticket.getPayload();
|
|
2849
|
+
if (!payload) {
|
|
2850
|
+
throw new TypeError("Token had no payload");
|
|
2752
2851
|
}
|
|
2852
|
+
return payload;
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
async function parseRequestToken(jwtToken, tokenValidator) {
|
|
2856
|
+
if (typeof jwtToken !== "string" || !jwtToken) {
|
|
2857
|
+
throw new errors.AuthenticationError(`Missing Google IAP header: ${IAP_JWT_HEADER}`);
|
|
2753
2858
|
}
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
}))
|
|
2760
|
-
};
|
|
2859
|
+
let payload;
|
|
2860
|
+
try {
|
|
2861
|
+
payload = await tokenValidator(jwtToken);
|
|
2862
|
+
} catch (e) {
|
|
2863
|
+
throw new errors.AuthenticationError(`Google IAP token verification failed, ${e}`);
|
|
2761
2864
|
}
|
|
2865
|
+
if (!payload.sub || !payload.email) {
|
|
2866
|
+
throw new errors.AuthenticationError("Google IAP token payload is missing sub and/or email claim");
|
|
2867
|
+
}
|
|
2868
|
+
return {
|
|
2869
|
+
iapToken: {
|
|
2870
|
+
...payload,
|
|
2871
|
+
sub: payload.sub,
|
|
2872
|
+
email: payload.email
|
|
2873
|
+
}
|
|
2874
|
+
};
|
|
2762
2875
|
}
|
|
2876
|
+
const defaultAuthHandler = async ({
|
|
2877
|
+
iapToken
|
|
2878
|
+
}) => ({ profile: { email: iapToken.email } });
|
|
2763
2879
|
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
this.
|
|
2769
|
-
this.
|
|
2770
|
-
this.
|
|
2771
|
-
|
|
2772
|
-
static async create(settings) {
|
|
2773
|
-
const { path, timeout, ...firestoreSettings } = settings != null ? settings : {};
|
|
2774
|
-
const database = new firestore.Firestore(firestoreSettings);
|
|
2775
|
-
return new FirestoreKeyStore(database, path != null ? path : DEFAULT_DOCUMENT_PATH, timeout != null ? timeout : DEFAULT_TIMEOUT_MS);
|
|
2880
|
+
class GcpIapProvider {
|
|
2881
|
+
constructor(options) {
|
|
2882
|
+
this.authHandler = options.authHandler;
|
|
2883
|
+
this.signInResolver = options.signInResolver;
|
|
2884
|
+
this.tokenValidator = options.tokenValidator;
|
|
2885
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
2886
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
2887
|
+
this.logger = options.logger;
|
|
2776
2888
|
}
|
|
2777
|
-
|
|
2778
|
-
try {
|
|
2779
|
-
await keyStore.verify();
|
|
2780
|
-
} catch (error) {
|
|
2781
|
-
if (process.env.NODE_ENV !== "development") {
|
|
2782
|
-
throw new Error(`Failed to connect to database: ${error.message}`);
|
|
2783
|
-
}
|
|
2784
|
-
logger == null ? void 0 : logger.warn(`Failed to connect to database: ${error.message}`);
|
|
2785
|
-
}
|
|
2889
|
+
async start() {
|
|
2786
2890
|
}
|
|
2787
|
-
async
|
|
2788
|
-
await this.withTimeout(this.database.collection(this.path).doc(key.kid).set({
|
|
2789
|
-
kid: key.kid,
|
|
2790
|
-
key: JSON.stringify(key)
|
|
2791
|
-
}));
|
|
2891
|
+
async frameHandler() {
|
|
2792
2892
|
}
|
|
2793
|
-
async
|
|
2794
|
-
const
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
}))
|
|
2893
|
+
async refresh(req, res) {
|
|
2894
|
+
const result = await parseRequestToken(req.header(IAP_JWT_HEADER), this.tokenValidator);
|
|
2895
|
+
const context = {
|
|
2896
|
+
logger: this.logger,
|
|
2897
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
2898
|
+
tokenIssuer: this.tokenIssuer
|
|
2800
2899
|
};
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
reject(new Error(`Operation timed out after ${this.timeout}ms`));
|
|
2810
|
-
}, this.timeout));
|
|
2811
|
-
return Promise.race([operation, timer]);
|
|
2812
|
-
}
|
|
2813
|
-
async verify() {
|
|
2814
|
-
await this.withTimeout(this.database.collection(this.path).limit(1).get());
|
|
2900
|
+
const { profile } = await this.authHandler(result, context);
|
|
2901
|
+
const backstageIdentity = await this.signInResolver({ profile, result }, context);
|
|
2902
|
+
const response = {
|
|
2903
|
+
providerInfo: { iapToken: result.iapToken },
|
|
2904
|
+
profile,
|
|
2905
|
+
backstageIdentity: prepareBackstageIdentityResponse(backstageIdentity)
|
|
2906
|
+
};
|
|
2907
|
+
res.json(response);
|
|
2815
2908
|
}
|
|
2816
2909
|
}
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
static async fromConfig(config, options) {
|
|
2910
|
+
function createGcpIapProvider(options) {
|
|
2911
|
+
return ({ config, tokenIssuer, catalogApi, logger }) => {
|
|
2820
2912
|
var _a;
|
|
2821
|
-
const
|
|
2822
|
-
const
|
|
2823
|
-
const
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
const keyStore = await FirestoreKeyStore.create(lodash.pickBy({
|
|
2839
|
-
projectId: settings == null ? void 0 : settings.getOptionalString("projectId"),
|
|
2840
|
-
keyFilename: settings == null ? void 0 : settings.getOptionalString("keyFilename"),
|
|
2841
|
-
host: settings == null ? void 0 : settings.getOptionalString("host"),
|
|
2842
|
-
port: settings == null ? void 0 : settings.getOptionalNumber("port"),
|
|
2843
|
-
ssl: settings == null ? void 0 : settings.getOptionalBoolean("ssl"),
|
|
2844
|
-
path: settings == null ? void 0 : settings.getOptionalString("path"),
|
|
2845
|
-
timeout: settings == null ? void 0 : settings.getOptionalNumber("timeout")
|
|
2846
|
-
}, (value) => value !== void 0));
|
|
2847
|
-
await FirestoreKeyStore.verifyConnection(keyStore, logger);
|
|
2848
|
-
return keyStore;
|
|
2849
|
-
}
|
|
2850
|
-
throw new Error(`Unknown KeyStore provider: ${provider}`);
|
|
2851
|
-
}
|
|
2913
|
+
const audience = config.getString("audience");
|
|
2914
|
+
const authHandler = (_a = options.authHandler) != null ? _a : defaultAuthHandler;
|
|
2915
|
+
const signInResolver = options.signIn.resolver;
|
|
2916
|
+
const tokenValidator = createTokenValidator(audience);
|
|
2917
|
+
const catalogIdentityClient = new CatalogIdentityClient({
|
|
2918
|
+
catalogApi,
|
|
2919
|
+
tokenIssuer
|
|
2920
|
+
});
|
|
2921
|
+
return new GcpIapProvider({
|
|
2922
|
+
authHandler,
|
|
2923
|
+
signInResolver,
|
|
2924
|
+
tokenValidator,
|
|
2925
|
+
tokenIssuer,
|
|
2926
|
+
catalogIdentityClient,
|
|
2927
|
+
logger
|
|
2928
|
+
});
|
|
2929
|
+
};
|
|
2852
2930
|
}
|
|
2853
2931
|
|
|
2932
|
+
const factories = {
|
|
2933
|
+
google: createGoogleProvider(),
|
|
2934
|
+
github: createGithubProvider(),
|
|
2935
|
+
gitlab: createGitlabProvider(),
|
|
2936
|
+
saml: createSamlProvider(),
|
|
2937
|
+
okta: createOktaProvider(),
|
|
2938
|
+
auth0: createAuth0Provider(),
|
|
2939
|
+
microsoft: createMicrosoftProvider(),
|
|
2940
|
+
oauth2: createOAuth2Provider(),
|
|
2941
|
+
oidc: createOidcProvider(),
|
|
2942
|
+
onelogin: createOneLoginProvider(),
|
|
2943
|
+
awsalb: createAwsAlbProvider(),
|
|
2944
|
+
bitbucket: createBitbucketProvider(),
|
|
2945
|
+
atlassian: createAtlassianProvider()
|
|
2946
|
+
};
|
|
2947
|
+
|
|
2854
2948
|
async function createRouter(options) {
|
|
2855
2949
|
const { logger, config, discovery, database, providerFactories } = options;
|
|
2856
2950
|
const router = Router__default["default"]();
|
|
@@ -2873,7 +2967,7 @@ async function createRouter(options) {
|
|
|
2873
2967
|
secret,
|
|
2874
2968
|
saveUninitialized: false,
|
|
2875
2969
|
resave: false,
|
|
2876
|
-
cookie: { secure: enforceCookieSSL }
|
|
2970
|
+
cookie: { secure: enforceCookieSSL ? "auto" : false }
|
|
2877
2971
|
}));
|
|
2878
2972
|
router.use(passport__default["default"].initialize());
|
|
2879
2973
|
router.use(passport__default["default"].session());
|
|
@@ -2969,6 +3063,7 @@ exports.createGitlabProvider = createGitlabProvider;
|
|
|
2969
3063
|
exports.createGoogleProvider = createGoogleProvider;
|
|
2970
3064
|
exports.createMicrosoftProvider = createMicrosoftProvider;
|
|
2971
3065
|
exports.createOAuth2Provider = createOAuth2Provider;
|
|
3066
|
+
exports.createOauth2ProxyProvider = createOauth2ProxyProvider;
|
|
2972
3067
|
exports.createOidcProvider = createOidcProvider;
|
|
2973
3068
|
exports.createOktaProvider = createOktaProvider;
|
|
2974
3069
|
exports.createOneLoginProvider = createOneLoginProvider;
|