@backstage/plugin-auth-backend 0.4.4 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/config.d.ts +27 -0
- package/dist/index.cjs.js +320 -19
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +156 -97
- package/package.json +11 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# @backstage/plugin-auth-backend
|
|
2
2
|
|
|
3
|
+
## 0.4.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 892c1d9202: Update OAuthAdapter to create identity.token from identity.idToken if it does not exist, and prevent overwrites to identity.toke. Update login page commonProvider to prefer .token over .idToken
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @backstage/catalog-client@0.5.2
|
|
10
|
+
- @backstage/catalog-model@0.9.7
|
|
11
|
+
- @backstage/backend-common@0.9.10
|
|
12
|
+
- @backstage/test-utils@0.1.22
|
|
13
|
+
|
|
14
|
+
## 0.4.7
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 5ee31f860b: Only use settings that have a value when creating a new FirestoreKeyStore instance
|
|
19
|
+
- 3e0e2f09d5: Added forwarding of the `audience` option for the SAML provider, making it possible to enable `audience` verification.
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
- @backstage/backend-common@0.9.9
|
|
22
|
+
- @backstage/test-utils@0.1.21
|
|
23
|
+
- @backstage/catalog-client@0.5.1
|
|
24
|
+
|
|
25
|
+
## 0.4.6
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- 3b767f19c9: Allow OAuth state to be encoded by a stateEncoder.
|
|
30
|
+
- Updated dependencies
|
|
31
|
+
- @backstage/test-utils@0.1.20
|
|
32
|
+
- @backstage/config@0.1.11
|
|
33
|
+
- @backstage/errors@0.1.4
|
|
34
|
+
- @backstage/backend-common@0.9.8
|
|
35
|
+
- @backstage/catalog-model@0.9.6
|
|
36
|
+
|
|
37
|
+
## 0.4.5
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- 9322e632e9: Require that audience URLs for Okta authentication start with https
|
|
42
|
+
- de3e26aecc: Fix a bug preventing an access token to be refreshed a second time with the GitHub provider.
|
|
43
|
+
- ab9b4a6ea6: Add Firestore as key-store provider.
|
|
44
|
+
Add `auth.keyStore` section to application config.
|
|
45
|
+
- 202f322927: Atlassian auth provider
|
|
46
|
+
|
|
47
|
+
- AtlassianAuth added to core-app-api
|
|
48
|
+
- Atlassian provider added to plugin-auth-backend
|
|
49
|
+
- Updated user-settings with Atlassian connection
|
|
50
|
+
|
|
51
|
+
- 36e67d2f24: Internal updates to apply more strict checks to throw errors.
|
|
52
|
+
- Updated dependencies
|
|
53
|
+
- @backstage/backend-common@0.9.7
|
|
54
|
+
- @backstage/errors@0.1.3
|
|
55
|
+
- @backstage/catalog-model@0.9.5
|
|
56
|
+
|
|
3
57
|
## 0.4.4
|
|
4
58
|
|
|
5
59
|
### Patch Changes
|
package/config.d.ts
CHANGED
|
@@ -31,6 +31,32 @@ export interface Config {
|
|
|
31
31
|
secret?: string;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
/** To control how to store JWK data in auth-backend */
|
|
35
|
+
keyStore?: {
|
|
36
|
+
provider?: 'database' | 'memory' | 'firestore';
|
|
37
|
+
firestore?: {
|
|
38
|
+
/** The host to connect to */
|
|
39
|
+
host?: string;
|
|
40
|
+
/** The port to connect to */
|
|
41
|
+
port?: number;
|
|
42
|
+
/** Whether to use SSL when connecting. */
|
|
43
|
+
ssl?: boolean;
|
|
44
|
+
/** The Google Cloud Project ID */
|
|
45
|
+
projectId?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Local file containing the Service Account credentials.
|
|
48
|
+
* You can omit this value to automatically read from
|
|
49
|
+
* GOOGLE_APPLICATION_CREDENTIALS env which is useful for local
|
|
50
|
+
* development.
|
|
51
|
+
*/
|
|
52
|
+
keyFilename?: string;
|
|
53
|
+
/** The path to use for the collection. Defaults to 'sessions' */
|
|
54
|
+
path?: string;
|
|
55
|
+
/** Timeout used for database operations. Defaults to 10000ms */
|
|
56
|
+
timeout?: number;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
34
60
|
/**
|
|
35
61
|
* The available auth-provider options and attributes
|
|
36
62
|
*/
|
|
@@ -49,6 +75,7 @@ export interface Config {
|
|
|
49
75
|
logoutUrl?: string;
|
|
50
76
|
issuer: string;
|
|
51
77
|
cert: string;
|
|
78
|
+
audience?: string;
|
|
52
79
|
privateKey?: string;
|
|
53
80
|
authnContext?: string[];
|
|
54
81
|
identifierFormat?: string;
|
package/dist/index.cjs.js
CHANGED
|
@@ -29,6 +29,8 @@ var catalogClient = require('@backstage/catalog-client');
|
|
|
29
29
|
var uuid = require('uuid');
|
|
30
30
|
var luxon = require('luxon');
|
|
31
31
|
var backendCommon = require('@backstage/backend-common');
|
|
32
|
+
var firestore = require('@google-cloud/firestore');
|
|
33
|
+
var lodash = require('lodash');
|
|
32
34
|
var session = require('express-session');
|
|
33
35
|
var passport = require('passport');
|
|
34
36
|
var minimatch = require('minimatch');
|
|
@@ -417,12 +419,10 @@ class OAuthAdapter {
|
|
|
417
419
|
response
|
|
418
420
|
});
|
|
419
421
|
} catch (error) {
|
|
422
|
+
const {name, message} = errors.isError(error) ? error : new Error("Encountered invalid error");
|
|
420
423
|
return postMessageResponse(res, appOrigin, {
|
|
421
424
|
type: "authorization_response",
|
|
422
|
-
error: {
|
|
423
|
-
name: error.name,
|
|
424
|
-
message: error.message
|
|
425
|
-
}
|
|
425
|
+
error: {name, message}
|
|
426
426
|
});
|
|
427
427
|
}
|
|
428
428
|
}
|
|
@@ -458,17 +458,19 @@ class OAuthAdapter {
|
|
|
458
458
|
}
|
|
459
459
|
res.status(200).json(response);
|
|
460
460
|
} catch (error) {
|
|
461
|
-
res.status(401).send(
|
|
461
|
+
res.status(401).send(String(error));
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
464
|
async populateIdentity(identity) {
|
|
465
465
|
if (!identity) {
|
|
466
466
|
return;
|
|
467
467
|
}
|
|
468
|
-
if (!identity.idToken) {
|
|
469
|
-
identity.
|
|
468
|
+
if (!(identity.token || identity.idToken)) {
|
|
469
|
+
identity.token = await this.options.tokenIssuer.issueToken({
|
|
470
470
|
claims: {sub: identity.id}
|
|
471
471
|
});
|
|
472
|
+
} else if (!identity.token && identity.idToken) {
|
|
473
|
+
identity.token = identity.idToken;
|
|
472
474
|
}
|
|
473
475
|
}
|
|
474
476
|
}
|
|
@@ -551,6 +553,7 @@ class GithubAuthProvider {
|
|
|
551
553
|
constructor(options) {
|
|
552
554
|
this.signInResolver = options.signInResolver;
|
|
553
555
|
this.authHandler = options.authHandler;
|
|
556
|
+
this.stateEncoder = options.stateEncoder;
|
|
554
557
|
this.tokenIssuer = options.tokenIssuer;
|
|
555
558
|
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
556
559
|
this.logger = options.logger;
|
|
@@ -568,7 +571,7 @@ class GithubAuthProvider {
|
|
|
568
571
|
async start(req) {
|
|
569
572
|
return await executeRedirectStrategy(req, this._strategy, {
|
|
570
573
|
scope: req.scope,
|
|
571
|
-
state:
|
|
574
|
+
state: (await this.stateEncoder(req)).encodedState
|
|
572
575
|
});
|
|
573
576
|
}
|
|
574
577
|
async handler(req) {
|
|
@@ -579,13 +582,17 @@ class GithubAuthProvider {
|
|
|
579
582
|
};
|
|
580
583
|
}
|
|
581
584
|
async refresh(req) {
|
|
582
|
-
const {
|
|
585
|
+
const {
|
|
586
|
+
accessToken,
|
|
587
|
+
refreshToken: newRefreshToken,
|
|
588
|
+
params
|
|
589
|
+
} = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
583
590
|
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
584
591
|
return this.handleResult({
|
|
585
592
|
fullProfile,
|
|
586
593
|
params,
|
|
587
594
|
accessToken,
|
|
588
|
-
refreshToken:
|
|
595
|
+
refreshToken: newRefreshToken
|
|
589
596
|
});
|
|
590
597
|
}
|
|
591
598
|
async handleResult(result) {
|
|
@@ -594,6 +601,7 @@ class GithubAuthProvider {
|
|
|
594
601
|
const response = {
|
|
595
602
|
providerInfo: {
|
|
596
603
|
accessToken: result.accessToken,
|
|
604
|
+
refreshToken: result.refreshToken,
|
|
597
605
|
scope: result.params.scope,
|
|
598
606
|
expiresInSeconds: expiresInStr === void 0 ? void 0 : Number(expiresInStr)
|
|
599
607
|
},
|
|
@@ -629,7 +637,7 @@ const createGithubProvider = (options) => {
|
|
|
629
637
|
catalogApi,
|
|
630
638
|
logger
|
|
631
639
|
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
632
|
-
var _a, _b;
|
|
640
|
+
var _a, _b, _c;
|
|
633
641
|
const clientId = envConfig.getString("clientId");
|
|
634
642
|
const clientSecret = envConfig.getString("clientSecret");
|
|
635
643
|
const enterpriseInstanceUrl = envConfig.getOptionalString("enterpriseInstanceUrl");
|
|
@@ -651,6 +659,9 @@ const createGithubProvider = (options) => {
|
|
|
651
659
|
tokenIssuer,
|
|
652
660
|
logger
|
|
653
661
|
});
|
|
662
|
+
const stateEncoder = (_c = options == null ? void 0 : options.stateEncoder) != null ? _c : async (req) => {
|
|
663
|
+
return {encodedState: encodeState(req.state)};
|
|
664
|
+
};
|
|
654
665
|
const provider = new GithubAuthProvider({
|
|
655
666
|
clientId,
|
|
656
667
|
clientSecret,
|
|
@@ -662,6 +673,7 @@ const createGithubProvider = (options) => {
|
|
|
662
673
|
authHandler,
|
|
663
674
|
tokenIssuer,
|
|
664
675
|
catalogIdentityClient,
|
|
676
|
+
stateEncoder,
|
|
665
677
|
logger
|
|
666
678
|
});
|
|
667
679
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
@@ -1379,6 +1391,9 @@ const createOktaProvider = (_options) => {
|
|
|
1379
1391
|
const clientSecret = envConfig.getString("clientSecret");
|
|
1380
1392
|
const audience = envConfig.getString("audience");
|
|
1381
1393
|
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1394
|
+
if (!audience.startsWith("https://")) {
|
|
1395
|
+
throw new Error("URL for 'audience' must start with 'https://'.");
|
|
1396
|
+
}
|
|
1382
1397
|
const catalogIdentityClient = new CatalogIdentityClient({
|
|
1383
1398
|
catalogApi,
|
|
1384
1399
|
tokenIssuer
|
|
@@ -1550,6 +1565,177 @@ const createBitbucketProvider = (options) => {
|
|
|
1550
1565
|
});
|
|
1551
1566
|
};
|
|
1552
1567
|
|
|
1568
|
+
const defaultScopes = ["offline_access", "read:me"];
|
|
1569
|
+
class AtlassianStrategy extends OAuth2Strategy__default['default'] {
|
|
1570
|
+
constructor(options, verify) {
|
|
1571
|
+
if (!options.scope) {
|
|
1572
|
+
throw new TypeError("Atlassian requires a scope option");
|
|
1573
|
+
}
|
|
1574
|
+
const scopes = options.scope.split(" ");
|
|
1575
|
+
const optionsWithURLs = {
|
|
1576
|
+
...options,
|
|
1577
|
+
authorizationURL: `https://auth.atlassian.com/authorize`,
|
|
1578
|
+
tokenURL: `https://auth.atlassian.com/oauth/token`,
|
|
1579
|
+
scope: Array.from(new Set([...defaultScopes, ...scopes]))
|
|
1580
|
+
};
|
|
1581
|
+
super(optionsWithURLs, verify);
|
|
1582
|
+
this.profileURL = "https://api.atlassian.com/me";
|
|
1583
|
+
this.name = "atlassian";
|
|
1584
|
+
this._oauth2.useAuthorizationHeaderforGET(true);
|
|
1585
|
+
}
|
|
1586
|
+
authorizationParams() {
|
|
1587
|
+
return {
|
|
1588
|
+
audience: "api.atlassian.com",
|
|
1589
|
+
prompt: "consent"
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
userProfile(accessToken, done) {
|
|
1593
|
+
this._oauth2.get(this.profileURL, accessToken, (err, body) => {
|
|
1594
|
+
if (err) {
|
|
1595
|
+
return done(new OAuth2Strategy.InternalOAuthError("Failed to fetch user profile", err.statusCode));
|
|
1596
|
+
}
|
|
1597
|
+
if (!body) {
|
|
1598
|
+
return done(new Error("Failed to fetch user profile, body cannot be empty"));
|
|
1599
|
+
}
|
|
1600
|
+
try {
|
|
1601
|
+
const json = typeof body !== "string" ? body.toString() : body;
|
|
1602
|
+
const profile = AtlassianStrategy.parse(json);
|
|
1603
|
+
return done(null, profile);
|
|
1604
|
+
} catch (e) {
|
|
1605
|
+
return done(new Error("Failed to parse user profile"));
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
static parse(json) {
|
|
1610
|
+
const resp = JSON.parse(json);
|
|
1611
|
+
return {
|
|
1612
|
+
id: resp.account_id,
|
|
1613
|
+
provider: "atlassian",
|
|
1614
|
+
username: resp.nickname,
|
|
1615
|
+
displayName: resp.name,
|
|
1616
|
+
emails: [{value: resp.email}],
|
|
1617
|
+
photos: [{value: resp.picture}]
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
const atlassianDefaultAuthHandler = async ({
|
|
1623
|
+
fullProfile,
|
|
1624
|
+
params
|
|
1625
|
+
}) => ({
|
|
1626
|
+
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
1627
|
+
});
|
|
1628
|
+
class AtlassianAuthProvider {
|
|
1629
|
+
constructor(options) {
|
|
1630
|
+
this.catalogIdentityClient = options.catalogIdentityClient;
|
|
1631
|
+
this.logger = options.logger;
|
|
1632
|
+
this.tokenIssuer = options.tokenIssuer;
|
|
1633
|
+
this.authHandler = options.authHandler;
|
|
1634
|
+
this.signInResolver = options.signInResolver;
|
|
1635
|
+
this._strategy = new AtlassianStrategy({
|
|
1636
|
+
clientID: options.clientId,
|
|
1637
|
+
clientSecret: options.clientSecret,
|
|
1638
|
+
callbackURL: options.callbackUrl,
|
|
1639
|
+
scope: options.scopes
|
|
1640
|
+
}, (accessToken, refreshToken, params, fullProfile, done) => {
|
|
1641
|
+
done(void 0, {
|
|
1642
|
+
fullProfile,
|
|
1643
|
+
accessToken,
|
|
1644
|
+
refreshToken,
|
|
1645
|
+
params
|
|
1646
|
+
});
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
async start(req) {
|
|
1650
|
+
return await executeRedirectStrategy(req, this._strategy, {
|
|
1651
|
+
state: encodeState(req.state)
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
async handler(req) {
|
|
1655
|
+
var _a;
|
|
1656
|
+
const {result} = await executeFrameHandlerStrategy(req, this._strategy);
|
|
1657
|
+
return {
|
|
1658
|
+
response: await this.handleResult(result),
|
|
1659
|
+
refreshToken: (_a = result.refreshToken) != null ? _a : ""
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
async handleResult(result) {
|
|
1663
|
+
const {profile} = await this.authHandler(result);
|
|
1664
|
+
const response = {
|
|
1665
|
+
providerInfo: {
|
|
1666
|
+
idToken: result.params.id_token,
|
|
1667
|
+
accessToken: result.accessToken,
|
|
1668
|
+
refreshToken: result.refreshToken,
|
|
1669
|
+
scope: result.params.scope,
|
|
1670
|
+
expiresInSeconds: result.params.expires_in
|
|
1671
|
+
},
|
|
1672
|
+
profile
|
|
1673
|
+
};
|
|
1674
|
+
if (this.signInResolver) {
|
|
1675
|
+
response.backstageIdentity = await this.signInResolver({
|
|
1676
|
+
result,
|
|
1677
|
+
profile
|
|
1678
|
+
}, {
|
|
1679
|
+
tokenIssuer: this.tokenIssuer,
|
|
1680
|
+
catalogIdentityClient: this.catalogIdentityClient,
|
|
1681
|
+
logger: this.logger
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
return response;
|
|
1685
|
+
}
|
|
1686
|
+
async refresh(req) {
|
|
1687
|
+
const {
|
|
1688
|
+
accessToken,
|
|
1689
|
+
params,
|
|
1690
|
+
refreshToken: newRefreshToken
|
|
1691
|
+
} = await executeRefreshTokenStrategy(this._strategy, req.refreshToken, req.scope);
|
|
1692
|
+
const fullProfile = await executeFetchUserProfileStrategy(this._strategy, accessToken);
|
|
1693
|
+
return this.handleResult({
|
|
1694
|
+
fullProfile,
|
|
1695
|
+
params,
|
|
1696
|
+
accessToken,
|
|
1697
|
+
refreshToken: newRefreshToken
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
const createAtlassianProvider = (options) => {
|
|
1702
|
+
return ({
|
|
1703
|
+
providerId,
|
|
1704
|
+
globalConfig,
|
|
1705
|
+
config,
|
|
1706
|
+
tokenIssuer,
|
|
1707
|
+
catalogApi,
|
|
1708
|
+
logger
|
|
1709
|
+
}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
|
|
1710
|
+
var _a, _b;
|
|
1711
|
+
const clientId = envConfig.getString("clientId");
|
|
1712
|
+
const clientSecret = envConfig.getString("clientSecret");
|
|
1713
|
+
const scopes = envConfig.getString("scopes");
|
|
1714
|
+
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
1715
|
+
const catalogIdentityClient = new CatalogIdentityClient({
|
|
1716
|
+
catalogApi,
|
|
1717
|
+
tokenIssuer
|
|
1718
|
+
});
|
|
1719
|
+
const authHandler = (_a = options == null ? void 0 : options.authHandler) != null ? _a : atlassianDefaultAuthHandler;
|
|
1720
|
+
const provider = new AtlassianAuthProvider({
|
|
1721
|
+
clientId,
|
|
1722
|
+
clientSecret,
|
|
1723
|
+
scopes,
|
|
1724
|
+
callbackUrl,
|
|
1725
|
+
authHandler,
|
|
1726
|
+
signInResolver: (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver,
|
|
1727
|
+
catalogIdentityClient,
|
|
1728
|
+
logger,
|
|
1729
|
+
tokenIssuer
|
|
1730
|
+
});
|
|
1731
|
+
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
1732
|
+
disableRefresh: true,
|
|
1733
|
+
providerId,
|
|
1734
|
+
tokenIssuer
|
|
1735
|
+
});
|
|
1736
|
+
});
|
|
1737
|
+
};
|
|
1738
|
+
|
|
1553
1739
|
const ALB_JWT_HEADER = "x-amzn-oidc-data";
|
|
1554
1740
|
const ALB_ACCESSTOKEN_HEADER = "x-amzn-oidc-accesstoken";
|
|
1555
1741
|
const getJWTHeaders = (input) => {
|
|
@@ -1830,12 +2016,10 @@ class SamlAuthProvider {
|
|
|
1830
2016
|
}
|
|
1831
2017
|
});
|
|
1832
2018
|
} catch (error) {
|
|
2019
|
+
const {name, message} = errors.isError(error) ? error : new Error("Encountered invalid error");
|
|
1833
2020
|
return postMessageResponse(res, this.appUrl, {
|
|
1834
2021
|
type: "authorization_response",
|
|
1835
|
-
error: {
|
|
1836
|
-
name: error.name,
|
|
1837
|
-
message: error.message
|
|
1838
|
-
}
|
|
2022
|
+
error: {name, message}
|
|
1839
2023
|
});
|
|
1840
2024
|
}
|
|
1841
2025
|
}
|
|
@@ -1852,6 +2036,7 @@ const createSamlProvider = (_options) => {
|
|
|
1852
2036
|
callbackUrl: `${globalConfig.baseUrl}/${providerId}/handler/frame`,
|
|
1853
2037
|
entryPoint: config.getString("entryPoint"),
|
|
1854
2038
|
logoutUrl: config.getOptionalString("logoutUrl"),
|
|
2039
|
+
audience: config.getOptionalString("audience"),
|
|
1855
2040
|
issuer: config.getString("issuer"),
|
|
1856
2041
|
cert: config.getString("cert"),
|
|
1857
2042
|
privateCert: config.getOptionalString("privateKey"),
|
|
@@ -2065,7 +2250,8 @@ const factories = {
|
|
|
2065
2250
|
oidc: createOidcProvider(),
|
|
2066
2251
|
onelogin: createOneLoginProvider(),
|
|
2067
2252
|
awsalb: createAwsAlbProvider(),
|
|
2068
|
-
bitbucket: createBitbucketProvider()
|
|
2253
|
+
bitbucket: createBitbucketProvider(),
|
|
2254
|
+
atlassian: createAtlassianProvider()
|
|
2069
2255
|
};
|
|
2070
2256
|
|
|
2071
2257
|
function createOidcRouter(options) {
|
|
@@ -2284,6 +2470,121 @@ class DatabaseKeyStore {
|
|
|
2284
2470
|
}
|
|
2285
2471
|
}
|
|
2286
2472
|
|
|
2473
|
+
class MemoryKeyStore {
|
|
2474
|
+
constructor() {
|
|
2475
|
+
this.keys = new Map();
|
|
2476
|
+
}
|
|
2477
|
+
async addKey(key) {
|
|
2478
|
+
this.keys.set(key.kid, {
|
|
2479
|
+
createdAt: luxon.DateTime.utc().toJSDate(),
|
|
2480
|
+
key: JSON.stringify(key)
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
async removeKeys(kids) {
|
|
2484
|
+
for (const kid of kids) {
|
|
2485
|
+
this.keys.delete(kid);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
async listKeys() {
|
|
2489
|
+
return {
|
|
2490
|
+
items: Array.from(this.keys).map(([, {createdAt, key: keyStr}]) => ({
|
|
2491
|
+
createdAt,
|
|
2492
|
+
key: JSON.parse(keyStr)
|
|
2493
|
+
}))
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
2499
|
+
const DEFAULT_DOCUMENT_PATH = "sessions";
|
|
2500
|
+
class FirestoreKeyStore {
|
|
2501
|
+
constructor(database, path, timeout) {
|
|
2502
|
+
this.database = database;
|
|
2503
|
+
this.path = path;
|
|
2504
|
+
this.timeout = timeout;
|
|
2505
|
+
}
|
|
2506
|
+
static async create(settings) {
|
|
2507
|
+
const {path, timeout, ...firestoreSettings} = settings != null ? settings : {};
|
|
2508
|
+
const database = new firestore.Firestore(firestoreSettings);
|
|
2509
|
+
return new FirestoreKeyStore(database, path != null ? path : DEFAULT_DOCUMENT_PATH, timeout != null ? timeout : DEFAULT_TIMEOUT_MS);
|
|
2510
|
+
}
|
|
2511
|
+
static async verifyConnection(keyStore, logger) {
|
|
2512
|
+
try {
|
|
2513
|
+
await keyStore.verify();
|
|
2514
|
+
} catch (error) {
|
|
2515
|
+
if (process.env.NODE_ENV !== "development") {
|
|
2516
|
+
throw new Error(`Failed to connect to database: ${error.message}`);
|
|
2517
|
+
}
|
|
2518
|
+
logger == null ? void 0 : logger.warn(`Failed to connect to database: ${error.message}`);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
async addKey(key) {
|
|
2522
|
+
await this.withTimeout(this.database.collection(this.path).doc(key.kid).set({
|
|
2523
|
+
kid: key.kid,
|
|
2524
|
+
key: JSON.stringify(key)
|
|
2525
|
+
}));
|
|
2526
|
+
}
|
|
2527
|
+
async listKeys() {
|
|
2528
|
+
const keys = await this.withTimeout(this.database.collection(this.path).get());
|
|
2529
|
+
return {
|
|
2530
|
+
items: keys.docs.map((key) => ({
|
|
2531
|
+
key: key.data(),
|
|
2532
|
+
createdAt: key.createTime.toDate()
|
|
2533
|
+
}))
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
async removeKeys(kids) {
|
|
2537
|
+
for (const kid of kids) {
|
|
2538
|
+
await this.withTimeout(this.database.collection(this.path).doc(kid).delete());
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
async withTimeout(operation) {
|
|
2542
|
+
const timer = new Promise((_, reject) => setTimeout(() => {
|
|
2543
|
+
reject(new Error(`Operation timed out after ${this.timeout}ms`));
|
|
2544
|
+
}, this.timeout));
|
|
2545
|
+
return Promise.race([operation, timer]);
|
|
2546
|
+
}
|
|
2547
|
+
async verify() {
|
|
2548
|
+
await this.withTimeout(this.database.collection(this.path).limit(1).get());
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
class KeyStores {
|
|
2553
|
+
static async fromConfig(config, options) {
|
|
2554
|
+
var _a;
|
|
2555
|
+
const {logger, database} = options != null ? options : {};
|
|
2556
|
+
const ks = config.getOptionalConfig("auth.keyStore");
|
|
2557
|
+
const provider = (_a = ks == null ? void 0 : ks.getOptionalString("provider")) != null ? _a : "database";
|
|
2558
|
+
logger == null ? void 0 : logger.info(`Configuring "${provider}" as KeyStore provider`);
|
|
2559
|
+
if (provider === "database") {
|
|
2560
|
+
if (!database) {
|
|
2561
|
+
throw new Error("This KeyStore provider requires a database");
|
|
2562
|
+
}
|
|
2563
|
+
return await DatabaseKeyStore.create({
|
|
2564
|
+
database: await database.getClient()
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2567
|
+
if (provider === "memory") {
|
|
2568
|
+
return new MemoryKeyStore();
|
|
2569
|
+
}
|
|
2570
|
+
if (provider === "firestore") {
|
|
2571
|
+
const settings = ks == null ? void 0 : ks.getConfig(provider);
|
|
2572
|
+
const keyStore = await FirestoreKeyStore.create(lodash.pickBy({
|
|
2573
|
+
projectId: settings == null ? void 0 : settings.getOptionalString("projectId"),
|
|
2574
|
+
keyFilename: settings == null ? void 0 : settings.getOptionalString("keyFilename"),
|
|
2575
|
+
host: settings == null ? void 0 : settings.getOptionalString("host"),
|
|
2576
|
+
port: settings == null ? void 0 : settings.getOptionalNumber("port"),
|
|
2577
|
+
ssl: settings == null ? void 0 : settings.getOptionalBoolean("ssl"),
|
|
2578
|
+
path: settings == null ? void 0 : settings.getOptionalString("path"),
|
|
2579
|
+
timeout: settings == null ? void 0 : settings.getOptionalNumber("timeout")
|
|
2580
|
+
}, (value) => value !== void 0));
|
|
2581
|
+
await FirestoreKeyStore.verifyConnection(keyStore, logger);
|
|
2582
|
+
return keyStore;
|
|
2583
|
+
}
|
|
2584
|
+
throw new Error(`Unknown KeyStore provider: ${provider}`);
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2287
2588
|
async function createRouter({
|
|
2288
2589
|
logger,
|
|
2289
2590
|
config,
|
|
@@ -2294,10 +2595,8 @@ async function createRouter({
|
|
|
2294
2595
|
const router = Router__default['default']();
|
|
2295
2596
|
const appUrl = config.getString("app.baseUrl");
|
|
2296
2597
|
const authUrl = await discovery.getExternalBaseUrl("auth");
|
|
2598
|
+
const keyStore = await KeyStores.fromConfig(config, {logger, database});
|
|
2297
2599
|
const keyDurationSeconds = 3600;
|
|
2298
|
-
const keyStore = await DatabaseKeyStore.create({
|
|
2299
|
-
database: await database.getClient()
|
|
2300
|
-
});
|
|
2301
2600
|
const tokenIssuer = new TokenFactory({
|
|
2302
2601
|
issuer: authUrl,
|
|
2303
2602
|
keyStore,
|
|
@@ -2348,6 +2647,7 @@ async function createRouter({
|
|
|
2348
2647
|
}
|
|
2349
2648
|
router.use(`/${providerId}`, r);
|
|
2350
2649
|
} catch (e) {
|
|
2650
|
+
errors.assertError(e);
|
|
2351
2651
|
if (process.env.NODE_ENV !== "development") {
|
|
2352
2652
|
throw new Error(`Failed to initialize ${providerId} auth provider, ${e.message}`);
|
|
2353
2653
|
}
|
|
@@ -2391,6 +2691,7 @@ exports.OAuthAdapter = OAuthAdapter;
|
|
|
2391
2691
|
exports.OAuthEnvironmentHandler = OAuthEnvironmentHandler;
|
|
2392
2692
|
exports.bitbucketUserIdSignInResolver = bitbucketUserIdSignInResolver;
|
|
2393
2693
|
exports.bitbucketUsernameSignInResolver = bitbucketUsernameSignInResolver;
|
|
2694
|
+
exports.createAtlassianProvider = createAtlassianProvider;
|
|
2394
2695
|
exports.createAwsAlbProvider = createAwsAlbProvider;
|
|
2395
2696
|
exports.createBitbucketProvider = createBitbucketProvider;
|
|
2396
2697
|
exports.createGithubProvider = createGithubProvider;
|