@backstage/plugin-auth-backend 0.9.0-next.1 → 0.10.1

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,18 @@ var passportGithub2 = require('passport-github2');
20
20
  var passportGitlab2 = require('passport-gitlab2');
21
21
  var passportGoogleOauth20 = require('passport-google-oauth20');
22
22
  var passportMicrosoft = require('passport-microsoft');
23
- var 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
+ var pluginAuthNode = require('@backstage/plugin-auth-node');
28
24
  var openidClient = require('openid-client');
29
25
  var passportOktaOauth = require('passport-okta-oauth');
30
26
  var passportOneloginOauth = require('passport-onelogin-oauth');
31
27
  var passportSaml = require('passport-saml');
32
28
  var googleAuthLibrary = require('google-auth-library');
33
29
  var catalogClient = require('@backstage/catalog-client');
30
+ var uuid = require('uuid');
31
+ var luxon = require('luxon');
32
+ var backendCommon = require('@backstage/backend-common');
33
+ var firestore = require('@google-cloud/firestore');
34
+ var lodash = require('lodash');
34
35
  var session = require('express-session');
35
36
  var passport = require('passport');
36
37
  var minimatch = require('minimatch');
@@ -149,15 +150,14 @@ const verifyNonce = (req, providerId) => {
149
150
  throw new Error("Invalid nonce");
150
151
  }
151
152
  };
152
- const getCookieConfig = (authUrl, providerId) => {
153
- const { hostname: cookieDomain, pathname, protocol } = authUrl;
153
+ const defaultCookieConfigurer = ({
154
+ callbackUrl,
155
+ providerId
156
+ }) => {
157
+ const { hostname: domain, pathname, protocol } = new URL(callbackUrl);
154
158
  const secure = protocol === "https:";
155
- const cookiePath = pathname.endsWith(`${providerId}/handler/frame`) ? pathname.slice(0, -"/handler/frame".length) : `${pathname}/${providerId}`;
156
- return {
157
- cookieDomain,
158
- cookiePath,
159
- secure
160
- };
159
+ const path = pathname.endsWith(`${providerId}/handler/frame`) ? pathname.slice(0, -"/handler/frame".length) : `${pathname}/${providerId}`;
160
+ return { domain, path, secure };
161
161
  };
162
162
 
163
163
  class OAuthEnvironmentHandler {
@@ -257,7 +257,7 @@ function prepareBackstageIdentityResponse(result) {
257
257
  const { sub, ent } = parseJwtPayload(result.token);
258
258
  const userEntityRef = catalogModel.stringifyEntityRef(catalogModel.parseEntityRef(sub, {
259
259
  defaultKind: "user",
260
- defaultNamespace: catalogModel.ENTITY_DEFAULT_NAMESPACE
260
+ defaultNamespace: catalogModel.DEFAULT_NAMESPACE
261
261
  }));
262
262
  return {
263
263
  ...{
@@ -317,14 +317,18 @@ class OAuthAdapter {
317
317
  static fromConfig(config, handlers, options) {
318
318
  var _a;
319
319
  const { origin: appOrigin } = new url.URL(config.appUrl);
320
- const authUrl = new url.URL((_a = options.callbackUrl) != null ? _a : config.baseUrl);
321
- const { cookieDomain, cookiePath, secure } = getCookieConfig(authUrl, options.providerId);
320
+ const cookieConfigurer = (_a = config.cookieConfigurer) != null ? _a : defaultCookieConfigurer;
321
+ const cookieConfig = cookieConfigurer({
322
+ providerId: options.providerId,
323
+ baseUrl: config.baseUrl,
324
+ callbackUrl: options.callbackUrl
325
+ });
322
326
  return new OAuthAdapter(handlers, {
323
327
  ...options,
324
328
  appOrigin,
325
- cookieDomain,
326
- cookiePath,
327
- secure,
329
+ cookieDomain: cookieConfig.domain,
330
+ cookiePath: cookieConfig.path,
331
+ secure: cookieConfig.secure,
328
332
  isOriginAllowed: config.isOriginAllowed
329
333
  });
330
334
  }
@@ -430,7 +434,7 @@ class OAuthAdapter {
430
434
  }
431
435
  const userEntityRef = catalogModel.stringifyEntityRef(catalogModel.parseEntityRef(identity.id, {
432
436
  defaultKind: "user",
433
- defaultNamespace: catalogModel.ENTITY_DEFAULT_NAMESPACE
437
+ defaultNamespace: catalogModel.DEFAULT_NAMESPACE
434
438
  }));
435
439
  const token = await this.options.tokenIssuer.issueToken({
436
440
  claims: { sub: userEntityRef }
@@ -713,7 +717,8 @@ const createAtlassianProvider = (options) => {
713
717
  const clientId = envConfig.getString("clientId");
714
718
  const clientSecret = envConfig.getString("clientSecret");
715
719
  const scopes = envConfig.getString("scopes");
716
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
720
+ const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
721
+ const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
717
722
  const catalogIdentityClient = new CatalogIdentityClient({
718
723
  catalogApi,
719
724
  tokenManager
@@ -731,9 +736,9 @@ const createAtlassianProvider = (options) => {
731
736
  tokenIssuer
732
737
  });
733
738
  return OAuthAdapter.fromConfig(globalConfig, provider, {
734
- disableRefresh: true,
735
739
  providerId,
736
- tokenIssuer
740
+ tokenIssuer,
741
+ callbackUrl
737
742
  });
738
743
  });
739
744
  };
@@ -849,7 +854,8 @@ const createAuth0Provider = (options) => {
849
854
  const clientId = envConfig.getString("clientId");
850
855
  const clientSecret = envConfig.getString("clientSecret");
851
856
  const domain = envConfig.getString("domain");
852
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
857
+ const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
858
+ const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
853
859
  const catalogIdentityClient = new CatalogIdentityClient({
854
860
  catalogApi,
855
861
  tokenManager
@@ -872,7 +878,8 @@ const createAuth0Provider = (options) => {
872
878
  return OAuthAdapter.fromConfig(globalConfig, provider, {
873
879
  disableRefresh: true,
874
880
  providerId,
875
- tokenIssuer
881
+ tokenIssuer,
882
+ callbackUrl
876
883
  });
877
884
  });
878
885
  };
@@ -1123,7 +1130,8 @@ const createBitbucketProvider = (options) => {
1123
1130
  var _a;
1124
1131
  const clientId = envConfig.getString("clientId");
1125
1132
  const clientSecret = envConfig.getString("clientSecret");
1126
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1133
+ const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
1134
+ const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1127
1135
  const catalogIdentityClient = new CatalogIdentityClient({
1128
1136
  catalogApi,
1129
1137
  tokenManager
@@ -1144,7 +1152,8 @@ const createBitbucketProvider = (options) => {
1144
1152
  return OAuthAdapter.fromConfig(globalConfig, provider, {
1145
1153
  disableRefresh: false,
1146
1154
  providerId,
1147
- tokenIssuer
1155
+ tokenIssuer,
1156
+ callbackUrl
1148
1157
  });
1149
1158
  });
1150
1159
  };
@@ -1418,7 +1427,8 @@ const createGitlabProvider = (options) => {
1418
1427
  const clientSecret = envConfig.getString("clientSecret");
1419
1428
  const audience = envConfig.getOptionalString("audience");
1420
1429
  const baseUrl = audience || "https://gitlab.com";
1421
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1430
+ const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
1431
+ const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1422
1432
  const catalogIdentityClient = new CatalogIdentityClient({
1423
1433
  catalogApi,
1424
1434
  tokenManager
@@ -1444,7 +1454,8 @@ const createGitlabProvider = (options) => {
1444
1454
  return OAuthAdapter.fromConfig(globalConfig, provider, {
1445
1455
  disableRefresh: false,
1446
1456
  providerId,
1447
- tokenIssuer
1457
+ tokenIssuer,
1458
+ callbackUrl
1448
1459
  });
1449
1460
  });
1450
1461
  };
@@ -1573,7 +1584,8 @@ const createGoogleProvider = (options) => {
1573
1584
  var _a, _b;
1574
1585
  const clientId = envConfig.getString("clientId");
1575
1586
  const clientSecret = envConfig.getString("clientSecret");
1576
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1587
+ const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
1588
+ const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1577
1589
  const catalogIdentityClient = new CatalogIdentityClient({
1578
1590
  catalogApi,
1579
1591
  tokenManager
@@ -1600,7 +1612,8 @@ const createGoogleProvider = (options) => {
1600
1612
  return OAuthAdapter.fromConfig(globalConfig, provider, {
1601
1613
  disableRefresh: false,
1602
1614
  providerId,
1603
- tokenIssuer
1615
+ tokenIssuer,
1616
+ callbackUrl
1604
1617
  });
1605
1618
  });
1606
1619
  };
@@ -1732,7 +1745,8 @@ const createMicrosoftProvider = (options) => {
1732
1745
  const clientId = envConfig.getString("clientId");
1733
1746
  const clientSecret = envConfig.getString("clientSecret");
1734
1747
  const tenantId = envConfig.getString("tenantId");
1735
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1748
+ const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
1749
+ const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1736
1750
  const authorizationUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`;
1737
1751
  const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
1738
1752
  const catalogIdentityClient = new CatalogIdentityClient({
@@ -1763,7 +1777,8 @@ const createMicrosoftProvider = (options) => {
1763
1777
  return OAuthAdapter.fromConfig(globalConfig, provider, {
1764
1778
  disableRefresh: false,
1765
1779
  providerId,
1766
- tokenIssuer
1780
+ tokenIssuer,
1781
+ callbackUrl
1767
1782
  });
1768
1783
  });
1769
1784
  };
@@ -1877,7 +1892,8 @@ const createOAuth2Provider = (options) => {
1877
1892
  var _a, _b, _c;
1878
1893
  const clientId = envConfig.getString("clientId");
1879
1894
  const clientSecret = envConfig.getString("clientSecret");
1880
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1895
+ const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
1896
+ const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1881
1897
  const authorizationUrl = envConfig.getString("authorizationUrl");
1882
1898
  const tokenUrl = envConfig.getString("tokenUrl");
1883
1899
  const scope = envConfig.getOptionalString("scope");
@@ -1913,633 +1929,449 @@ const createOAuth2Provider = (options) => {
1913
1929
  return OAuthAdapter.fromConfig(globalConfig, provider, {
1914
1930
  disableRefresh,
1915
1931
  providerId,
1916
- tokenIssuer
1932
+ tokenIssuer,
1933
+ callbackUrl
1917
1934
  });
1918
1935
  });
1919
1936
  };
1920
1937
 
1921
- function createOidcRouter(options) {
1922
- const { baseUrl, tokenIssuer } = options;
1923
- const router = Router__default["default"]();
1924
- const config = {
1925
- issuer: baseUrl,
1926
- token_endpoint: `${baseUrl}/v1/token`,
1927
- userinfo_endpoint: `${baseUrl}/v1/userinfo`,
1928
- jwks_uri: `${baseUrl}/.well-known/jwks.json`,
1929
- response_types_supported: ["id_token"],
1930
- subject_types_supported: ["public"],
1931
- id_token_signing_alg_values_supported: ["RS256"],
1932
- scopes_supported: ["openid"],
1933
- token_endpoint_auth_methods_supported: [],
1934
- claims_supported: ["sub"],
1935
- grant_types_supported: []
1936
- };
1937
- router.get("/.well-known/openid-configuration", (_req, res) => {
1938
- res.json(config);
1939
- });
1940
- router.get("/.well-known/jwks.json", async (_req, res) => {
1941
- const { keys } = await tokenIssuer.listPublicKeys();
1942
- res.json({ keys });
1943
- });
1944
- router.get("/v1/token", (_req, res) => {
1945
- res.status(501).send("Not Implemented");
1946
- });
1947
- router.get("/v1/userinfo", (_req, res) => {
1948
- res.status(501).send("Not Implemented");
1949
- });
1950
- return router;
1951
- }
1952
-
1953
- const CLOCK_MARGIN_S = 10;
1954
- class IdentityClient {
1938
+ const OAUTH2_PROXY_JWT_HEADER = "X-OAUTH2-PROXY-ID-TOKEN";
1939
+ class Oauth2ProxyAuthProvider {
1955
1940
  constructor(options) {
1956
- this.discovery = options.discovery;
1957
- this.issuer = options.issuer;
1958
- this.keyStore = new jose.JWKS.KeyStore();
1959
- this.keyStoreUpdated = 0;
1941
+ this.catalogIdentityClient = options.catalogIdentityClient;
1942
+ this.logger = options.logger;
1943
+ this.tokenIssuer = options.tokenIssuer;
1944
+ this.signInResolver = options.signInResolver;
1945
+ this.authHandler = options.authHandler;
1960
1946
  }
1961
- async authenticate(token) {
1962
- var _a;
1963
- if (!token) {
1964
- throw new errors.AuthenticationError("No token specified");
1965
- }
1966
- const key = await this.getKey(token);
1967
- if (!key) {
1968
- throw new errors.AuthenticationError("No signing key matching token found");
1969
- }
1970
- const decoded = jose.JWT.IdToken.verify(token, key, {
1971
- algorithms: ["ES256"],
1972
- audience: "backstage",
1973
- issuer: this.issuer
1974
- });
1975
- if (!decoded.sub) {
1976
- throw new errors.AuthenticationError("No user sub found in token");
1977
- }
1978
- const user = {
1979
- id: decoded.sub,
1980
- token,
1981
- identity: {
1982
- type: "user",
1983
- userEntityRef: decoded.sub,
1984
- ownershipEntityRefs: (_a = decoded.ent) != null ? _a : []
1985
- }
1986
- };
1987
- return user;
1947
+ frameHandler() {
1948
+ return Promise.resolve(void 0);
1988
1949
  }
1989
- static getBearerToken(authorizationHeader) {
1990
- if (typeof authorizationHeader !== "string") {
1991
- return void 0;
1950
+ async refresh(req, res) {
1951
+ try {
1952
+ const result = this.getResult(req);
1953
+ const response = await this.handleResult(result);
1954
+ res.json(response);
1955
+ } catch (e) {
1956
+ this.logger.error(`Exception occurred during ${OAUTH2_PROXY_JWT_HEADER} refresh`, e);
1957
+ res.status(401);
1958
+ res.end();
1992
1959
  }
1993
- const matches = authorizationHeader.match(/Bearer\s+(\S+)/i);
1994
- return matches == null ? void 0 : matches[1];
1995
1960
  }
1996
- async getKey(rawJwtToken) {
1997
- const { header, payload } = jose.JWT.decode(rawJwtToken, {
1998
- complete: true
1999
- });
2000
- const keyStoreHasKey = !!this.keyStore.get({ kid: header.kid });
2001
- const issuedAfterLastRefresh = (payload == null ? void 0 : payload.iat) && payload.iat > this.keyStoreUpdated - CLOCK_MARGIN_S;
2002
- if (!keyStoreHasKey && issuedAfterLastRefresh) {
2003
- await this.refreshKeyStore();
2004
- }
2005
- return this.keyStore.get({ kid: header.kid });
1961
+ start() {
1962
+ return Promise.resolve(void 0);
2006
1963
  }
2007
- async listPublicKeys() {
2008
- const url = `${await this.discovery.getBaseUrl("auth")}/.well-known/jwks.json`;
2009
- const response = await fetch__default["default"](url);
2010
- if (!response.ok) {
2011
- const payload = await response.text();
2012
- const message = `Request failed with ${response.status} ${response.statusText}, ${payload}`;
2013
- throw new Error(message);
2014
- }
2015
- const publicKeys = await response.json();
2016
- return publicKeys;
1964
+ async handleResult(result) {
1965
+ const ctx = {
1966
+ logger: this.logger,
1967
+ tokenIssuer: this.tokenIssuer,
1968
+ catalogIdentityClient: this.catalogIdentityClient
1969
+ };
1970
+ const { profile } = await this.authHandler(result, ctx);
1971
+ const backstageSignInResult = await this.signInResolver({
1972
+ result,
1973
+ profile
1974
+ }, ctx);
1975
+ return {
1976
+ providerInfo: {
1977
+ accessToken: result.accessToken
1978
+ },
1979
+ backstageIdentity: prepareBackstageIdentityResponse(backstageSignInResult),
1980
+ profile
1981
+ };
2017
1982
  }
2018
- async refreshKeyStore() {
2019
- const now = Date.now() / 1e3;
2020
- const publicKeys = await this.listPublicKeys();
2021
- this.keyStore = jose.JWKS.asKeyStore({
2022
- keys: publicKeys.keys.map((key) => key)
2023
- });
2024
- this.keyStoreUpdated = now;
1983
+ getResult(req) {
1984
+ const authHeader = req.header(OAUTH2_PROXY_JWT_HEADER);
1985
+ const jwt = pluginAuthNode.getBearerTokenFromAuthorizationHeader(authHeader);
1986
+ if (!jwt) {
1987
+ throw new errors.AuthenticationError(`Missing or in incorrect format - Oauth2Proxy OIDC header: ${OAUTH2_PROXY_JWT_HEADER}`);
1988
+ }
1989
+ const decodedJWT = jose.JWT.decode(jwt);
1990
+ return {
1991
+ fullProfile: decodedJWT,
1992
+ accessToken: jwt
1993
+ };
2025
1994
  }
2026
1995
  }
1996
+ const createOauth2ProxyProvider = (options) => ({ catalogApi, logger, tokenIssuer, tokenManager }) => {
1997
+ const signInResolver = options.signIn.resolver;
1998
+ const authHandler = options.authHandler;
1999
+ const catalogIdentityClient = new CatalogIdentityClient({
2000
+ catalogApi,
2001
+ tokenManager
2002
+ });
2003
+ return new Oauth2ProxyAuthProvider({
2004
+ logger,
2005
+ signInResolver,
2006
+ authHandler,
2007
+ tokenIssuer,
2008
+ catalogIdentityClient
2009
+ });
2010
+ };
2027
2011
 
2028
- const MS_IN_S = 1e3;
2029
- class TokenFactory {
2012
+ class OidcAuthProvider {
2030
2013
  constructor(options) {
2031
- this.issuer = options.issuer;
2014
+ this.implementation = this.setupStrategy(options);
2015
+ this.scope = options.scope;
2016
+ this.prompt = options.prompt;
2017
+ this.signInResolver = options.signInResolver;
2018
+ this.authHandler = options.authHandler;
2019
+ this.tokenIssuer = options.tokenIssuer;
2020
+ this.catalogIdentityClient = options.catalogIdentityClient;
2032
2021
  this.logger = options.logger;
2033
- this.keyStore = options.keyStore;
2034
- this.keyDurationSeconds = options.keyDurationSeconds;
2035
- }
2036
- async issueToken(params) {
2037
- const key = await this.getKey();
2038
- const iss = this.issuer;
2039
- const sub = params.claims.sub;
2040
- const ent = params.claims.ent;
2041
- const aud = "backstage";
2042
- const iat = Math.floor(Date.now() / MS_IN_S);
2043
- const exp = iat + this.keyDurationSeconds;
2044
- this.logger.info(`Issuing token for ${sub}, with entities ${ent != null ? ent : []}`);
2045
- return jose.JWS.sign({ iss, sub, aud, iat, exp, ent }, key, {
2046
- alg: key.alg,
2047
- kid: key.kid
2048
- });
2049
2022
  }
2050
- async listPublicKeys() {
2051
- const { items: keys } = await this.keyStore.listKeys();
2052
- const validKeys = [];
2053
- const expiredKeys = [];
2054
- for (const key of keys) {
2055
- const expireAt = luxon.DateTime.fromJSDate(key.createdAt).plus({
2056
- seconds: 3 * this.keyDurationSeconds
2057
- });
2058
- if (expireAt < luxon.DateTime.local()) {
2059
- expiredKeys.push(key);
2060
- } else {
2061
- validKeys.push(key);
2062
- }
2023
+ async start(req) {
2024
+ const { strategy } = await this.implementation;
2025
+ const options = {
2026
+ scope: req.scope || this.scope || "openid profile email",
2027
+ state: encodeState(req.state)
2028
+ };
2029
+ const prompt = this.prompt || "none";
2030
+ if (prompt !== "auto") {
2031
+ options.prompt = prompt;
2063
2032
  }
2064
- if (expiredKeys.length > 0) {
2065
- const kids = expiredKeys.map(({ key }) => key.kid);
2066
- this.logger.info(`Removing expired signing keys, '${kids.join("', '")}'`);
2067
- this.keyStore.removeKeys(kids).catch((error) => {
2068
- this.logger.error(`Failed to remove expired keys, ${error}`);
2069
- });
2033
+ return await executeRedirectStrategy(req, strategy, options);
2034
+ }
2035
+ async handler(req) {
2036
+ const { strategy } = await this.implementation;
2037
+ const { result, privateInfo } = await executeFrameHandlerStrategy(req, strategy);
2038
+ return {
2039
+ response: await this.handleResult(result),
2040
+ refreshToken: privateInfo.refreshToken
2041
+ };
2042
+ }
2043
+ async refresh(req) {
2044
+ const { client } = await this.implementation;
2045
+ const tokenset = await client.refresh(req.refreshToken);
2046
+ if (!tokenset.access_token) {
2047
+ throw new Error("Refresh failed");
2070
2048
  }
2071
- return { keys: validKeys.map(({ key }) => key) };
2049
+ const userinfo = await client.userinfo(tokenset.access_token);
2050
+ return {
2051
+ response: await this.handleResult({ tokenset, userinfo }),
2052
+ refreshToken: tokenset.refresh_token
2053
+ };
2072
2054
  }
2073
- async getKey() {
2074
- if (this.privateKeyPromise) {
2075
- if (this.keyExpiry && luxon.DateTime.fromJSDate(this.keyExpiry) > luxon.DateTime.local()) {
2076
- return this.privateKeyPromise;
2055
+ async setupStrategy(options) {
2056
+ const issuer = await openidClient.Issuer.discover(options.metadataUrl);
2057
+ const client = new issuer.Client({
2058
+ access_type: "offline",
2059
+ client_id: options.clientId,
2060
+ client_secret: options.clientSecret,
2061
+ redirect_uris: [options.callbackUrl],
2062
+ response_types: ["code"],
2063
+ id_token_signed_response_alg: options.tokenSignedResponseAlg || "RS256",
2064
+ scope: options.scope || ""
2065
+ });
2066
+ const strategy = new openidClient.Strategy({
2067
+ client,
2068
+ passReqToCallback: false
2069
+ }, (tokenset, userinfo, done) => {
2070
+ if (typeof done !== "function") {
2071
+ throw new Error("OIDC IdP must provide a userinfo_endpoint in the metadata response");
2077
2072
  }
2078
- this.logger.info(`Signing key has expired, generating new key`);
2079
- delete this.privateKeyPromise;
2080
- }
2081
- this.keyExpiry = luxon.DateTime.utc().plus({
2082
- seconds: this.keyDurationSeconds
2083
- }).toJSDate();
2084
- const promise = (async () => {
2085
- const key = await jose.JWK.generate("EC", "P-256", {
2086
- use: "sig",
2087
- kid: uuid.v4(),
2088
- alg: "ES256"
2073
+ done(void 0, { tokenset, userinfo }, {
2074
+ refreshToken: tokenset.refresh_token
2089
2075
  });
2090
- this.logger.info(`Created new signing key ${key.kid}`);
2091
- await this.keyStore.addKey(key.toJWK(false));
2092
- return key;
2093
- })();
2094
- this.privateKeyPromise = promise;
2095
- try {
2096
- await promise;
2097
- } catch (error) {
2098
- this.logger.error(`Failed to generate new signing key, ${error}`);
2099
- delete this.keyExpiry;
2100
- delete this.privateKeyPromise;
2076
+ });
2077
+ strategy.error = console.error;
2078
+ return { strategy, client };
2079
+ }
2080
+ async handleResult(result) {
2081
+ const context = {
2082
+ logger: this.logger,
2083
+ catalogIdentityClient: this.catalogIdentityClient,
2084
+ tokenIssuer: this.tokenIssuer
2085
+ };
2086
+ const { profile } = await this.authHandler(result, context);
2087
+ const response = {
2088
+ providerInfo: {
2089
+ idToken: result.tokenset.id_token,
2090
+ accessToken: result.tokenset.access_token,
2091
+ scope: result.tokenset.scope,
2092
+ expiresInSeconds: result.tokenset.expires_in
2093
+ },
2094
+ profile
2095
+ };
2096
+ if (this.signInResolver) {
2097
+ response.backstageIdentity = await this.signInResolver({
2098
+ result,
2099
+ profile
2100
+ }, context);
2101
2101
  }
2102
- return promise;
2102
+ return response;
2103
2103
  }
2104
2104
  }
2105
-
2106
- const migrationsDir = backendCommon.resolvePackagePath("@backstage/plugin-auth-backend", "migrations");
2107
- const TABLE = "signing_keys";
2108
- const parseDate = (date) => {
2109
- const parsedDate = typeof date === "string" ? luxon.DateTime.fromSQL(date, { zone: "UTC" }) : luxon.DateTime.fromJSDate(date);
2110
- if (!parsedDate.isValid) {
2111
- throw new Error(`Failed to parse date, reason: ${parsedDate.invalidReason}, explanation: ${parsedDate.invalidExplanation}`);
2105
+ const oAuth2DefaultSignInResolver = async (info, ctx) => {
2106
+ const { profile } = info;
2107
+ if (!profile.email) {
2108
+ throw new Error("Profile contained no email");
2112
2109
  }
2113
- return parsedDate.toJSDate();
2110
+ const userId = profile.email.split("@")[0];
2111
+ const token = await ctx.tokenIssuer.issueToken({
2112
+ claims: {
2113
+ sub: `user:default/${userId}`,
2114
+ ent: [`user:default/${userId}`]
2115
+ }
2116
+ });
2117
+ return { id: userId, token };
2114
2118
  };
2115
- class DatabaseKeyStore {
2116
- static async create(options) {
2117
- const { database } = options;
2118
- await database.migrate.latest({
2119
- directory: migrationsDir
2119
+ const createOidcProvider = (options) => {
2120
+ return ({
2121
+ providerId,
2122
+ globalConfig,
2123
+ config,
2124
+ tokenIssuer,
2125
+ tokenManager,
2126
+ catalogApi,
2127
+ logger
2128
+ }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
2129
+ var _a, _b;
2130
+ const clientId = envConfig.getString("clientId");
2131
+ const clientSecret = envConfig.getString("clientSecret");
2132
+ const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
2133
+ const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
2134
+ const metadataUrl = envConfig.getString("metadataUrl");
2135
+ const tokenSignedResponseAlg = envConfig.getOptionalString("tokenSignedResponseAlg");
2136
+ const scope = envConfig.getOptionalString("scope");
2137
+ const prompt = envConfig.getOptionalString("prompt");
2138
+ const catalogIdentityClient = new CatalogIdentityClient({
2139
+ catalogApi,
2140
+ tokenManager
2120
2141
  });
2121
- return new DatabaseKeyStore(options);
2122
- }
2142
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ userinfo }) => ({
2143
+ profile: {
2144
+ displayName: userinfo.name,
2145
+ email: userinfo.email,
2146
+ picture: userinfo.picture
2147
+ }
2148
+ });
2149
+ const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : oAuth2DefaultSignInResolver;
2150
+ const signInResolver = (info) => signInResolverFn(info, {
2151
+ catalogIdentityClient,
2152
+ tokenIssuer,
2153
+ logger
2154
+ });
2155
+ const provider = new OidcAuthProvider({
2156
+ clientId,
2157
+ clientSecret,
2158
+ callbackUrl,
2159
+ tokenSignedResponseAlg,
2160
+ metadataUrl,
2161
+ scope,
2162
+ prompt,
2163
+ signInResolver,
2164
+ authHandler,
2165
+ logger,
2166
+ tokenIssuer,
2167
+ catalogIdentityClient
2168
+ });
2169
+ return OAuthAdapter.fromConfig(globalConfig, provider, {
2170
+ disableRefresh: false,
2171
+ providerId,
2172
+ tokenIssuer,
2173
+ callbackUrl
2174
+ });
2175
+ });
2176
+ };
2177
+
2178
+ class OktaAuthProvider {
2123
2179
  constructor(options) {
2124
- this.database = options.database;
2180
+ this._store = {
2181
+ store(_req, cb) {
2182
+ cb(null, null);
2183
+ },
2184
+ verify(_req, _state, cb) {
2185
+ cb(null, true);
2186
+ }
2187
+ };
2188
+ this._signInResolver = options.signInResolver;
2189
+ this._authHandler = options.authHandler;
2190
+ this._tokenIssuer = options.tokenIssuer;
2191
+ this._catalogIdentityClient = options.catalogIdentityClient;
2192
+ this._logger = options.logger;
2193
+ this._strategy = new passportOktaOauth.Strategy({
2194
+ clientID: options.clientId,
2195
+ clientSecret: options.clientSecret,
2196
+ callbackURL: options.callbackUrl,
2197
+ audience: options.audience,
2198
+ passReqToCallback: false,
2199
+ store: this._store,
2200
+ response_type: "code"
2201
+ }, (accessToken, refreshToken, params, fullProfile, done) => {
2202
+ done(void 0, {
2203
+ accessToken,
2204
+ refreshToken,
2205
+ params,
2206
+ fullProfile
2207
+ }, {
2208
+ refreshToken
2209
+ });
2210
+ });
2125
2211
  }
2126
- async addKey(key) {
2127
- await this.database(TABLE).insert({
2128
- kid: key.kid,
2129
- key: JSON.stringify(key)
2212
+ async start(req) {
2213
+ return await executeRedirectStrategy(req, this._strategy, {
2214
+ accessType: "offline",
2215
+ prompt: "consent",
2216
+ scope: req.scope,
2217
+ state: encodeState(req.state)
2130
2218
  });
2131
2219
  }
2132
- async listKeys() {
2133
- const rows = await this.database(TABLE).select();
2220
+ async handler(req) {
2221
+ const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
2134
2222
  return {
2135
- items: rows.map((row) => ({
2136
- key: JSON.parse(row.key),
2137
- createdAt: parseDate(row.created_at)
2138
- }))
2223
+ response: await this.handleResult(result),
2224
+ refreshToken: privateInfo.refreshToken
2139
2225
  };
2140
2226
  }
2141
- async removeKeys(kids) {
2142
- await this.database(TABLE).delete().whereIn("kid", kids);
2143
- }
2144
- }
2145
-
2146
- class MemoryKeyStore {
2147
- constructor() {
2148
- this.keys = /* @__PURE__ */ new Map();
2149
- }
2150
- async addKey(key) {
2151
- this.keys.set(key.kid, {
2152
- createdAt: luxon.DateTime.utc().toJSDate(),
2153
- key: JSON.stringify(key)
2154
- });
2155
- }
2156
- async removeKeys(kids) {
2157
- for (const kid of kids) {
2158
- this.keys.delete(kid);
2159
- }
2160
- }
2161
- async listKeys() {
2227
+ async refresh(req) {
2228
+ const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
2229
+ const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
2162
2230
  return {
2163
- items: Array.from(this.keys).map(([, { createdAt, key: keyStr }]) => ({
2164
- createdAt,
2165
- key: JSON.parse(keyStr)
2166
- }))
2231
+ response: await this.handleResult({
2232
+ fullProfile,
2233
+ params,
2234
+ accessToken
2235
+ }),
2236
+ refreshToken
2237
+ };
2238
+ }
2239
+ async handleResult(result) {
2240
+ const context = {
2241
+ logger: this._logger,
2242
+ catalogIdentityClient: this._catalogIdentityClient,
2243
+ tokenIssuer: this._tokenIssuer
2244
+ };
2245
+ const { profile } = await this._authHandler(result, context);
2246
+ const response = {
2247
+ providerInfo: {
2248
+ idToken: result.params.id_token,
2249
+ accessToken: result.accessToken,
2250
+ scope: result.params.scope,
2251
+ expiresInSeconds: result.params.expires_in
2252
+ },
2253
+ profile
2167
2254
  };
2255
+ if (this._signInResolver) {
2256
+ response.backstageIdentity = await this._signInResolver({
2257
+ result,
2258
+ profile
2259
+ }, context);
2260
+ }
2261
+ return response;
2168
2262
  }
2169
2263
  }
2170
-
2171
- const DEFAULT_TIMEOUT_MS = 1e4;
2172
- const DEFAULT_DOCUMENT_PATH = "sessions";
2173
- class FirestoreKeyStore {
2174
- constructor(database, path, timeout) {
2175
- this.database = database;
2176
- this.path = path;
2177
- this.timeout = timeout;
2264
+ const oktaEmailSignInResolver = async (info, ctx) => {
2265
+ const { profile } = info;
2266
+ if (!profile.email) {
2267
+ throw new Error("Okta profile contained no email");
2178
2268
  }
2179
- static async create(settings) {
2180
- const { path, timeout, ...firestoreSettings } = settings != null ? settings : {};
2181
- const database = new firestore.Firestore(firestoreSettings);
2182
- return new FirestoreKeyStore(database, path != null ? path : DEFAULT_DOCUMENT_PATH, timeout != null ? timeout : DEFAULT_TIMEOUT_MS);
2183
- }
2184
- static async verifyConnection(keyStore, logger) {
2185
- try {
2186
- await keyStore.verify();
2187
- } catch (error) {
2188
- if (process.env.NODE_ENV !== "development") {
2189
- throw new Error(`Failed to connect to database: ${error.message}`);
2190
- }
2191
- logger == null ? void 0 : logger.warn(`Failed to connect to database: ${error.message}`);
2192
- }
2193
- }
2194
- async addKey(key) {
2195
- await this.withTimeout(this.database.collection(this.path).doc(key.kid).set({
2196
- kid: key.kid,
2197
- key: JSON.stringify(key)
2198
- }));
2199
- }
2200
- async listKeys() {
2201
- const keys = await this.withTimeout(this.database.collection(this.path).get());
2202
- return {
2203
- items: keys.docs.map((key) => ({
2204
- key: key.data(),
2205
- createdAt: key.createTime.toDate()
2206
- }))
2207
- };
2208
- }
2209
- async removeKeys(kids) {
2210
- for (const kid of kids) {
2211
- await this.withTimeout(this.database.collection(this.path).doc(kid).delete());
2212
- }
2213
- }
2214
- async withTimeout(operation) {
2215
- const timer = new Promise((_, reject) => setTimeout(() => {
2216
- reject(new Error(`Operation timed out after ${this.timeout}ms`));
2217
- }, this.timeout));
2218
- return Promise.race([operation, timer]);
2219
- }
2220
- async verify() {
2221
- await this.withTimeout(this.database.collection(this.path).limit(1).get());
2222
- }
2223
- }
2224
-
2225
- class KeyStores {
2226
- static async fromConfig(config, options) {
2227
- var _a;
2228
- const { logger, database } = options != null ? options : {};
2229
- const ks = config.getOptionalConfig("auth.keyStore");
2230
- const provider = (_a = ks == null ? void 0 : ks.getOptionalString("provider")) != null ? _a : "database";
2231
- logger == null ? void 0 : logger.info(`Configuring "${provider}" as KeyStore provider`);
2232
- if (provider === "database") {
2233
- if (!database) {
2234
- throw new Error("This KeyStore provider requires a database");
2235
- }
2236
- return await DatabaseKeyStore.create({
2237
- database: await database.getClient()
2238
- });
2239
- }
2240
- if (provider === "memory") {
2241
- return new MemoryKeyStore();
2242
- }
2243
- if (provider === "firestore") {
2244
- const settings = ks == null ? void 0 : ks.getConfig(provider);
2245
- const keyStore = await FirestoreKeyStore.create(lodash.pickBy({
2246
- projectId: settings == null ? void 0 : settings.getOptionalString("projectId"),
2247
- keyFilename: settings == null ? void 0 : settings.getOptionalString("keyFilename"),
2248
- host: settings == null ? void 0 : settings.getOptionalString("host"),
2249
- port: settings == null ? void 0 : settings.getOptionalNumber("port"),
2250
- ssl: settings == null ? void 0 : settings.getOptionalBoolean("ssl"),
2251
- path: settings == null ? void 0 : settings.getOptionalString("path"),
2252
- timeout: settings == null ? void 0 : settings.getOptionalNumber("timeout")
2253
- }, (value) => value !== void 0));
2254
- await FirestoreKeyStore.verifyConnection(keyStore, logger);
2255
- return keyStore;
2256
- }
2257
- throw new Error(`Unknown KeyStore provider: ${provider}`);
2258
- }
2259
- }
2260
-
2261
- const OAUTH2_PROXY_JWT_HEADER = "X-OAUTH2-PROXY-ID-TOKEN";
2262
- class Oauth2ProxyAuthProvider {
2263
- constructor(options) {
2264
- this.catalogIdentityClient = options.catalogIdentityClient;
2265
- this.logger = options.logger;
2266
- this.tokenIssuer = options.tokenIssuer;
2267
- this.signInResolver = options.signInResolver;
2268
- this.authHandler = options.authHandler;
2269
- }
2270
- frameHandler() {
2271
- return Promise.resolve(void 0);
2272
- }
2273
- async refresh(req, res) {
2274
- try {
2275
- const result = this.getResult(req);
2276
- const response = await this.handleResult(result);
2277
- res.json(response);
2278
- } catch (e) {
2279
- this.logger.error(`Exception occurred during ${OAUTH2_PROXY_JWT_HEADER} refresh`, e);
2280
- res.status(401);
2281
- res.end();
2282
- }
2283
- }
2284
- start() {
2285
- return Promise.resolve(void 0);
2286
- }
2287
- async handleResult(result) {
2288
- const ctx = {
2289
- logger: this.logger,
2290
- tokenIssuer: this.tokenIssuer,
2291
- catalogIdentityClient: this.catalogIdentityClient
2292
- };
2293
- const { profile } = await this.authHandler(result, ctx);
2294
- const backstageSignInResult = await this.signInResolver({
2295
- result,
2296
- profile
2297
- }, ctx);
2298
- return {
2299
- providerInfo: {
2300
- accessToken: result.accessToken
2301
- },
2302
- backstageIdentity: prepareBackstageIdentityResponse(backstageSignInResult),
2303
- profile
2304
- };
2305
- }
2306
- getResult(req) {
2307
- const authHeader = req.header(OAUTH2_PROXY_JWT_HEADER);
2308
- const jwt = IdentityClient.getBearerToken(authHeader);
2309
- if (!jwt) {
2310
- throw new errors.AuthenticationError(`Missing or in incorrect format - Oauth2Proxy OIDC header: ${OAUTH2_PROXY_JWT_HEADER}`);
2269
+ const entity = await ctx.catalogIdentityClient.findUser({
2270
+ annotations: {
2271
+ "okta.com/email": profile.email
2311
2272
  }
2312
- const decodedJWT = jose.JWT.decode(jwt);
2313
- return {
2314
- fullProfile: decodedJWT,
2315
- accessToken: jwt
2316
- };
2273
+ });
2274
+ const claims = getEntityClaims(entity);
2275
+ const token = await ctx.tokenIssuer.issueToken({ claims });
2276
+ return { id: entity.metadata.name, entity, token };
2277
+ };
2278
+ const oktaDefaultSignInResolver = async (info, ctx) => {
2279
+ const { profile } = info;
2280
+ if (!profile.email) {
2281
+ throw new Error("Okta profile contained no email");
2317
2282
  }
2318
- }
2319
- const createOauth2ProxyProvider = (options) => ({ catalogApi, logger, tokenIssuer, tokenManager }) => {
2320
- const signInResolver = options.signIn.resolver;
2321
- const authHandler = options.authHandler;
2322
- const catalogIdentityClient = new CatalogIdentityClient({
2323
- catalogApi,
2324
- tokenManager
2283
+ const userId = profile.email.split("@")[0];
2284
+ const token = await ctx.tokenIssuer.issueToken({
2285
+ claims: { sub: `user:default/${userId}`, ent: [`user:default/${userId}`] }
2325
2286
  });
2326
- return new Oauth2ProxyAuthProvider({
2327
- logger,
2328
- signInResolver,
2329
- authHandler,
2287
+ return { id: userId, token };
2288
+ };
2289
+ const createOktaProvider = (_options) => {
2290
+ return ({
2291
+ providerId,
2292
+ globalConfig,
2293
+ config,
2330
2294
  tokenIssuer,
2331
- catalogIdentityClient
2295
+ tokenManager,
2296
+ catalogApi,
2297
+ logger
2298
+ }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
2299
+ var _a, _b;
2300
+ const clientId = envConfig.getString("clientId");
2301
+ const clientSecret = envConfig.getString("clientSecret");
2302
+ const audience = envConfig.getString("audience");
2303
+ const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
2304
+ const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
2305
+ if (!audience.startsWith("https://")) {
2306
+ throw new Error("URL for 'audience' must start with 'https://'.");
2307
+ }
2308
+ const catalogIdentityClient = new CatalogIdentityClient({
2309
+ catalogApi,
2310
+ tokenManager
2311
+ });
2312
+ const authHandler = (_options == null ? void 0 : _options.authHandler) ? _options.authHandler : async ({ fullProfile, params }) => ({
2313
+ profile: makeProfileInfo(fullProfile, params.id_token)
2314
+ });
2315
+ const signInResolverFn = (_b = (_a = _options == null ? void 0 : _options.signIn) == null ? void 0 : _a.resolver) != null ? _b : oktaDefaultSignInResolver;
2316
+ const signInResolver = (info) => signInResolverFn(info, {
2317
+ catalogIdentityClient,
2318
+ tokenIssuer,
2319
+ logger
2320
+ });
2321
+ const provider = new OktaAuthProvider({
2322
+ audience,
2323
+ clientId,
2324
+ clientSecret,
2325
+ callbackUrl,
2326
+ authHandler,
2327
+ signInResolver,
2328
+ tokenIssuer,
2329
+ catalogIdentityClient,
2330
+ logger
2331
+ });
2332
+ return OAuthAdapter.fromConfig(globalConfig, provider, {
2333
+ disableRefresh: false,
2334
+ providerId,
2335
+ tokenIssuer,
2336
+ callbackUrl
2337
+ });
2332
2338
  });
2333
2339
  };
2334
2340
 
2335
- class OidcAuthProvider {
2341
+ class OneLoginProvider {
2336
2342
  constructor(options) {
2337
- this.implementation = this.setupStrategy(options);
2338
- this.scope = options.scope;
2339
- this.prompt = options.prompt;
2340
2343
  this.signInResolver = options.signInResolver;
2341
2344
  this.authHandler = options.authHandler;
2342
2345
  this.tokenIssuer = options.tokenIssuer;
2343
2346
  this.catalogIdentityClient = options.catalogIdentityClient;
2344
2347
  this.logger = options.logger;
2348
+ this._strategy = new passportOneloginOauth.Strategy({
2349
+ issuer: options.issuer,
2350
+ clientID: options.clientId,
2351
+ clientSecret: options.clientSecret,
2352
+ callbackURL: options.callbackUrl,
2353
+ passReqToCallback: false
2354
+ }, (accessToken, refreshToken, params, fullProfile, done) => {
2355
+ done(void 0, {
2356
+ accessToken,
2357
+ refreshToken,
2358
+ params,
2359
+ fullProfile
2360
+ }, {
2361
+ refreshToken
2362
+ });
2363
+ });
2345
2364
  }
2346
2365
  async start(req) {
2347
- const { strategy } = await this.implementation;
2348
- const options = {
2349
- scope: req.scope || this.scope || "openid profile email",
2366
+ return await executeRedirectStrategy(req, this._strategy, {
2367
+ accessType: "offline",
2368
+ prompt: "consent",
2369
+ scope: "openid",
2350
2370
  state: encodeState(req.state)
2351
- };
2352
- const prompt = this.prompt || "none";
2353
- if (prompt !== "auto") {
2354
- options.prompt = prompt;
2355
- }
2356
- return await executeRedirectStrategy(req, strategy, options);
2371
+ });
2357
2372
  }
2358
2373
  async handler(req) {
2359
- const { strategy } = await this.implementation;
2360
- const { result, privateInfo } = await executeFrameHandlerStrategy(req, strategy);
2361
- return {
2362
- response: await this.handleResult(result),
2363
- refreshToken: privateInfo.refreshToken
2364
- };
2365
- }
2366
- async refresh(req) {
2367
- const { client } = await this.implementation;
2368
- const tokenset = await client.refresh(req.refreshToken);
2369
- if (!tokenset.access_token) {
2370
- throw new Error("Refresh failed");
2371
- }
2372
- const userinfo = await client.userinfo(tokenset.access_token);
2373
- return {
2374
- response: await this.handleResult({ tokenset, userinfo }),
2375
- refreshToken: tokenset.refresh_token
2376
- };
2377
- }
2378
- async setupStrategy(options) {
2379
- const issuer = await openidClient.Issuer.discover(options.metadataUrl);
2380
- const client = new issuer.Client({
2381
- access_type: "offline",
2382
- client_id: options.clientId,
2383
- client_secret: options.clientSecret,
2384
- redirect_uris: [options.callbackUrl],
2385
- response_types: ["code"],
2386
- id_token_signed_response_alg: options.tokenSignedResponseAlg || "RS256",
2387
- scope: options.scope || ""
2388
- });
2389
- const strategy = new openidClient.Strategy({
2390
- client,
2391
- passReqToCallback: false
2392
- }, (tokenset, userinfo, done) => {
2393
- if (typeof done !== "function") {
2394
- throw new Error("OIDC IdP must provide a userinfo_endpoint in the metadata response");
2395
- }
2396
- done(void 0, { tokenset, userinfo }, {
2397
- refreshToken: tokenset.refresh_token
2398
- });
2399
- });
2400
- strategy.error = console.error;
2401
- return { strategy, client };
2402
- }
2403
- async handleResult(result) {
2404
- const context = {
2405
- logger: this.logger,
2406
- catalogIdentityClient: this.catalogIdentityClient,
2407
- tokenIssuer: this.tokenIssuer
2408
- };
2409
- const { profile } = await this.authHandler(result, context);
2410
- const response = {
2411
- providerInfo: {
2412
- idToken: result.tokenset.id_token,
2413
- accessToken: result.tokenset.access_token,
2414
- scope: result.tokenset.scope,
2415
- expiresInSeconds: result.tokenset.expires_in
2416
- },
2417
- profile
2418
- };
2419
- if (this.signInResolver) {
2420
- response.backstageIdentity = await this.signInResolver({
2421
- result,
2422
- profile
2423
- }, context);
2424
- }
2425
- return response;
2426
- }
2427
- }
2428
- const oAuth2DefaultSignInResolver = async (info, ctx) => {
2429
- const { profile } = info;
2430
- if (!profile.email) {
2431
- throw new Error("Profile contained no email");
2432
- }
2433
- const userId = profile.email.split("@")[0];
2434
- const token = await ctx.tokenIssuer.issueToken({
2435
- claims: {
2436
- sub: `user:default/${userId}`,
2437
- ent: [`user:default/${userId}`]
2438
- }
2439
- });
2440
- return { id: userId, token };
2441
- };
2442
- const createOidcProvider = (options) => {
2443
- return ({
2444
- providerId,
2445
- globalConfig,
2446
- config,
2447
- tokenIssuer,
2448
- tokenManager,
2449
- catalogApi,
2450
- logger
2451
- }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
2452
- var _a, _b;
2453
- const clientId = envConfig.getString("clientId");
2454
- const clientSecret = envConfig.getString("clientSecret");
2455
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
2456
- const metadataUrl = envConfig.getString("metadataUrl");
2457
- const tokenSignedResponseAlg = envConfig.getOptionalString("tokenSignedResponseAlg");
2458
- const scope = envConfig.getOptionalString("scope");
2459
- const prompt = envConfig.getOptionalString("prompt");
2460
- const catalogIdentityClient = new CatalogIdentityClient({
2461
- catalogApi,
2462
- tokenManager
2463
- });
2464
- const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ userinfo }) => ({
2465
- profile: {
2466
- displayName: userinfo.name,
2467
- email: userinfo.email,
2468
- picture: userinfo.picture
2469
- }
2470
- });
2471
- const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : oAuth2DefaultSignInResolver;
2472
- const signInResolver = (info) => signInResolverFn(info, {
2473
- catalogIdentityClient,
2474
- tokenIssuer,
2475
- logger
2476
- });
2477
- const provider = new OidcAuthProvider({
2478
- clientId,
2479
- clientSecret,
2480
- callbackUrl,
2481
- tokenSignedResponseAlg,
2482
- metadataUrl,
2483
- scope,
2484
- prompt,
2485
- signInResolver,
2486
- authHandler,
2487
- logger,
2488
- tokenIssuer,
2489
- catalogIdentityClient
2490
- });
2491
- return OAuthAdapter.fromConfig(globalConfig, provider, {
2492
- disableRefresh: false,
2493
- providerId,
2494
- tokenIssuer
2495
- });
2496
- });
2497
- };
2498
-
2499
- class OktaAuthProvider {
2500
- constructor(options) {
2501
- this._store = {
2502
- store(_req, cb) {
2503
- cb(null, null);
2504
- },
2505
- verify(_req, _state, cb) {
2506
- cb(null, true);
2507
- }
2508
- };
2509
- this._signInResolver = options.signInResolver;
2510
- this._authHandler = options.authHandler;
2511
- this._tokenIssuer = options.tokenIssuer;
2512
- this._catalogIdentityClient = options.catalogIdentityClient;
2513
- this._logger = options.logger;
2514
- this._strategy = new passportOktaOauth.Strategy({
2515
- clientID: options.clientId,
2516
- clientSecret: options.clientSecret,
2517
- callbackURL: options.callbackUrl,
2518
- audience: options.audience,
2519
- passReqToCallback: false,
2520
- store: this._store,
2521
- response_type: "code"
2522
- }, (accessToken, refreshToken, params, fullProfile, done) => {
2523
- done(void 0, {
2524
- accessToken,
2525
- refreshToken,
2526
- params,
2527
- fullProfile
2528
- }, {
2529
- refreshToken
2530
- });
2531
- });
2532
- }
2533
- async start(req) {
2534
- return await executeRedirectStrategy(req, this._strategy, {
2535
- accessType: "offline",
2536
- prompt: "consent",
2537
- scope: req.scope,
2538
- state: encodeState(req.state)
2539
- });
2540
- }
2541
- async handler(req) {
2542
- const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
2374
+ const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
2543
2375
  return {
2544
2376
  response: await this.handleResult(result),
2545
2377
  refreshToken: privateInfo.refreshToken
@@ -2559,11 +2391,11 @@ class OktaAuthProvider {
2559
2391
  }
2560
2392
  async handleResult(result) {
2561
2393
  const context = {
2562
- logger: this._logger,
2563
- catalogIdentityClient: this._catalogIdentityClient,
2564
- tokenIssuer: this._tokenIssuer
2394
+ logger: this.logger,
2395
+ catalogIdentityClient: this.catalogIdentityClient,
2396
+ tokenIssuer: this.tokenIssuer
2565
2397
  };
2566
- const { profile } = await this._authHandler(result, context);
2398
+ const { profile } = await this.authHandler(result, context);
2567
2399
  const response = {
2568
2400
  providerInfo: {
2569
2401
  idToken: result.params.id_token,
@@ -2573,8 +2405,8 @@ class OktaAuthProvider {
2573
2405
  },
2574
2406
  profile
2575
2407
  };
2576
- if (this._signInResolver) {
2577
- response.backstageIdentity = await this._signInResolver({
2408
+ if (this.signInResolver) {
2409
+ response.backstageIdentity = await this.signInResolver({
2578
2410
  result,
2579
2411
  profile
2580
2412
  }, context);
@@ -2582,32 +2414,15 @@ class OktaAuthProvider {
2582
2414
  return response;
2583
2415
  }
2584
2416
  }
2585
- const oktaEmailSignInResolver = async (info, ctx) => {
2586
- const { profile } = info;
2587
- if (!profile.email) {
2588
- throw new Error("Okta profile contained no email");
2589
- }
2590
- const entity = await ctx.catalogIdentityClient.findUser({
2591
- annotations: {
2592
- "okta.com/email": profile.email
2593
- }
2594
- });
2595
- const claims = getEntityClaims(entity);
2596
- const token = await ctx.tokenIssuer.issueToken({ claims });
2597
- return { id: entity.metadata.name, entity, token };
2598
- };
2599
- const oktaDefaultSignInResolver = async (info, ctx) => {
2417
+ const defaultSignInResolver = async (info) => {
2600
2418
  const { profile } = info;
2601
2419
  if (!profile.email) {
2602
- throw new Error("Okta profile contained no email");
2420
+ throw new Error("OIDC profile contained no email");
2603
2421
  }
2604
- const userId = profile.email.split("@")[0];
2605
- const token = await ctx.tokenIssuer.issueToken({
2606
- claims: { sub: `user:default/${userId}`, ent: [`user:default/${userId}`] }
2607
- });
2608
- return { id: userId, token };
2422
+ const id = profile.email.split("@")[0];
2423
+ return { id, token: "" };
2609
2424
  };
2610
- const createOktaProvider = (_options) => {
2425
+ const createOneLoginProvider = (options) => {
2611
2426
  return ({
2612
2427
  providerId,
2613
2428
  globalConfig,
@@ -2620,29 +2435,22 @@ const createOktaProvider = (_options) => {
2620
2435
  var _a, _b;
2621
2436
  const clientId = envConfig.getString("clientId");
2622
2437
  const clientSecret = envConfig.getString("clientSecret");
2623
- const audience = envConfig.getString("audience");
2624
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
2625
- if (!audience.startsWith("https://")) {
2626
- throw new Error("URL for 'audience' must start with 'https://'.");
2627
- }
2438
+ const issuer = envConfig.getString("issuer");
2439
+ const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
2440
+ const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
2628
2441
  const catalogIdentityClient = new CatalogIdentityClient({
2629
2442
  catalogApi,
2630
2443
  tokenManager
2631
2444
  });
2632
- const authHandler = (_options == null ? void 0 : _options.authHandler) ? _options.authHandler : async ({ fullProfile, params }) => ({
2445
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
2633
2446
  profile: makeProfileInfo(fullProfile, params.id_token)
2634
2447
  });
2635
- const signInResolverFn = (_b = (_a = _options == null ? void 0 : _options.signIn) == null ? void 0 : _a.resolver) != null ? _b : oktaDefaultSignInResolver;
2636
- const signInResolver = (info) => signInResolverFn(info, {
2637
- catalogIdentityClient,
2638
- tokenIssuer,
2639
- logger
2640
- });
2641
- const provider = new OktaAuthProvider({
2642
- audience,
2448
+ const signInResolver = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : defaultSignInResolver;
2449
+ const provider = new OneLoginProvider({
2643
2450
  clientId,
2644
2451
  clientSecret,
2645
2452
  callbackUrl,
2453
+ issuer,
2646
2454
  authHandler,
2647
2455
  signInResolver,
2648
2456
  tokenIssuer,
@@ -2652,147 +2460,22 @@ const createOktaProvider = (_options) => {
2652
2460
  return OAuthAdapter.fromConfig(globalConfig, provider, {
2653
2461
  disableRefresh: false,
2654
2462
  providerId,
2655
- tokenIssuer
2463
+ tokenIssuer,
2464
+ callbackUrl
2656
2465
  });
2657
2466
  });
2658
2467
  };
2659
2468
 
2660
- class OneLoginProvider {
2469
+ class SamlAuthProvider {
2661
2470
  constructor(options) {
2471
+ this.appUrl = options.appUrl;
2662
2472
  this.signInResolver = options.signInResolver;
2663
2473
  this.authHandler = options.authHandler;
2664
2474
  this.tokenIssuer = options.tokenIssuer;
2665
2475
  this.catalogIdentityClient = options.catalogIdentityClient;
2666
2476
  this.logger = options.logger;
2667
- this._strategy = new passportOneloginOauth.Strategy({
2668
- issuer: options.issuer,
2669
- clientID: options.clientId,
2670
- clientSecret: options.clientSecret,
2671
- callbackURL: options.callbackUrl,
2672
- passReqToCallback: false
2673
- }, (accessToken, refreshToken, params, fullProfile, done) => {
2674
- done(void 0, {
2675
- accessToken,
2676
- refreshToken,
2677
- params,
2678
- fullProfile
2679
- }, {
2680
- refreshToken
2681
- });
2682
- });
2683
- }
2684
- async start(req) {
2685
- return await executeRedirectStrategy(req, this._strategy, {
2686
- accessType: "offline",
2687
- prompt: "consent",
2688
- scope: "openid",
2689
- state: encodeState(req.state)
2690
- });
2691
- }
2692
- async handler(req) {
2693
- const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy);
2694
- return {
2695
- response: await this.handleResult(result),
2696
- refreshToken: privateInfo.refreshToken
2697
- };
2698
- }
2699
- async refresh(req) {
2700
- const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
2701
- const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
2702
- return {
2703
- response: await this.handleResult({
2704
- fullProfile,
2705
- params,
2706
- accessToken
2707
- }),
2708
- refreshToken
2709
- };
2710
- }
2711
- async handleResult(result) {
2712
- const context = {
2713
- logger: this.logger,
2714
- catalogIdentityClient: this.catalogIdentityClient,
2715
- tokenIssuer: this.tokenIssuer
2716
- };
2717
- const { profile } = await this.authHandler(result, context);
2718
- const response = {
2719
- providerInfo: {
2720
- idToken: result.params.id_token,
2721
- accessToken: result.accessToken,
2722
- scope: result.params.scope,
2723
- expiresInSeconds: result.params.expires_in
2724
- },
2725
- profile
2726
- };
2727
- if (this.signInResolver) {
2728
- response.backstageIdentity = await this.signInResolver({
2729
- result,
2730
- profile
2731
- }, context);
2732
- }
2733
- return response;
2734
- }
2735
- }
2736
- const defaultSignInResolver = async (info) => {
2737
- const { profile } = info;
2738
- if (!profile.email) {
2739
- throw new Error("OIDC profile contained no email");
2740
- }
2741
- const id = profile.email.split("@")[0];
2742
- return { id, token: "" };
2743
- };
2744
- const createOneLoginProvider = (options) => {
2745
- return ({
2746
- providerId,
2747
- globalConfig,
2748
- config,
2749
- tokenIssuer,
2750
- tokenManager,
2751
- catalogApi,
2752
- logger
2753
- }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
2754
- var _a, _b;
2755
- const clientId = envConfig.getString("clientId");
2756
- const clientSecret = envConfig.getString("clientSecret");
2757
- const issuer = envConfig.getString("issuer");
2758
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
2759
- const catalogIdentityClient = new CatalogIdentityClient({
2760
- catalogApi,
2761
- tokenManager
2762
- });
2763
- const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
2764
- profile: makeProfileInfo(fullProfile, params.id_token)
2765
- });
2766
- const signInResolver = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : defaultSignInResolver;
2767
- const provider = new OneLoginProvider({
2768
- clientId,
2769
- clientSecret,
2770
- callbackUrl,
2771
- issuer,
2772
- authHandler,
2773
- signInResolver,
2774
- tokenIssuer,
2775
- catalogIdentityClient,
2776
- logger
2777
- });
2778
- return OAuthAdapter.fromConfig(globalConfig, provider, {
2779
- disableRefresh: false,
2780
- providerId,
2781
- tokenIssuer
2782
- });
2783
- });
2784
- };
2785
-
2786
- class SamlAuthProvider {
2787
- constructor(options) {
2788
- this.appUrl = options.appUrl;
2789
- this.signInResolver = options.signInResolver;
2790
- this.authHandler = options.authHandler;
2791
- this.tokenIssuer = options.tokenIssuer;
2792
- this.catalogIdentityClient = options.catalogIdentityClient;
2793
- this.logger = options.logger;
2794
- this.strategy = new passportSaml.Strategy({ ...options }, (fullProfile, done) => {
2795
- done(void 0, { fullProfile });
2477
+ this.strategy = new passportSaml.Strategy({ ...options }, (fullProfile, done) => {
2478
+ done(void 0, { fullProfile });
2796
2479
  });
2797
2480
  }
2798
2481
  async start(req, res) {
@@ -3000,6 +2683,271 @@ const factories = {
3000
2683
  atlassian: createAtlassianProvider()
3001
2684
  };
3002
2685
 
2686
+ function createOidcRouter(options) {
2687
+ const { baseUrl, tokenIssuer } = options;
2688
+ const router = Router__default["default"]();
2689
+ const config = {
2690
+ issuer: baseUrl,
2691
+ token_endpoint: `${baseUrl}/v1/token`,
2692
+ userinfo_endpoint: `${baseUrl}/v1/userinfo`,
2693
+ jwks_uri: `${baseUrl}/.well-known/jwks.json`,
2694
+ response_types_supported: ["id_token"],
2695
+ subject_types_supported: ["public"],
2696
+ id_token_signing_alg_values_supported: ["RS256"],
2697
+ scopes_supported: ["openid"],
2698
+ token_endpoint_auth_methods_supported: [],
2699
+ claims_supported: ["sub"],
2700
+ grant_types_supported: []
2701
+ };
2702
+ router.get("/.well-known/openid-configuration", (_req, res) => {
2703
+ res.json(config);
2704
+ });
2705
+ router.get("/.well-known/jwks.json", async (_req, res) => {
2706
+ const { keys } = await tokenIssuer.listPublicKeys();
2707
+ res.json({ keys });
2708
+ });
2709
+ router.get("/v1/token", (_req, res) => {
2710
+ res.status(501).send("Not Implemented");
2711
+ });
2712
+ router.get("/v1/userinfo", (_req, res) => {
2713
+ res.status(501).send("Not Implemented");
2714
+ });
2715
+ return router;
2716
+ }
2717
+
2718
+ const MS_IN_S = 1e3;
2719
+ class TokenFactory {
2720
+ constructor(options) {
2721
+ this.issuer = options.issuer;
2722
+ this.logger = options.logger;
2723
+ this.keyStore = options.keyStore;
2724
+ this.keyDurationSeconds = options.keyDurationSeconds;
2725
+ }
2726
+ async issueToken(params) {
2727
+ const key = await this.getKey();
2728
+ const iss = this.issuer;
2729
+ const sub = params.claims.sub;
2730
+ const ent = params.claims.ent;
2731
+ const aud = "backstage";
2732
+ const iat = Math.floor(Date.now() / MS_IN_S);
2733
+ const exp = iat + this.keyDurationSeconds;
2734
+ this.logger.info(`Issuing token for ${sub}, with entities ${ent != null ? ent : []}`);
2735
+ return jose.JWS.sign({ iss, sub, aud, iat, exp, ent }, key, {
2736
+ alg: key.alg,
2737
+ kid: key.kid
2738
+ });
2739
+ }
2740
+ async listPublicKeys() {
2741
+ const { items: keys } = await this.keyStore.listKeys();
2742
+ const validKeys = [];
2743
+ const expiredKeys = [];
2744
+ for (const key of keys) {
2745
+ const expireAt = luxon.DateTime.fromJSDate(key.createdAt).plus({
2746
+ seconds: 3 * this.keyDurationSeconds
2747
+ });
2748
+ if (expireAt < luxon.DateTime.local()) {
2749
+ expiredKeys.push(key);
2750
+ } else {
2751
+ validKeys.push(key);
2752
+ }
2753
+ }
2754
+ if (expiredKeys.length > 0) {
2755
+ const kids = expiredKeys.map(({ key }) => key.kid);
2756
+ this.logger.info(`Removing expired signing keys, '${kids.join("', '")}'`);
2757
+ this.keyStore.removeKeys(kids).catch((error) => {
2758
+ this.logger.error(`Failed to remove expired keys, ${error}`);
2759
+ });
2760
+ }
2761
+ return { keys: validKeys.map(({ key }) => key) };
2762
+ }
2763
+ async getKey() {
2764
+ if (this.privateKeyPromise) {
2765
+ if (this.keyExpiry && luxon.DateTime.fromJSDate(this.keyExpiry) > luxon.DateTime.local()) {
2766
+ return this.privateKeyPromise;
2767
+ }
2768
+ this.logger.info(`Signing key has expired, generating new key`);
2769
+ delete this.privateKeyPromise;
2770
+ }
2771
+ this.keyExpiry = luxon.DateTime.utc().plus({
2772
+ seconds: this.keyDurationSeconds
2773
+ }).toJSDate();
2774
+ const promise = (async () => {
2775
+ const key = await jose.JWK.generate("EC", "P-256", {
2776
+ use: "sig",
2777
+ kid: uuid.v4(),
2778
+ alg: "ES256"
2779
+ });
2780
+ this.logger.info(`Created new signing key ${key.kid}`);
2781
+ await this.keyStore.addKey(key.toJWK(false));
2782
+ return key;
2783
+ })();
2784
+ this.privateKeyPromise = promise;
2785
+ try {
2786
+ await promise;
2787
+ } catch (error) {
2788
+ this.logger.error(`Failed to generate new signing key, ${error}`);
2789
+ delete this.keyExpiry;
2790
+ delete this.privateKeyPromise;
2791
+ }
2792
+ return promise;
2793
+ }
2794
+ }
2795
+
2796
+ const migrationsDir = backendCommon.resolvePackagePath("@backstage/plugin-auth-backend", "migrations");
2797
+ const TABLE = "signing_keys";
2798
+ const parseDate = (date) => {
2799
+ const parsedDate = typeof date === "string" ? luxon.DateTime.fromSQL(date, { zone: "UTC" }) : luxon.DateTime.fromJSDate(date);
2800
+ if (!parsedDate.isValid) {
2801
+ throw new Error(`Failed to parse date, reason: ${parsedDate.invalidReason}, explanation: ${parsedDate.invalidExplanation}`);
2802
+ }
2803
+ return parsedDate.toJSDate();
2804
+ };
2805
+ class DatabaseKeyStore {
2806
+ static async create(options) {
2807
+ const { database } = options;
2808
+ await database.migrate.latest({
2809
+ directory: migrationsDir
2810
+ });
2811
+ return new DatabaseKeyStore(options);
2812
+ }
2813
+ constructor(options) {
2814
+ this.database = options.database;
2815
+ }
2816
+ async addKey(key) {
2817
+ await this.database(TABLE).insert({
2818
+ kid: key.kid,
2819
+ key: JSON.stringify(key)
2820
+ });
2821
+ }
2822
+ async listKeys() {
2823
+ const rows = await this.database(TABLE).select();
2824
+ return {
2825
+ items: rows.map((row) => ({
2826
+ key: JSON.parse(row.key),
2827
+ createdAt: parseDate(row.created_at)
2828
+ }))
2829
+ };
2830
+ }
2831
+ async removeKeys(kids) {
2832
+ await this.database(TABLE).delete().whereIn("kid", kids);
2833
+ }
2834
+ }
2835
+
2836
+ class MemoryKeyStore {
2837
+ constructor() {
2838
+ this.keys = /* @__PURE__ */ new Map();
2839
+ }
2840
+ async addKey(key) {
2841
+ this.keys.set(key.kid, {
2842
+ createdAt: luxon.DateTime.utc().toJSDate(),
2843
+ key: JSON.stringify(key)
2844
+ });
2845
+ }
2846
+ async removeKeys(kids) {
2847
+ for (const kid of kids) {
2848
+ this.keys.delete(kid);
2849
+ }
2850
+ }
2851
+ async listKeys() {
2852
+ return {
2853
+ items: Array.from(this.keys).map(([, { createdAt, key: keyStr }]) => ({
2854
+ createdAt,
2855
+ key: JSON.parse(keyStr)
2856
+ }))
2857
+ };
2858
+ }
2859
+ }
2860
+
2861
+ const DEFAULT_TIMEOUT_MS = 1e4;
2862
+ const DEFAULT_DOCUMENT_PATH = "sessions";
2863
+ class FirestoreKeyStore {
2864
+ constructor(database, path, timeout) {
2865
+ this.database = database;
2866
+ this.path = path;
2867
+ this.timeout = timeout;
2868
+ }
2869
+ static async create(settings) {
2870
+ const { path, timeout, ...firestoreSettings } = settings != null ? settings : {};
2871
+ const database = new firestore.Firestore(firestoreSettings);
2872
+ return new FirestoreKeyStore(database, path != null ? path : DEFAULT_DOCUMENT_PATH, timeout != null ? timeout : DEFAULT_TIMEOUT_MS);
2873
+ }
2874
+ static async verifyConnection(keyStore, logger) {
2875
+ try {
2876
+ await keyStore.verify();
2877
+ } catch (error) {
2878
+ if (process.env.NODE_ENV !== "development") {
2879
+ throw new Error(`Failed to connect to database: ${error.message}`);
2880
+ }
2881
+ logger == null ? void 0 : logger.warn(`Failed to connect to database: ${error.message}`);
2882
+ }
2883
+ }
2884
+ async addKey(key) {
2885
+ await this.withTimeout(this.database.collection(this.path).doc(key.kid).set({
2886
+ kid: key.kid,
2887
+ key: JSON.stringify(key)
2888
+ }));
2889
+ }
2890
+ async listKeys() {
2891
+ const keys = await this.withTimeout(this.database.collection(this.path).get());
2892
+ return {
2893
+ items: keys.docs.map((key) => ({
2894
+ key: key.data(),
2895
+ createdAt: key.createTime.toDate()
2896
+ }))
2897
+ };
2898
+ }
2899
+ async removeKeys(kids) {
2900
+ for (const kid of kids) {
2901
+ await this.withTimeout(this.database.collection(this.path).doc(kid).delete());
2902
+ }
2903
+ }
2904
+ async withTimeout(operation) {
2905
+ const timer = new Promise((_, reject) => setTimeout(() => {
2906
+ reject(new Error(`Operation timed out after ${this.timeout}ms`));
2907
+ }, this.timeout));
2908
+ return Promise.race([operation, timer]);
2909
+ }
2910
+ async verify() {
2911
+ await this.withTimeout(this.database.collection(this.path).limit(1).get());
2912
+ }
2913
+ }
2914
+
2915
+ class KeyStores {
2916
+ static async fromConfig(config, options) {
2917
+ var _a;
2918
+ const { logger, database } = options != null ? options : {};
2919
+ const ks = config.getOptionalConfig("auth.keyStore");
2920
+ const provider = (_a = ks == null ? void 0 : ks.getOptionalString("provider")) != null ? _a : "database";
2921
+ logger == null ? void 0 : logger.info(`Configuring "${provider}" as KeyStore provider`);
2922
+ if (provider === "database") {
2923
+ if (!database) {
2924
+ throw new Error("This KeyStore provider requires a database");
2925
+ }
2926
+ return await DatabaseKeyStore.create({
2927
+ database: await database.getClient()
2928
+ });
2929
+ }
2930
+ if (provider === "memory") {
2931
+ return new MemoryKeyStore();
2932
+ }
2933
+ if (provider === "firestore") {
2934
+ const settings = ks == null ? void 0 : ks.getConfig(provider);
2935
+ const keyStore = await FirestoreKeyStore.create(lodash.pickBy({
2936
+ projectId: settings == null ? void 0 : settings.getOptionalString("projectId"),
2937
+ keyFilename: settings == null ? void 0 : settings.getOptionalString("keyFilename"),
2938
+ host: settings == null ? void 0 : settings.getOptionalString("host"),
2939
+ port: settings == null ? void 0 : settings.getOptionalNumber("port"),
2940
+ ssl: settings == null ? void 0 : settings.getOptionalBoolean("ssl"),
2941
+ path: settings == null ? void 0 : settings.getOptionalString("path"),
2942
+ timeout: settings == null ? void 0 : settings.getOptionalNumber("timeout")
2943
+ }, (value) => value !== void 0));
2944
+ await FirestoreKeyStore.verifyConnection(keyStore, logger);
2945
+ return keyStore;
2946
+ }
2947
+ throw new Error(`Unknown KeyStore provider: ${provider}`);
2948
+ }
2949
+ }
2950
+
3003
2951
  async function createRouter(options) {
3004
2952
  const {
3005
2953
  logger,
@@ -3051,7 +2999,11 @@ async function createRouter(options) {
3051
2999
  try {
3052
3000
  const provider = providerFactory({
3053
3001
  providerId,
3054
- globalConfig: { baseUrl: authUrl, appUrl, isOriginAllowed },
3002
+ globalConfig: {
3003
+ baseUrl: authUrl,
3004
+ appUrl,
3005
+ isOriginAllowed
3006
+ },
3055
3007
  config: providersConfig.getConfig(providerId),
3056
3008
  logger,
3057
3009
  tokenManager,
@@ -3111,7 +3063,6 @@ function createOriginFilter(config) {
3111
3063
  }
3112
3064
 
3113
3065
  exports.CatalogIdentityClient = CatalogIdentityClient;
3114
- exports.IdentityClient = IdentityClient;
3115
3066
  exports.OAuthAdapter = OAuthAdapter;
3116
3067
  exports.OAuthEnvironmentHandler = OAuthEnvironmentHandler;
3117
3068
  exports.bitbucketUserIdSignInResolver = bitbucketUserIdSignInResolver;