@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/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
  }
@@ -1327,7 +1332,12 @@ class GitlabAuthProvider {
1327
1332
  };
1328
1333
  }
1329
1334
  async handleResult(result) {
1330
- const { profile } = await this.authHandler(result);
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 { profile } = await this.authHandler(result);
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 { profile } = await this.authHandler(result);
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 { profile } = await this.authHandler(result);
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
- class OidcAuthProvider {
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.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;
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 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;
1907
+ async authenticate(token) {
1908
+ var _a;
1909
+ if (!token) {
1910
+ throw new errors.AuthenticationError("No token specified");
1878
1911
  }
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
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
- 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");
1935
+ static getBearerToken(authorizationHeader) {
1936
+ if (typeof authorizationHeader !== "string") {
1937
+ return void 0;
1894
1938
  }
1895
- const userinfo = await client.userinfo(tokenset.access_token);
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 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
- });
1942
+ async getKey(rawJwtToken) {
1943
+ const { header, payload } = jose.JWT.decode(rawJwtToken, {
1944
+ complete: true
1922
1945
  });
1923
- strategy.error = console.error;
1924
- return { strategy, client };
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 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
- });
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
- return response;
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
- const oAuth2DefaultSignInResolver = async (info, ctx) => {
1951
- const { profile } = info;
1952
- if (!profile.email) {
1953
- throw new Error("Profile contained no email");
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
- 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,
1965
- config,
1966
- tokenIssuer,
1967
- catalogApi,
1968
- logger
1969
- }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
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 { profile } = await this._authHandler(result);
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: id }
2548
+ claims: { sub: userId, ent: [`user:default/${userId}`] }
2355
2549
  });
2356
- return { id, token };
2550
+ return { id: userId, token };
2357
2551
  };
2358
- const createSamlProvider = (options) => {
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 = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile }) => ({
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 = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : samlDefaultSignInResolver;
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
- 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)
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
- this.keyStoreUpdated = now;
2618
- }
2619
- }
2593
+ return OAuthAdapter.fromConfig(globalConfig, provider, {
2594
+ disableRefresh: false,
2595
+ providerId,
2596
+ tokenIssuer
2597
+ });
2598
+ });
2599
+ };
2620
2600
 
2621
- const MS_IN_S = 1e3;
2622
- class TokenFactory {
2601
+ class OneLoginProvider {
2623
2602
  constructor(options) {
2624
- this.issuer = options.issuer;
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.keyStore = options.keyStore;
2627
- this.keyDurationSeconds = options.keyDurationSeconds;
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 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
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 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) };
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 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;
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 promise;
2674
+ return response;
2696
2675
  }
2697
2676
  }
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}`);
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
- return parsedDate.toJSDate();
2682
+ const id = profile.email.split("@")[0];
2683
+ return { id, token: "" };
2707
2684
  };
2708
- class DatabaseKeyStore {
2709
- static async create(options) {
2710
- const { database } = options;
2711
- await database.migrate.latest({
2712
- directory: migrationsDir
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
- return new DatabaseKeyStore(options);
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.database = options.database;
2718
- }
2719
- async addKey(key) {
2720
- await this.database(TABLE).insert({
2721
- kid: key.kid,
2722
- key: JSON.stringify(key)
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 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
- };
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 removeKeys(kids) {
2735
- await this.database(TABLE).delete().whereIn("kid", kids);
2774
+ async logout(_req, res) {
2775
+ res.end();
2736
2776
  }
2737
2777
  }
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)
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
- async removeKeys(kids) {
2750
- for (const kid of kids) {
2751
- this.keys.delete(kid);
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
- async listKeys() {
2755
- return {
2756
- items: Array.from(this.keys).map(([, { createdAt, key: keyStr }]) => ({
2757
- createdAt,
2758
- key: JSON.parse(keyStr)
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
- 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);
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
- 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
- }
2883
+ async start() {
2786
2884
  }
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
- }));
2885
+ async frameHandler() {
2792
2886
  }
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
- }))
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
- 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());
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
- class KeyStores {
2819
- static async fromConfig(config, options) {
2904
+ function createGcpIapProvider(options) {
2905
+ return ({ config, tokenIssuer, catalogApi, logger }) => {
2820
2906
  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
- }
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;