@backstage/plugin-auth-backend 0.4.2 → 0.4.6

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
@@ -18,16 +18,18 @@ var passportMicrosoft = require('passport-microsoft');
18
18
  var got = require('got');
19
19
  var OAuth2Strategy = require('passport-oauth2');
20
20
  var passportOktaOauth = require('passport-okta-oauth');
21
- var openidClient = require('openid-client');
22
- var passportSaml = require('passport-saml');
23
- var passportOneloginOauth = require('passport-onelogin-oauth');
21
+ var passportBitbucketOauth2 = require('passport-bitbucket-oauth2');
24
22
  var fetch = require('cross-fetch');
25
23
  var NodeCache = require('node-cache');
26
24
  var jose = require('jose');
25
+ var openidClient = require('openid-client');
26
+ var passportSaml = require('passport-saml');
27
+ var passportOneloginOauth = require('passport-onelogin-oauth');
27
28
  var catalogClient = require('@backstage/catalog-client');
28
29
  var uuid = require('uuid');
29
30
  var luxon = require('luxon');
30
31
  var backendCommon = require('@backstage/backend-common');
32
+ var firestore = require('@google-cloud/firestore');
31
33
  var session = require('express-session');
32
34
  var passport = require('passport');
33
35
  var minimatch = require('minimatch');
@@ -416,12 +418,10 @@ class OAuthAdapter {
416
418
  response
417
419
  });
418
420
  } catch (error) {
421
+ const {name, message} = errors.isError(error) ? error : new Error("Encountered invalid error");
419
422
  return postMessageResponse(res, appOrigin, {
420
423
  type: "authorization_response",
421
- error: {
422
- name: error.name,
423
- message: error.message
424
- }
424
+ error: {name, message}
425
425
  });
426
426
  }
427
427
  }
@@ -457,7 +457,7 @@ class OAuthAdapter {
457
457
  }
458
458
  res.status(200).json(response);
459
459
  } catch (error) {
460
- res.status(401).send(`${error.message}`);
460
+ res.status(401).send(String(error));
461
461
  }
462
462
  }
463
463
  async populateIdentity(identity) {
@@ -550,6 +550,7 @@ class GithubAuthProvider {
550
550
  constructor(options) {
551
551
  this.signInResolver = options.signInResolver;
552
552
  this.authHandler = options.authHandler;
553
+ this.stateEncoder = options.stateEncoder;
553
554
  this.tokenIssuer = options.tokenIssuer;
554
555
  this.catalogIdentityClient = options.catalogIdentityClient;
555
556
  this.logger = options.logger;
@@ -567,7 +568,7 @@ class GithubAuthProvider {
567
568
  async start(req) {
568
569
  return await executeRedirectStrategy(req, this._strategy, {
569
570
  scope: req.scope,
570
- state: encodeState(req.state)
571
+ state: (await this.stateEncoder(req)).encodedState
571
572
  });
572
573
  }
573
574
  async handler(req) {
@@ -578,13 +579,17 @@ class GithubAuthProvider {
578
579
  };
579
580
  }
580
581
  async refresh(req) {
581
- const {accessToken, params} = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
582
+ const {
583
+ accessToken,
584
+ refreshToken: newRefreshToken,
585
+ params
586
+ } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
582
587
  const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
583
588
  return this.handleResult({
584
589
  fullProfile,
585
590
  params,
586
591
  accessToken,
587
- refreshToken: req.refreshToken
592
+ refreshToken: newRefreshToken
588
593
  });
589
594
  }
590
595
  async handleResult(result) {
@@ -593,6 +598,7 @@ class GithubAuthProvider {
593
598
  const response = {
594
599
  providerInfo: {
595
600
  accessToken: result.accessToken,
601
+ refreshToken: result.refreshToken,
596
602
  scope: result.params.scope,
597
603
  expiresInSeconds: expiresInStr === void 0 ? void 0 : Number(expiresInStr)
598
604
  },
@@ -628,7 +634,7 @@ const createGithubProvider = (options) => {
628
634
  catalogApi,
629
635
  logger
630
636
  }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
631
- var _a, _b;
637
+ var _a, _b, _c;
632
638
  const clientId = envConfig.getString("clientId");
633
639
  const clientSecret = envConfig.getString("clientSecret");
634
640
  const enterpriseInstanceUrl = envConfig.getOptionalString("enterpriseInstanceUrl");
@@ -650,6 +656,9 @@ const createGithubProvider = (options) => {
650
656
  tokenIssuer,
651
657
  logger
652
658
  });
659
+ const stateEncoder = (_c = options == null ? void 0 : options.stateEncoder) != null ? _c : async (req) => {
660
+ return {encodedState: encodeState(req.state)};
661
+ };
653
662
  const provider = new GithubAuthProvider({
654
663
  clientId,
655
664
  clientSecret,
@@ -661,6 +670,7 @@ const createGithubProvider = (options) => {
661
670
  authHandler,
662
671
  tokenIssuer,
663
672
  catalogIdentityClient,
673
+ stateEncoder,
664
674
  logger
665
675
  });
666
676
  return OAuthAdapter.fromConfig(globalConfig, provider, {
@@ -1378,6 +1388,9 @@ const createOktaProvider = (_options) => {
1378
1388
  const clientSecret = envConfig.getString("clientSecret");
1379
1389
  const audience = envConfig.getString("audience");
1380
1390
  const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1391
+ if (!audience.startsWith("https://")) {
1392
+ throw new Error("URL for 'audience' must start with 'https://'.");
1393
+ }
1381
1394
  const catalogIdentityClient = new CatalogIdentityClient({
1382
1395
  catalogApi,
1383
1396
  tokenIssuer
@@ -1410,6 +1423,444 @@ const createOktaProvider = (_options) => {
1410
1423
  });
1411
1424
  };
1412
1425
 
1426
+ class BitbucketAuthProvider {
1427
+ constructor(options) {
1428
+ this.signInResolver = options.signInResolver;
1429
+ this.authHandler = options.authHandler;
1430
+ this.tokenIssuer = options.tokenIssuer;
1431
+ this.catalogIdentityClient = options.catalogIdentityClient;
1432
+ this.logger = options.logger;
1433
+ this._strategy = new passportBitbucketOauth2.Strategy({
1434
+ clientID: options.clientId,
1435
+ clientSecret: options.clientSecret,
1436
+ callbackURL: options.callbackUrl,
1437
+ passReqToCallback: false
1438
+ }, (accessToken, refreshToken, params, fullProfile, done) => {
1439
+ done(void 0, {
1440
+ fullProfile,
1441
+ params,
1442
+ accessToken,
1443
+ refreshToken
1444
+ }, {
1445
+ refreshToken
1446
+ });
1447
+ });
1448
+ }
1449
+ async start(req) {
1450
+ return await executeRedirectStrategy(req, this._strategy, {
1451
+ accessType: "offline",
1452
+ prompt: "consent",
1453
+ scope: req.scope,
1454
+ state: encodeState(req.state)
1455
+ });
1456
+ }
1457
+ async handler(req) {
1458
+ const {result, privateInfo} = await executeFrameHandlerStrategy(req, this._strategy);
1459
+ return {
1460
+ response: await this.handleResult(result),
1461
+ refreshToken: privateInfo.refreshToken
1462
+ };
1463
+ }
1464
+ async refresh(req) {
1465
+ const {accessToken, params} = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1466
+ const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
1467
+ return this.handleResult({
1468
+ fullProfile,
1469
+ params,
1470
+ accessToken,
1471
+ refreshToken: req.refreshToken
1472
+ });
1473
+ }
1474
+ async handleResult(result) {
1475
+ result.fullProfile.avatarUrl = result.fullProfile._json.links.avatar.href;
1476
+ const {profile} = await this.authHandler(result);
1477
+ const response = {
1478
+ providerInfo: {
1479
+ idToken: result.params.id_token,
1480
+ accessToken: result.accessToken,
1481
+ scope: result.params.scope,
1482
+ expiresInSeconds: result.params.expires_in
1483
+ },
1484
+ profile
1485
+ };
1486
+ if (this.signInResolver) {
1487
+ response.backstageIdentity = await this.signInResolver({
1488
+ result,
1489
+ profile
1490
+ }, {
1491
+ tokenIssuer: this.tokenIssuer,
1492
+ catalogIdentityClient: this.catalogIdentityClient,
1493
+ logger: this.logger
1494
+ });
1495
+ }
1496
+ return response;
1497
+ }
1498
+ }
1499
+ const bitbucketUsernameSignInResolver = async (info, ctx) => {
1500
+ const {result} = info;
1501
+ if (!result.fullProfile.username) {
1502
+ throw new Error("Bitbucket profile contained no Username");
1503
+ }
1504
+ const entity = await ctx.catalogIdentityClient.findUser({
1505
+ annotations: {
1506
+ "bitbucket.org/username": result.fullProfile.username
1507
+ }
1508
+ });
1509
+ const claims = getEntityClaims(entity);
1510
+ const token = await ctx.tokenIssuer.issueToken({claims});
1511
+ return {id: entity.metadata.name, entity, token};
1512
+ };
1513
+ const bitbucketUserIdSignInResolver = async (info, ctx) => {
1514
+ const {result} = info;
1515
+ if (!result.fullProfile.id) {
1516
+ throw new Error("Bitbucket profile contained no User ID");
1517
+ }
1518
+ const entity = await ctx.catalogIdentityClient.findUser({
1519
+ annotations: {
1520
+ "bitbucket.org/user-id": result.fullProfile.id
1521
+ }
1522
+ });
1523
+ const claims = getEntityClaims(entity);
1524
+ const token = await ctx.tokenIssuer.issueToken({claims});
1525
+ return {id: entity.metadata.name, entity, token};
1526
+ };
1527
+ const createBitbucketProvider = (options) => {
1528
+ return ({
1529
+ providerId,
1530
+ globalConfig,
1531
+ config,
1532
+ tokenIssuer,
1533
+ catalogApi,
1534
+ logger
1535
+ }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
1536
+ var _a;
1537
+ const clientId = envConfig.getString("clientId");
1538
+ const clientSecret = envConfig.getString("clientSecret");
1539
+ const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1540
+ const catalogIdentityClient = new CatalogIdentityClient({
1541
+ catalogApi,
1542
+ tokenIssuer
1543
+ });
1544
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({fullProfile, params}) => ({
1545
+ profile: makeProfileInfo(fullProfile, params.id_token)
1546
+ });
1547
+ const provider = new BitbucketAuthProvider({
1548
+ clientId,
1549
+ clientSecret,
1550
+ callbackUrl,
1551
+ signInResolver: (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver,
1552
+ authHandler,
1553
+ tokenIssuer,
1554
+ catalogIdentityClient,
1555
+ logger
1556
+ });
1557
+ return OAuthAdapter.fromConfig(globalConfig, provider, {
1558
+ disableRefresh: false,
1559
+ providerId,
1560
+ tokenIssuer
1561
+ });
1562
+ });
1563
+ };
1564
+
1565
+ const defaultScopes = ["offline_access", "read:me"];
1566
+ class AtlassianStrategy extends OAuth2Strategy__default['default'] {
1567
+ constructor(options, verify) {
1568
+ if (!options.scope) {
1569
+ throw new TypeError("Atlassian requires a scope option");
1570
+ }
1571
+ const scopes = options.scope.split(" ");
1572
+ const optionsWithURLs = {
1573
+ ...options,
1574
+ authorizationURL: `https://auth.atlassian.com/authorize`,
1575
+ tokenURL: `https://auth.atlassian.com/oauth/token`,
1576
+ scope: Array.from(new Set([...defaultScopes, ...scopes]))
1577
+ };
1578
+ super(optionsWithURLs, verify);
1579
+ this.profileURL = "https://api.atlassian.com/me";
1580
+ this.name = "atlassian";
1581
+ this._oauth2.useAuthorizationHeaderforGET(true);
1582
+ }
1583
+ authorizationParams() {
1584
+ return {
1585
+ audience: "api.atlassian.com",
1586
+ prompt: "consent"
1587
+ };
1588
+ }
1589
+ userProfile(accessToken, done) {
1590
+ this._oauth2.get(this.profileURL, accessToken, (err, body) => {
1591
+ if (err) {
1592
+ return done(new OAuth2Strategy.InternalOAuthError("Failed to fetch user profile", err.statusCode));
1593
+ }
1594
+ if (!body) {
1595
+ return done(new Error("Failed to fetch user profile, body cannot be empty"));
1596
+ }
1597
+ try {
1598
+ const json = typeof body !== "string" ? body.toString() : body;
1599
+ const profile = AtlassianStrategy.parse(json);
1600
+ return done(null, profile);
1601
+ } catch (e) {
1602
+ return done(new Error("Failed to parse user profile"));
1603
+ }
1604
+ });
1605
+ }
1606
+ static parse(json) {
1607
+ const resp = JSON.parse(json);
1608
+ return {
1609
+ id: resp.account_id,
1610
+ provider: "atlassian",
1611
+ username: resp.nickname,
1612
+ displayName: resp.name,
1613
+ emails: [{value: resp.email}],
1614
+ photos: [{value: resp.picture}]
1615
+ };
1616
+ }
1617
+ }
1618
+
1619
+ const atlassianDefaultAuthHandler = async ({
1620
+ fullProfile,
1621
+ params
1622
+ }) => ({
1623
+ profile: makeProfileInfo(fullProfile, params.id_token)
1624
+ });
1625
+ class AtlassianAuthProvider {
1626
+ constructor(options) {
1627
+ this.catalogIdentityClient = options.catalogIdentityClient;
1628
+ this.logger = options.logger;
1629
+ this.tokenIssuer = options.tokenIssuer;
1630
+ this.authHandler = options.authHandler;
1631
+ this.signInResolver = options.signInResolver;
1632
+ this._strategy = new AtlassianStrategy({
1633
+ clientID: options.clientId,
1634
+ clientSecret: options.clientSecret,
1635
+ callbackURL: options.callbackUrl,
1636
+ scope: options.scopes
1637
+ }, (accessToken, refreshToken, params, fullProfile, done) => {
1638
+ done(void 0, {
1639
+ fullProfile,
1640
+ accessToken,
1641
+ refreshToken,
1642
+ params
1643
+ });
1644
+ });
1645
+ }
1646
+ async start(req) {
1647
+ return await executeRedirectStrategy(req, this._strategy, {
1648
+ state: encodeState(req.state)
1649
+ });
1650
+ }
1651
+ async handler(req) {
1652
+ var _a;
1653
+ const {result} = await executeFrameHandlerStrategy(req, this._strategy);
1654
+ return {
1655
+ response: await this.handleResult(result),
1656
+ refreshToken: (_a = result.refreshToken) != null ? _a : ""
1657
+ };
1658
+ }
1659
+ async handleResult(result) {
1660
+ const {profile} = await this.authHandler(result);
1661
+ const response = {
1662
+ providerInfo: {
1663
+ idToken: result.params.id_token,
1664
+ accessToken: result.accessToken,
1665
+ refreshToken: result.refreshToken,
1666
+ scope: result.params.scope,
1667
+ expiresInSeconds: result.params.expires_in
1668
+ },
1669
+ profile
1670
+ };
1671
+ if (this.signInResolver) {
1672
+ response.backstageIdentity = await this.signInResolver({
1673
+ result,
1674
+ profile
1675
+ }, {
1676
+ tokenIssuer: this.tokenIssuer,
1677
+ catalogIdentityClient: this.catalogIdentityClient,
1678
+ logger: this.logger
1679
+ });
1680
+ }
1681
+ return response;
1682
+ }
1683
+ async refresh(req) {
1684
+ const {
1685
+ accessToken,
1686
+ params,
1687
+ refreshToken: newRefreshToken
1688
+ } = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
1689
+ const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
1690
+ return this.handleResult({
1691
+ fullProfile,
1692
+ params,
1693
+ accessToken,
1694
+ refreshToken: newRefreshToken
1695
+ });
1696
+ }
1697
+ }
1698
+ const createAtlassianProvider = (options) => {
1699
+ return ({
1700
+ providerId,
1701
+ globalConfig,
1702
+ config,
1703
+ tokenIssuer,
1704
+ catalogApi,
1705
+ logger
1706
+ }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
1707
+ var _a, _b;
1708
+ const clientId = envConfig.getString("clientId");
1709
+ const clientSecret = envConfig.getString("clientSecret");
1710
+ const scopes = envConfig.getString("scopes");
1711
+ const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1712
+ const catalogIdentityClient = new CatalogIdentityClient({
1713
+ catalogApi,
1714
+ tokenIssuer
1715
+ });
1716
+ const authHandler = (_a = options == null ? void 0 : options.authHandler) != null ? _a : atlassianDefaultAuthHandler;
1717
+ const provider = new AtlassianAuthProvider({
1718
+ clientId,
1719
+ clientSecret,
1720
+ scopes,
1721
+ callbackUrl,
1722
+ authHandler,
1723
+ signInResolver: (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver,
1724
+ catalogIdentityClient,
1725
+ logger,
1726
+ tokenIssuer
1727
+ });
1728
+ return OAuthAdapter.fromConfig(globalConfig, provider, {
1729
+ disableRefresh: true,
1730
+ providerId,
1731
+ tokenIssuer
1732
+ });
1733
+ });
1734
+ };
1735
+
1736
+ const ALB_JWT_HEADER = "x-amzn-oidc-data";
1737
+ const ALB_ACCESSTOKEN_HEADER = "x-amzn-oidc-accesstoken";
1738
+ const getJWTHeaders = (input) => {
1739
+ const encoded = input.split(".")[0];
1740
+ return JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
1741
+ };
1742
+ class AwsAlbAuthProvider {
1743
+ constructor(options) {
1744
+ this.region = options.region;
1745
+ this.issuer = options.issuer;
1746
+ this.authHandler = options.authHandler;
1747
+ this.signInResolver = options.signInResolver;
1748
+ this.tokenIssuer = options.tokenIssuer;
1749
+ this.catalogIdentityClient = options.catalogIdentityClient;
1750
+ this.logger = options.logger;
1751
+ this.keyCache = new NodeCache__default['default']({stdTTL: 3600});
1752
+ }
1753
+ frameHandler() {
1754
+ return Promise.resolve(void 0);
1755
+ }
1756
+ async refresh(req, res) {
1757
+ try {
1758
+ const result = await this.getResult(req);
1759
+ const response = await this.handleResult(result);
1760
+ res.json(response);
1761
+ } catch (e) {
1762
+ this.logger.error("Exception occurred during AWS ALB token refresh", e);
1763
+ res.status(401);
1764
+ res.end();
1765
+ }
1766
+ }
1767
+ start() {
1768
+ return Promise.resolve(void 0);
1769
+ }
1770
+ async getResult(req) {
1771
+ const jwt = req.header(ALB_JWT_HEADER);
1772
+ const accessToken = req.header(ALB_ACCESSTOKEN_HEADER);
1773
+ if (jwt === void 0) {
1774
+ throw new errors.AuthenticationError(`Missing ALB OIDC header: ${ALB_JWT_HEADER}`);
1775
+ }
1776
+ if (accessToken === void 0) {
1777
+ throw new errors.AuthenticationError(`Missing ALB OIDC header: ${ALB_ACCESSTOKEN_HEADER}`);
1778
+ }
1779
+ try {
1780
+ const headers = getJWTHeaders(jwt);
1781
+ const key = await this.getKey(headers.kid);
1782
+ const claims = jose.JWT.verify(jwt, key);
1783
+ if (this.issuer && claims.iss !== this.issuer) {
1784
+ throw new errors.AuthenticationError("Issuer mismatch on JWT token");
1785
+ }
1786
+ const fullProfile = {
1787
+ provider: "unknown",
1788
+ id: claims.sub,
1789
+ displayName: claims.name,
1790
+ username: claims.email.split("@")[0].toLowerCase(),
1791
+ name: {
1792
+ familyName: claims.family_name,
1793
+ givenName: claims.given_name
1794
+ },
1795
+ emails: [{value: claims.email.toLowerCase()}],
1796
+ photos: [{value: claims.picture}]
1797
+ };
1798
+ return {
1799
+ fullProfile,
1800
+ expiresInSeconds: claims.exp,
1801
+ accessToken
1802
+ };
1803
+ } catch (e) {
1804
+ throw new Error(`Exception occurred during JWT processing: ${e}`);
1805
+ }
1806
+ }
1807
+ async handleResult(result) {
1808
+ const {profile} = await this.authHandler(result);
1809
+ const backstageIdentity = await this.signInResolver({
1810
+ result,
1811
+ profile
1812
+ }, {
1813
+ tokenIssuer: this.tokenIssuer,
1814
+ catalogIdentityClient: this.catalogIdentityClient,
1815
+ logger: this.logger
1816
+ });
1817
+ return {
1818
+ providerInfo: {
1819
+ accessToken: result.accessToken,
1820
+ expiresInSeconds: result.expiresInSeconds
1821
+ },
1822
+ backstageIdentity,
1823
+ profile
1824
+ };
1825
+ }
1826
+ async getKey(keyId) {
1827
+ const optionalCacheKey = this.keyCache.get(keyId);
1828
+ if (optionalCacheKey) {
1829
+ return crypto__namespace.createPublicKey(optionalCacheKey);
1830
+ }
1831
+ const keyText = await fetch__default['default'](`https://public-keys.auth.elb.${this.region}.amazonaws.com/${keyId}`).then((response) => response.text());
1832
+ const keyValue = crypto__namespace.createPublicKey(keyText);
1833
+ this.keyCache.set(keyId, keyValue.export({format: "pem", type: "spki"}));
1834
+ return keyValue;
1835
+ }
1836
+ }
1837
+ const createAwsAlbProvider = (options) => {
1838
+ return ({config, tokenIssuer, catalogApi, logger}) => {
1839
+ const region = config.getString("region");
1840
+ const issuer = config.getOptionalString("iss");
1841
+ if ((options == null ? void 0 : options.signIn.resolver) === void 0) {
1842
+ throw new Error("SignInResolver is required to use this authentication provider");
1843
+ }
1844
+ const catalogIdentityClient = new CatalogIdentityClient({
1845
+ catalogApi,
1846
+ tokenIssuer
1847
+ });
1848
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({fullProfile}) => ({
1849
+ profile: makeProfileInfo(fullProfile)
1850
+ });
1851
+ const signInResolver = options == null ? void 0 : options.signIn.resolver;
1852
+ return new AwsAlbAuthProvider({
1853
+ region,
1854
+ issuer,
1855
+ signInResolver,
1856
+ authHandler,
1857
+ tokenIssuer,
1858
+ catalogIdentityClient,
1859
+ logger
1860
+ });
1861
+ };
1862
+ };
1863
+
1413
1864
  class OidcAuthProvider {
1414
1865
  constructor(options) {
1415
1866
  this.implementation = this.setupStrategy(options);
@@ -1562,12 +2013,10 @@ class SamlAuthProvider {
1562
2013
  }
1563
2014
  });
1564
2015
  } catch (error) {
2016
+ const {name, message} = errors.isError(error) ? error : new Error("Encountered invalid error");
1565
2017
  return postMessageResponse(res, this.appUrl, {
1566
2018
  type: "authorization_response",
1567
- error: {
1568
- name: error.name,
1569
- message: error.message
1570
- }
2019
+ error: {name, message}
1571
2020
  });
1572
2021
  }
1573
2022
  }
@@ -1587,6 +2036,8 @@ const createSamlProvider = (_options) => {
1587
2036
  issuer: config.getString("issuer"),
1588
2037
  cert: config.getString("cert"),
1589
2038
  privateCert: config.getOptionalString("privateKey"),
2039
+ authnContext: config.getOptionalStringArray("authnContext"),
2040
+ identifierFormat: config.getOptionalString("identifierFormat"),
1590
2041
  decryptionPvk: config.getOptionalString("decryptionPvk"),
1591
2042
  signatureAlgorithm: config.getOptionalString("signatureAlgorithm"),
1592
2043
  digestAlgorithm: config.getOptionalString("digestAlgorithm"),
@@ -1783,77 +2234,6 @@ const createOneLoginProvider = (_options) => {
1783
2234
  });
1784
2235
  };
1785
2236
 
1786
- const ALB_JWT_HEADER = "x-amzn-oidc-data";
1787
- const getJWTHeaders = (input) => {
1788
- const encoded = input.split(".")[0];
1789
- return JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
1790
- };
1791
- class AwsAlbAuthProvider {
1792
- constructor(logger, catalogClient, options) {
1793
- this.logger = logger;
1794
- this.catalogClient = catalogClient;
1795
- this.options = options;
1796
- this.keyCache = new NodeCache__default['default']({stdTTL: 3600});
1797
- }
1798
- frameHandler() {
1799
- return Promise.resolve(void 0);
1800
- }
1801
- async refresh(req, res) {
1802
- const jwt = req.header(ALB_JWT_HEADER);
1803
- if (jwt !== void 0) {
1804
- try {
1805
- const headers = getJWTHeaders(jwt);
1806
- const key = await this.getKey(headers.kid);
1807
- const payload = jose.JWT.verify(jwt, key);
1808
- if (this.options.issuer && headers.iss !== this.options.issuer) {
1809
- throw new Error("issuer mismatch on JWT");
1810
- }
1811
- const resolvedEntity = await this.options.identityResolutionCallback(payload, this.catalogClient);
1812
- res.json(resolvedEntity);
1813
- } catch (e) {
1814
- this.logger.error("exception occurred during JWT processing", e);
1815
- res.status(401);
1816
- res.end();
1817
- }
1818
- } else {
1819
- res.status(401);
1820
- res.end();
1821
- }
1822
- }
1823
- start() {
1824
- return Promise.resolve(void 0);
1825
- }
1826
- async getKey(keyId) {
1827
- const optionalCacheKey = this.keyCache.get(keyId);
1828
- if (optionalCacheKey) {
1829
- return crypto__namespace.createPublicKey(optionalCacheKey);
1830
- }
1831
- const keyText = await fetch__default['default'](`https://public-keys.auth.elb.${this.options.region}.amazonaws.com/${keyId}`).then((response) => response.text());
1832
- const keyValue = crypto__namespace.createPublicKey(keyText);
1833
- this.keyCache.set(keyId, keyValue.export({format: "pem", type: "spki"}));
1834
- return keyValue;
1835
- }
1836
- }
1837
- const createAwsAlbProvider = (_options) => {
1838
- return ({
1839
- logger,
1840
- catalogApi,
1841
- config,
1842
- identityResolver
1843
- }) => {
1844
- const region = config.getString("region");
1845
- const issuer = config.getOptionalString("iss");
1846
- if (identityResolver !== void 0) {
1847
- return new AwsAlbAuthProvider(logger, catalogApi, {
1848
- region,
1849
- issuer,
1850
- identityResolutionCallback: identityResolver
1851
- });
1852
- }
1853
- throw new Error("Identity resolver is required to use this authentication provider");
1854
- };
1855
- };
1856
-
1857
2237
  const factories = {
1858
2238
  google: createGoogleProvider(),
1859
2239
  github: createGithubProvider(),
@@ -1865,7 +2245,9 @@ const factories = {
1865
2245
  oauth2: createOAuth2Provider(),
1866
2246
  oidc: createOidcProvider(),
1867
2247
  onelogin: createOneLoginProvider(),
1868
- awsalb: createAwsAlbProvider()
2248
+ awsalb: createAwsAlbProvider(),
2249
+ bitbucket: createBitbucketProvider(),
2250
+ atlassian: createAtlassianProvider()
1869
2251
  };
1870
2252
 
1871
2253
  function createOidcRouter(options) {
@@ -2084,6 +2466,121 @@ class DatabaseKeyStore {
2084
2466
  }
2085
2467
  }
2086
2468
 
2469
+ class MemoryKeyStore {
2470
+ constructor() {
2471
+ this.keys = new Map();
2472
+ }
2473
+ async addKey(key) {
2474
+ this.keys.set(key.kid, {
2475
+ createdAt: luxon.DateTime.utc().toJSDate(),
2476
+ key: JSON.stringify(key)
2477
+ });
2478
+ }
2479
+ async removeKeys(kids) {
2480
+ for (const kid of kids) {
2481
+ this.keys.delete(kid);
2482
+ }
2483
+ }
2484
+ async listKeys() {
2485
+ return {
2486
+ items: Array.from(this.keys).map(([, {createdAt, key: keyStr}]) => ({
2487
+ createdAt,
2488
+ key: JSON.parse(keyStr)
2489
+ }))
2490
+ };
2491
+ }
2492
+ }
2493
+
2494
+ const DEFAULT_TIMEOUT_MS = 1e4;
2495
+ const DEFAULT_DOCUMENT_PATH = "sessions";
2496
+ class FirestoreKeyStore {
2497
+ constructor(database, path, timeout) {
2498
+ this.database = database;
2499
+ this.path = path;
2500
+ this.timeout = timeout;
2501
+ }
2502
+ static async create(settings) {
2503
+ const {path, timeout, ...firestoreSettings} = settings != null ? settings : {};
2504
+ const database = new firestore.Firestore(firestoreSettings);
2505
+ return new FirestoreKeyStore(database, path != null ? path : DEFAULT_DOCUMENT_PATH, timeout != null ? timeout : DEFAULT_TIMEOUT_MS);
2506
+ }
2507
+ static async verifyConnection(keyStore, logger) {
2508
+ try {
2509
+ await keyStore.verify();
2510
+ } catch (error) {
2511
+ if (process.env.NODE_ENV !== "development") {
2512
+ throw new Error(`Failed to connect to database: ${error.message}`);
2513
+ }
2514
+ logger == null ? void 0 : logger.warn(`Failed to connect to database: ${error.message}`);
2515
+ }
2516
+ }
2517
+ async addKey(key) {
2518
+ await this.withTimeout(this.database.collection(this.path).doc(key.kid).set({
2519
+ kid: key.kid,
2520
+ key: JSON.stringify(key)
2521
+ }));
2522
+ }
2523
+ async listKeys() {
2524
+ const keys = await this.withTimeout(this.database.collection(this.path).get());
2525
+ return {
2526
+ items: keys.docs.map((key) => ({
2527
+ key: key.data(),
2528
+ createdAt: key.createTime.toDate()
2529
+ }))
2530
+ };
2531
+ }
2532
+ async removeKeys(kids) {
2533
+ for (const kid of kids) {
2534
+ await this.withTimeout(this.database.collection(this.path).doc(kid).delete());
2535
+ }
2536
+ }
2537
+ async withTimeout(operation) {
2538
+ const timer = new Promise((_, reject) => setTimeout(() => {
2539
+ reject(new Error(`Operation timed out after ${this.timeout}ms`));
2540
+ }, this.timeout));
2541
+ return Promise.race([operation, timer]);
2542
+ }
2543
+ async verify() {
2544
+ await this.withTimeout(this.database.collection(this.path).limit(1).get());
2545
+ }
2546
+ }
2547
+
2548
+ class KeyStores {
2549
+ static async fromConfig(config, options) {
2550
+ var _a;
2551
+ const {logger, database} = options != null ? options : {};
2552
+ const ks = config.getOptionalConfig("auth.keyStore");
2553
+ const provider = (_a = ks == null ? void 0 : ks.getOptionalString("provider")) != null ? _a : "database";
2554
+ logger == null ? void 0 : logger.info(`Configuring "${provider}" as KeyStore provider`);
2555
+ if (provider === "database") {
2556
+ if (!database) {
2557
+ throw new Error("This KeyStore provider requires a database");
2558
+ }
2559
+ return await DatabaseKeyStore.create({
2560
+ database: await database.getClient()
2561
+ });
2562
+ }
2563
+ if (provider === "memory") {
2564
+ return new MemoryKeyStore();
2565
+ }
2566
+ if (provider === "firestore") {
2567
+ const settings = ks == null ? void 0 : ks.getConfig(provider);
2568
+ const keyStore = await FirestoreKeyStore.create({
2569
+ projectId: settings == null ? void 0 : settings.getOptionalString("projectId"),
2570
+ keyFilename: settings == null ? void 0 : settings.getOptionalString("keyFilename"),
2571
+ host: settings == null ? void 0 : settings.getOptionalString("host"),
2572
+ port: settings == null ? void 0 : settings.getOptionalNumber("port"),
2573
+ ssl: settings == null ? void 0 : settings.getOptionalBoolean("ssl"),
2574
+ path: settings == null ? void 0 : settings.getOptionalString("path"),
2575
+ timeout: settings == null ? void 0 : settings.getOptionalNumber("timeout")
2576
+ });
2577
+ await FirestoreKeyStore.verifyConnection(keyStore, logger);
2578
+ return keyStore;
2579
+ }
2580
+ throw new Error(`Unknown KeyStore provider: ${provider}`);
2581
+ }
2582
+ }
2583
+
2087
2584
  async function createRouter({
2088
2585
  logger,
2089
2586
  config,
@@ -2094,10 +2591,8 @@ async function createRouter({
2094
2591
  const router = Router__default['default']();
2095
2592
  const appUrl = config.getString("app.baseUrl");
2096
2593
  const authUrl = await discovery.getExternalBaseUrl("auth");
2594
+ const keyStore = await KeyStores.fromConfig(config, {logger, database});
2097
2595
  const keyDurationSeconds = 3600;
2098
- const keyStore = await DatabaseKeyStore.create({
2099
- database: await database.getClient()
2100
- });
2101
2596
  const tokenIssuer = new TokenFactory({
2102
2597
  issuer: authUrl,
2103
2598
  keyStore,
@@ -2148,6 +2643,7 @@ async function createRouter({
2148
2643
  }
2149
2644
  router.use(`/${providerId}`, r);
2150
2645
  } catch (e) {
2646
+ errors.assertError(e);
2151
2647
  if (process.env.NODE_ENV !== "development") {
2152
2648
  throw new Error(`Failed to initialize ${providerId} auth provider, ${e.message}`);
2153
2649
  }
@@ -2189,6 +2685,11 @@ function createOriginFilter(config) {
2189
2685
  exports.IdentityClient = IdentityClient;
2190
2686
  exports.OAuthAdapter = OAuthAdapter;
2191
2687
  exports.OAuthEnvironmentHandler = OAuthEnvironmentHandler;
2688
+ exports.bitbucketUserIdSignInResolver = bitbucketUserIdSignInResolver;
2689
+ exports.bitbucketUsernameSignInResolver = bitbucketUsernameSignInResolver;
2690
+ exports.createAtlassianProvider = createAtlassianProvider;
2691
+ exports.createAwsAlbProvider = createAwsAlbProvider;
2692
+ exports.createBitbucketProvider = createBitbucketProvider;
2192
2693
  exports.createGithubProvider = createGithubProvider;
2193
2694
  exports.createGitlabProvider = createGitlabProvider;
2194
2695
  exports.createGoogleProvider = createGoogleProvider;