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