@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/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 { profile } = await this.authHandler(result);
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 { profile } = await this.authHandler(result);
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 { profile } = await this.authHandler(result);
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 { profile } = await this.authHandler(result);
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 { profile } = await this.authHandler(result);
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: { sub: userId, ent: [`user:default/${userId}`] }
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, ent: [`user:default/${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 { profile } = await this.authHandler(result);
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 { profile } = await this.authHandler(result);
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 { profile } = await this.authHandler(result);
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: { sub: userId, ent: [`user:default/${userId}`] }
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 { profile } = await this.authHandler(result);
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, ent: [`user:default/${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
- class OidcAuthProvider {
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.implementation = this.setupStrategy(options);
1861
- this.scope = options.scope;
1862
- this.prompt = options.prompt;
1863
- this.signInResolver = options.signInResolver;
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 start(req) {
1870
- const { strategy } = await this.implementation;
1871
- const options = {
1872
- scope: req.scope || this.scope || "openid profile email",
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
- return await executeRedirectStrategy(req, strategy, options);
1880
- }
1881
- async handler(req) {
1882
- const { strategy } = await this.implementation;
1883
- const { result, privateInfo } = await executeFrameHandlerStrategy(req, strategy);
1884
- return {
1885
- response: await this.handleResult(result),
1886
- refreshToken: privateInfo.refreshToken
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
- async refresh(req) {
1890
- const { client } = await this.implementation;
1891
- const tokenset = await client.refresh(req.refreshToken);
1892
- if (!tokenset.access_token) {
1893
- throw new Error("Refresh failed");
1941
+ static getBearerToken(authorizationHeader) {
1942
+ if (typeof authorizationHeader !== "string") {
1943
+ return void 0;
1894
1944
  }
1895
- const userinfo = await client.userinfo(tokenset.access_token);
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 setupStrategy(options) {
1902
- const issuer = await openidClient.Issuer.discover(options.metadataUrl);
1903
- const client = new issuer.Client({
1904
- access_type: "offline",
1905
- client_id: options.clientId,
1906
- client_secret: options.clientSecret,
1907
- redirect_uris: [options.callbackUrl],
1908
- response_types: ["code"],
1909
- id_token_signed_response_alg: options.tokenSignedResponseAlg || "RS256",
1910
- scope: options.scope || ""
1911
- });
1912
- const strategy = new openidClient.Strategy({
1913
- client,
1914
- passReqToCallback: false
1915
- }, (tokenset, userinfo, done) => {
1916
- if (typeof done !== "function") {
1917
- throw new Error("OIDC IdP must provide a userinfo_endpoint in the metadata response");
1918
- }
1919
- done(void 0, { tokenset, userinfo }, {
1920
- refreshToken: tokenset.refresh_token
1921
- });
1948
+ async getKey(rawJwtToken) {
1949
+ const { header, payload } = jose.JWT.decode(rawJwtToken, {
1950
+ complete: true
1922
1951
  });
1923
- strategy.error = console.error;
1924
- return { strategy, client };
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 handleResult(result) {
1927
- const { profile } = await this.authHandler(result);
1928
- const response = {
1929
- providerInfo: {
1930
- idToken: result.tokenset.id_token,
1931
- accessToken: result.tokenset.access_token,
1932
- scope: result.tokenset.scope,
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
- return response;
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
- const oAuth2DefaultSignInResolver = async (info, ctx) => {
1951
- const { profile } = info;
1952
- if (!profile.email) {
1953
- throw new Error("Profile contained no email");
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
- const userId = profile.email.split("@")[0];
1956
- const token = await ctx.tokenIssuer.issueToken({
1957
- claims: { sub: userId, ent: [`user:default/${userId}`] }
1958
- });
1959
- return { id: userId, token };
1960
- };
1961
- const createOidcProvider = (options) => {
1962
- return ({
1963
- providerId,
1964
- globalConfig,
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 { profile } = await this._authHandler(result);
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: id }
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 createSamlProvider = (options) => {
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 = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
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 = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : samlDefaultSignInResolver;
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
- return new SamlAuthProvider({
2385
- callbackUrl: `${globalConfig.baseUrl}/${providerId}/handler/frame`,
2386
- entryPoint: config.getString("entryPoint"),
2387
- logoutUrl: config.getOptionalString("logoutUrl"),
2388
- audience: config.getOptionalString("audience"),
2389
- issuer: config.getString("issuer"),
2390
- cert: config.getString("cert"),
2391
- privateKey: config.getOptionalString("privateKey"),
2392
- authnContext: config.getOptionalStringArray("authnContext"),
2393
- identifierFormat: config.getOptionalString("identifierFormat"),
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
- this.keyStoreUpdated = now;
2618
- }
2619
- }
2599
+ return OAuthAdapter.fromConfig(globalConfig, provider, {
2600
+ disableRefresh: false,
2601
+ providerId,
2602
+ tokenIssuer
2603
+ });
2604
+ });
2605
+ };
2620
2606
 
2621
- const MS_IN_S = 1e3;
2622
- class TokenFactory {
2607
+ class OneLoginProvider {
2623
2608
  constructor(options) {
2624
- this.issuer = options.issuer;
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.keyStore = options.keyStore;
2627
- this.keyDurationSeconds = options.keyDurationSeconds;
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 issueToken(params) {
2630
- const key = await this.getKey();
2631
- const iss = this.issuer;
2632
- const sub = params.claims.sub;
2633
- const ent = params.claims.ent;
2634
- const aud = "backstage";
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 listPublicKeys() {
2644
- const { items: keys } = await this.keyStore.listKeys();
2645
- const validKeys = [];
2646
- const expiredKeys = [];
2647
- for (const key of keys) {
2648
- const expireAt = luxon.DateTime.fromJSDate(key.createdAt).plus({
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 getKey() {
2667
- if (this.privateKeyPromise) {
2668
- if (this.keyExpiry && luxon.DateTime.fromJSDate(this.keyExpiry) > luxon.DateTime.local()) {
2669
- return this.privateKeyPromise;
2670
- }
2671
- this.logger.info(`Signing key has expired, generating new key`);
2672
- delete this.privateKeyPromise;
2673
- }
2674
- this.keyExpiry = luxon.DateTime.utc().plus({
2675
- seconds: this.keyDurationSeconds
2676
- }).toJSDate();
2677
- const promise = (async () => {
2678
- const key = await jose.JWK.generate("EC", "P-256", {
2679
- use: "sig",
2680
- kid: uuid.v4(),
2681
- alg: "ES256"
2682
- });
2683
- this.logger.info(`Created new signing key ${key.kid}`);
2684
- await this.keyStore.addKey(key.toJWK(false));
2685
- return key;
2686
- })();
2687
- this.privateKeyPromise = promise;
2688
- try {
2689
- await promise;
2690
- } catch (error) {
2691
- this.logger.error(`Failed to generate new signing key, ${error}`);
2692
- delete this.keyExpiry;
2693
- delete this.privateKeyPromise;
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 promise;
2680
+ return response;
2696
2681
  }
2697
2682
  }
2698
-
2699
- const migrationsDir = backendCommon.resolvePackagePath("@backstage/plugin-auth-backend", "migrations");
2700
- const TABLE = "signing_keys";
2701
- const parseDate = (date) => {
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
- return parsedDate.toJSDate();
2688
+ const id = profile.email.split("@")[0];
2689
+ return { id, token: "" };
2707
2690
  };
2708
- class DatabaseKeyStore {
2709
- static async create(options) {
2710
- const { database } = options;
2711
- await database.migrate.latest({
2712
- directory: migrationsDir
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
- return new DatabaseKeyStore(options);
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.database = options.database;
2718
- }
2719
- async addKey(key) {
2720
- await this.database(TABLE).insert({
2721
- kid: key.kid,
2722
- key: JSON.stringify(key)
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 listKeys() {
2726
- const rows = await this.database(TABLE).select();
2727
- return {
2728
- items: rows.map((row) => ({
2729
- key: JSON.parse(row.key),
2730
- createdAt: parseDate(row.created_at)
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 removeKeys(kids) {
2735
- await this.database(TABLE).delete().whereIn("kid", kids);
2780
+ async logout(_req, res) {
2781
+ res.end();
2736
2782
  }
2737
2783
  }
2738
-
2739
- class MemoryKeyStore {
2740
- constructor() {
2741
- this.keys = /* @__PURE__ */ new Map();
2742
- }
2743
- async addKey(key) {
2744
- this.keys.set(key.kid, {
2745
- createdAt: luxon.DateTime.utc().toJSDate(),
2746
- key: JSON.stringify(key)
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
- async removeKeys(kids) {
2750
- for (const kid of kids) {
2751
- this.keys.delete(kid);
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
- async listKeys() {
2755
- return {
2756
- items: Array.from(this.keys).map(([, { createdAt, key: keyStr }]) => ({
2757
- createdAt,
2758
- key: JSON.parse(keyStr)
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
- const DEFAULT_TIMEOUT_MS = 1e4;
2765
- const DEFAULT_DOCUMENT_PATH = "sessions";
2766
- class FirestoreKeyStore {
2767
- constructor(database, path, timeout) {
2768
- this.database = database;
2769
- this.path = path;
2770
- this.timeout = timeout;
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
- static async verifyConnection(keyStore, logger) {
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 addKey(key) {
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 listKeys() {
2794
- const keys = await this.withTimeout(this.database.collection(this.path).get());
2795
- return {
2796
- items: keys.docs.map((key) => ({
2797
- key: key.data(),
2798
- createdAt: key.createTime.toDate()
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
- async removeKeys(kids) {
2803
- for (const kid of kids) {
2804
- await this.withTimeout(this.database.collection(this.path).doc(kid).delete());
2805
- }
2806
- }
2807
- async withTimeout(operation) {
2808
- const timer = new Promise((_, reject) => setTimeout(() => {
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
- class KeyStores {
2819
- static async fromConfig(config, options) {
2910
+ function createGcpIapProvider(options) {
2911
+ return ({ config, tokenIssuer, catalogApi, logger }) => {
2820
2912
  var _a;
2821
- const { logger, database } = options != null ? options : {};
2822
- const ks = config.getOptionalConfig("auth.keyStore");
2823
- const provider = (_a = ks == null ? void 0 : ks.getOptionalString("provider")) != null ? _a : "database";
2824
- logger == null ? void 0 : logger.info(`Configuring "${provider}" as KeyStore provider`);
2825
- if (provider === "database") {
2826
- if (!database) {
2827
- throw new Error("This KeyStore provider requires a database");
2828
- }
2829
- return await DatabaseKeyStore.create({
2830
- database: await database.getClient()
2831
- });
2832
- }
2833
- if (provider === "memory") {
2834
- return new MemoryKeyStore();
2835
- }
2836
- if (provider === "firestore") {
2837
- const settings = ks == null ? void 0 : ks.getConfig(provider);
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;