@backstage/plugin-auth-backend 0.4.9 → 0.4.10

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @backstage/plugin-auth-backend
2
2
 
3
+ ## 0.4.10
4
+
5
+ ### Patch Changes
6
+
7
+ - 4bf4111902: Migrated the SAML provider to implement the `authHandler` and `signIn.resolver` options.
8
+ - b055a6addc: Align on usage of `cross-fetch` vs `node-fetch` in frontend vs backend packages, and remove some unnecessary imports of either one of them
9
+ - 36fa32216f: Added signIn and authHandler resolver for oidc provider
10
+ - 7071dce02d: Expose catalog lib in plugin-auth-backend, i.e `CatalogIdentityClient` class is exposed now.
11
+ - 1b69ed44f2: Added custom OAuth2.0 authorization header for generic oauth2 provider.
12
+ - Updated dependencies
13
+ - @backstage/backend-common@0.9.12
14
+
3
15
  ## 0.4.9
4
16
 
5
17
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -17,12 +17,12 @@ var passportGoogleOauth20 = require('passport-google-oauth20');
17
17
  var passportMicrosoft = require('passport-microsoft');
18
18
  var got = require('got');
19
19
  var OAuth2Strategy = require('passport-oauth2');
20
+ var openidClient = require('openid-client');
20
21
  var passportOktaOauth = require('passport-okta-oauth');
21
22
  var passportBitbucketOauth2 = require('passport-bitbucket-oauth2');
22
- var fetch = require('cross-fetch');
23
+ var fetch = require('node-fetch');
23
24
  var NodeCache = require('node-cache');
24
25
  var jose = require('jose');
25
- var openidClient = require('openid-client');
26
26
  var passportSaml = require('passport-saml');
27
27
  var passportOneloginOauth = require('passport-onelogin-oauth');
28
28
  var catalogClient = require('@backstage/catalog-client');
@@ -1134,7 +1134,10 @@ class OAuth2AuthProvider {
1134
1134
  authorizationURL: options.authorizationUrl,
1135
1135
  tokenURL: options.tokenUrl,
1136
1136
  passReqToCallback: false,
1137
- scope: options.scope
1137
+ scope: options.scope,
1138
+ customHeaders: {
1139
+ Authorization: `Basic ${this.encodeClientCredentials(options.clientId, options.clientSecret)}`
1140
+ }
1138
1141
  }, (accessToken, refreshToken, params, fullProfile, done) => {
1139
1142
  done(void 0, {
1140
1143
  fullProfile,
@@ -1200,8 +1203,11 @@ class OAuth2AuthProvider {
1200
1203
  }
1201
1204
  return response;
1202
1205
  }
1206
+ encodeClientCredentials(clientID, clientSecret) {
1207
+ return Buffer.from(`${clientID}:${clientSecret}`).toString("base64");
1208
+ }
1203
1209
  }
1204
- const oAuth2DefaultSignInResolver = async (info, ctx) => {
1210
+ const oAuth2DefaultSignInResolver$1 = async (info, ctx) => {
1205
1211
  const {profile} = info;
1206
1212
  if (!profile.email) {
1207
1213
  throw new Error("Profile contained no email");
@@ -1236,7 +1242,7 @@ const createOAuth2Provider = (options) => {
1236
1242
  const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({fullProfile, params}) => ({
1237
1243
  profile: makeProfileInfo(fullProfile, params.id_token)
1238
1244
  });
1239
- const signInResolverFn = (_c = (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver) != null ? _c : oAuth2DefaultSignInResolver;
1245
+ const signInResolverFn = (_c = (_b = options == null ? void 0 : options.signIn) == null ? void 0 : _b.resolver) != null ? _c : oAuth2DefaultSignInResolver$1;
1240
1246
  const signInResolver = (info) => signInResolverFn(info, {
1241
1247
  catalogIdentityClient,
1242
1248
  tokenIssuer,
@@ -1263,6 +1269,168 @@ const createOAuth2Provider = (options) => {
1263
1269
  });
1264
1270
  };
1265
1271
 
1272
+ class OidcAuthProvider {
1273
+ constructor(options) {
1274
+ this.implementation = this.setupStrategy(options);
1275
+ this.scope = options.scope;
1276
+ this.prompt = options.prompt;
1277
+ this.signInResolver = options.signInResolver;
1278
+ this.authHandler = options.authHandler;
1279
+ this.tokenIssuer = options.tokenIssuer;
1280
+ this.catalogIdentityClient = options.catalogIdentityClient;
1281
+ this.logger = options.logger;
1282
+ }
1283
+ async start(req) {
1284
+ const {strategy} = await this.implementation;
1285
+ const options = {
1286
+ scope: req.scope || this.scope || "openid profile email",
1287
+ state: encodeState(req.state)
1288
+ };
1289
+ const prompt = this.prompt || "none";
1290
+ if (prompt !== "auto") {
1291
+ options.prompt = prompt;
1292
+ }
1293
+ return await executeRedirectStrategy(req, strategy, options);
1294
+ }
1295
+ async handler(req) {
1296
+ const {strategy} = await this.implementation;
1297
+ const strategyResponse = await executeFrameHandlerStrategy(req, strategy);
1298
+ const {
1299
+ result: {userinfo, tokenset},
1300
+ privateInfo
1301
+ } = strategyResponse;
1302
+ const identityResponse = await this.handleResult({tokenset, userinfo});
1303
+ return {
1304
+ response: identityResponse,
1305
+ refreshToken: privateInfo.refreshToken
1306
+ };
1307
+ }
1308
+ async refresh(req) {
1309
+ const {client} = await this.implementation;
1310
+ const tokenset = await client.refresh(req.refreshToken);
1311
+ if (!tokenset.access_token) {
1312
+ throw new Error("Refresh failed");
1313
+ }
1314
+ const profile = await client.userinfo(tokenset.access_token);
1315
+ return this.handleResult({tokenset, userinfo: profile});
1316
+ }
1317
+ async setupStrategy(options) {
1318
+ const issuer = await openidClient.Issuer.discover(options.metadataUrl);
1319
+ const client = new issuer.Client({
1320
+ access_type: "offline",
1321
+ client_id: options.clientId,
1322
+ client_secret: options.clientSecret,
1323
+ redirect_uris: [options.callbackUrl],
1324
+ response_types: ["code"],
1325
+ id_token_signed_response_alg: options.tokenSignedResponseAlg || "RS256",
1326
+ scope: options.scope || ""
1327
+ });
1328
+ const strategy = new openidClient.Strategy({
1329
+ client,
1330
+ passReqToCallback: false
1331
+ }, (tokenset, userinfo, done) => {
1332
+ if (typeof done !== "function") {
1333
+ throw new Error("OIDC IdP must provide a userinfo_endpoint in the metadata response");
1334
+ }
1335
+ done(void 0, {tokenset, userinfo}, {
1336
+ refreshToken: tokenset.refresh_token
1337
+ });
1338
+ });
1339
+ strategy.error = console.error;
1340
+ return {strategy, client};
1341
+ }
1342
+ async handleResult(result) {
1343
+ const {profile} = await this.authHandler(result);
1344
+ const response = {
1345
+ providerInfo: {
1346
+ idToken: result.tokenset.id_token,
1347
+ accessToken: result.tokenset.access_token,
1348
+ refreshToken: result.tokenset.refresh_token,
1349
+ scope: result.tokenset.scope,
1350
+ expiresInSeconds: result.tokenset.expires_in
1351
+ },
1352
+ profile
1353
+ };
1354
+ if (this.signInResolver) {
1355
+ response.backstageIdentity = await this.signInResolver({
1356
+ result,
1357
+ profile
1358
+ }, {
1359
+ tokenIssuer: this.tokenIssuer,
1360
+ catalogIdentityClient: this.catalogIdentityClient,
1361
+ logger: this.logger
1362
+ });
1363
+ }
1364
+ return response;
1365
+ }
1366
+ }
1367
+ const oAuth2DefaultSignInResolver = async (info, ctx) => {
1368
+ const {profile} = info;
1369
+ if (!profile.email) {
1370
+ throw new Error("Profile contained no email");
1371
+ }
1372
+ const userId = profile.email.split("@")[0];
1373
+ const token = await ctx.tokenIssuer.issueToken({
1374
+ claims: {sub: userId, ent: [`user:default/${userId}`]}
1375
+ });
1376
+ return {id: userId, token};
1377
+ };
1378
+ const createOidcProvider = (options) => {
1379
+ return ({
1380
+ providerId,
1381
+ globalConfig,
1382
+ config,
1383
+ tokenIssuer,
1384
+ catalogApi,
1385
+ logger
1386
+ }) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
1387
+ var _a, _b;
1388
+ const clientId = envConfig.getString("clientId");
1389
+ const clientSecret = envConfig.getString("clientSecret");
1390
+ const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1391
+ const metadataUrl = envConfig.getString("metadataUrl");
1392
+ const tokenSignedResponseAlg = envConfig.getOptionalString("tokenSignedResponseAlg");
1393
+ const scope = envConfig.getOptionalString("scope");
1394
+ const prompt = envConfig.getOptionalString("prompt");
1395
+ const catalogIdentityClient = new CatalogIdentityClient({
1396
+ catalogApi,
1397
+ tokenIssuer
1398
+ });
1399
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({userinfo}) => ({
1400
+ profile: {
1401
+ displayName: userinfo.name,
1402
+ email: userinfo.email,
1403
+ picture: userinfo.picture
1404
+ }
1405
+ });
1406
+ const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : oAuth2DefaultSignInResolver;
1407
+ const signInResolver = (info) => signInResolverFn(info, {
1408
+ catalogIdentityClient,
1409
+ tokenIssuer,
1410
+ logger
1411
+ });
1412
+ const provider = new OidcAuthProvider({
1413
+ clientId,
1414
+ clientSecret,
1415
+ callbackUrl,
1416
+ tokenSignedResponseAlg,
1417
+ metadataUrl,
1418
+ scope,
1419
+ prompt,
1420
+ signInResolver,
1421
+ authHandler,
1422
+ logger,
1423
+ tokenIssuer,
1424
+ catalogIdentityClient
1425
+ });
1426
+ return OAuthAdapter.fromConfig(globalConfig, provider, {
1427
+ disableRefresh: false,
1428
+ providerId,
1429
+ tokenIssuer
1430
+ });
1431
+ });
1432
+ };
1433
+
1266
1434
  class OktaAuthProvider {
1267
1435
  constructor(options) {
1268
1436
  this._store = {
@@ -1858,131 +2026,14 @@ const createAwsAlbProvider = (options) => {
1858
2026
  };
1859
2027
  };
1860
2028
 
1861
- class OidcAuthProvider {
1862
- constructor(options) {
1863
- this.implementation = this.setupStrategy(options);
1864
- this.scope = options.scope;
1865
- this.prompt = options.prompt;
1866
- }
1867
- async start(req) {
1868
- const {strategy} = await this.implementation;
1869
- const options = {
1870
- accessType: "offline",
1871
- scope: req.scope || this.scope || "openid profile email",
1872
- state: encodeState(req.state)
1873
- };
1874
- const prompt = this.prompt || "none";
1875
- if (prompt !== "auto") {
1876
- options.prompt = prompt;
1877
- }
1878
- return await executeRedirectStrategy(req, strategy, options);
1879
- }
1880
- async handler(req) {
1881
- const {strategy} = await this.implementation;
1882
- const strategyResponse = await executeFrameHandlerStrategy(req, strategy);
1883
- const {
1884
- result: {userinfo, tokenset},
1885
- privateInfo
1886
- } = strategyResponse;
1887
- const identityResponse = await this.populateIdentity({
1888
- profile: {
1889
- displayName: userinfo.name,
1890
- email: userinfo.email,
1891
- picture: userinfo.picture
1892
- },
1893
- providerInfo: {
1894
- idToken: tokenset.id_token,
1895
- accessToken: tokenset.access_token || "",
1896
- scope: tokenset.scope || "",
1897
- expiresInSeconds: tokenset.expires_in
1898
- }
1899
- });
1900
- return {
1901
- response: identityResponse,
1902
- refreshToken: privateInfo.refreshToken
1903
- };
1904
- }
1905
- async refresh(req) {
1906
- const {client} = await this.implementation;
1907
- const tokenset = await client.refresh(req.refreshToken);
1908
- if (!tokenset.access_token) {
1909
- throw new Error("Refresh failed");
1910
- }
1911
- const profile = await client.userinfo(tokenset.access_token);
1912
- return this.populateIdentity({
1913
- providerInfo: {
1914
- accessToken: tokenset.access_token,
1915
- refreshToken: tokenset.refresh_token,
1916
- expiresInSeconds: tokenset.expires_in,
1917
- idToken: tokenset.id_token,
1918
- scope: tokenset.scope || ""
1919
- },
1920
- profile
1921
- });
1922
- }
1923
- async setupStrategy(options) {
1924
- const issuer = await openidClient.Issuer.discover(options.metadataUrl);
1925
- const client = new issuer.Client({
1926
- client_id: options.clientId,
1927
- client_secret: options.clientSecret,
1928
- redirect_uris: [options.callbackUrl],
1929
- response_types: ["code"],
1930
- id_token_signed_response_alg: options.tokenSignedResponseAlg || "RS256",
1931
- scope: options.scope || ""
1932
- });
1933
- const strategy = new openidClient.Strategy({
1934
- client,
1935
- passReqToCallback: false
1936
- }, (tokenset, userinfo, done) => {
1937
- if (typeof done !== "function") {
1938
- throw new Error("OIDC IdP must provide a userinfo_endpoint in the metadata response");
1939
- }
1940
- done(void 0, {tokenset, userinfo}, {
1941
- refreshToken: tokenset.refresh_token
1942
- });
1943
- });
1944
- strategy.error = console.error;
1945
- return {strategy, client};
1946
- }
1947
- async populateIdentity(response) {
1948
- const {profile} = response;
1949
- if (!profile.email) {
1950
- throw new Error("Profile does not contain an email");
1951
- }
1952
- const id = profile.email.split("@")[0];
1953
- return {...response, backstageIdentity: {id}};
1954
- }
1955
- }
1956
- const createOidcProvider = (_options) => {
1957
- return ({providerId, globalConfig, config, tokenIssuer}) => OAuthEnvironmentHandler.mapConfig(config, (envConfig) => {
1958
- const clientId = envConfig.getString("clientId");
1959
- const clientSecret = envConfig.getString("clientSecret");
1960
- const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
1961
- const metadataUrl = envConfig.getString("metadataUrl");
1962
- const tokenSignedResponseAlg = envConfig.getOptionalString("tokenSignedResponseAlg");
1963
- const scope = envConfig.getOptionalString("scope");
1964
- const prompt = envConfig.getOptionalString("prompt");
1965
- const provider = new OidcAuthProvider({
1966
- clientId,
1967
- clientSecret,
1968
- callbackUrl,
1969
- tokenSignedResponseAlg,
1970
- metadataUrl,
1971
- scope,
1972
- prompt
1973
- });
1974
- return OAuthAdapter.fromConfig(globalConfig, provider, {
1975
- disableRefresh: false,
1976
- providerId,
1977
- tokenIssuer
1978
- });
1979
- });
1980
- };
1981
-
1982
2029
  class SamlAuthProvider {
1983
2030
  constructor(options) {
1984
2031
  this.appUrl = options.appUrl;
2032
+ this.signInResolver = options.signInResolver;
2033
+ this.authHandler = options.authHandler;
1985
2034
  this.tokenIssuer = options.tokenIssuer;
2035
+ this.catalogIdentityClient = options.catalogIdentityClient;
2036
+ this.logger = options.logger;
1986
2037
  this.strategy = new passportSaml.Strategy({...options}, (fullProfile, done) => {
1987
2038
  done(void 0, {fullProfile});
1988
2039
  });
@@ -1994,20 +2045,24 @@ class SamlAuthProvider {
1994
2045
  async frameHandler(req, res) {
1995
2046
  try {
1996
2047
  const {result} = await executeFrameHandlerStrategy(req, this.strategy);
1997
- const id = result.fullProfile.nameID;
1998
- const idToken = await this.tokenIssuer.issueToken({
1999
- claims: {sub: id}
2000
- });
2048
+ const {profile} = await this.authHandler(result);
2049
+ const response = {
2050
+ profile,
2051
+ providerInfo: {}
2052
+ };
2053
+ if (this.signInResolver) {
2054
+ response.backstageIdentity = await this.signInResolver({
2055
+ result,
2056
+ profile
2057
+ }, {
2058
+ tokenIssuer: this.tokenIssuer,
2059
+ catalogIdentityClient: this.catalogIdentityClient,
2060
+ logger: this.logger
2061
+ });
2062
+ }
2001
2063
  return postMessageResponse(res, this.appUrl, {
2002
2064
  type: "authorization_response",
2003
- response: {
2004
- profile: {
2005
- email: result.fullProfile.email,
2006
- displayName: result.fullProfile.displayName
2007
- },
2008
- providerInfo: {},
2009
- backstageIdentity: {id, idToken}
2010
- }
2065
+ response
2011
2066
  });
2012
2067
  } catch (error) {
2013
2068
  const {name, message} = errors.isError(error) ? error : new Error("Encountered invalid error");
@@ -2018,22 +2073,50 @@ class SamlAuthProvider {
2018
2073
  }
2019
2074
  }
2020
2075
  async logout(_req, res) {
2021
- res.send("noop");
2022
- }
2023
- identifyEnv() {
2024
- return void 0;
2076
+ res.end();
2025
2077
  }
2026
2078
  }
2027
- const createSamlProvider = (_options) => {
2028
- return ({providerId, globalConfig, config, tokenIssuer}) => {
2029
- const opts = {
2079
+ const samlDefaultSignInResolver = async (info, ctx) => {
2080
+ const id = info.result.fullProfile.nameID;
2081
+ const token = await ctx.tokenIssuer.issueToken({
2082
+ claims: {sub: id}
2083
+ });
2084
+ return {id, token};
2085
+ };
2086
+ const createSamlProvider = (options) => {
2087
+ return ({
2088
+ providerId,
2089
+ globalConfig,
2090
+ config,
2091
+ tokenIssuer,
2092
+ catalogApi,
2093
+ logger
2094
+ }) => {
2095
+ var _a, _b;
2096
+ const catalogIdentityClient = new CatalogIdentityClient({
2097
+ catalogApi,
2098
+ tokenIssuer
2099
+ });
2100
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({fullProfile}) => ({
2101
+ profile: {
2102
+ email: fullProfile.email,
2103
+ displayName: fullProfile.displayName
2104
+ }
2105
+ });
2106
+ const signInResolverFn = (_b = (_a = options == null ? void 0 : options.signIn) == null ? void 0 : _a.resolver) != null ? _b : samlDefaultSignInResolver;
2107
+ const signInResolver = (info) => signInResolverFn(info, {
2108
+ catalogIdentityClient,
2109
+ tokenIssuer,
2110
+ logger
2111
+ });
2112
+ return new SamlAuthProvider({
2030
2113
  callbackUrl: `${globalConfig.baseUrl}/${providerId}/handler/frame`,
2031
2114
  entryPoint: config.getString("entryPoint"),
2032
2115
  logoutUrl: config.getOptionalString("logoutUrl"),
2033
2116
  audience: config.getOptionalString("audience"),
2034
2117
  issuer: config.getString("issuer"),
2035
2118
  cert: config.getString("cert"),
2036
- privateCert: config.getOptionalString("privateKey"),
2119
+ privateKey: config.getOptionalString("privateKey"),
2037
2120
  authnContext: config.getOptionalStringArray("authnContext"),
2038
2121
  identifierFormat: config.getOptionalString("identifierFormat"),
2039
2122
  decryptionPvk: config.getOptionalString("decryptionPvk"),
@@ -2041,9 +2124,12 @@ const createSamlProvider = (_options) => {
2041
2124
  digestAlgorithm: config.getOptionalString("digestAlgorithm"),
2042
2125
  acceptedClockSkewMs: config.getOptionalNumber("acceptedClockSkewMs"),
2043
2126
  tokenIssuer,
2044
- appUrl: globalConfig.appUrl
2045
- };
2046
- return new SamlAuthProvider(opts);
2127
+ appUrl: globalConfig.appUrl,
2128
+ authHandler,
2129
+ signInResolver,
2130
+ logger,
2131
+ catalogIdentityClient
2132
+ });
2047
2133
  };
2048
2134
  };
2049
2135
 
@@ -2680,6 +2766,7 @@ function createOriginFilter(config) {
2680
2766
  };
2681
2767
  }
2682
2768
 
2769
+ exports.CatalogIdentityClient = CatalogIdentityClient;
2683
2770
  exports.IdentityClient = IdentityClient;
2684
2771
  exports.OAuthAdapter = OAuthAdapter;
2685
2772
  exports.OAuthEnvironmentHandler = OAuthEnvironmentHandler;
@@ -2693,12 +2780,15 @@ exports.createGitlabProvider = createGitlabProvider;
2693
2780
  exports.createGoogleProvider = createGoogleProvider;
2694
2781
  exports.createMicrosoftProvider = createMicrosoftProvider;
2695
2782
  exports.createOAuth2Provider = createOAuth2Provider;
2783
+ exports.createOidcProvider = createOidcProvider;
2696
2784
  exports.createOktaProvider = createOktaProvider;
2697
2785
  exports.createOriginFilter = createOriginFilter;
2698
2786
  exports.createRouter = createRouter;
2787
+ exports.createSamlProvider = createSamlProvider;
2699
2788
  exports.defaultAuthProviderFactories = factories;
2700
2789
  exports.encodeState = encodeState;
2701
2790
  exports.ensuresXRequestedWith = ensuresXRequestedWith;
2791
+ exports.getEntityClaims = getEntityClaims;
2702
2792
  exports.googleEmailSignInResolver = googleEmailSignInResolver;
2703
2793
  exports.microsoftEmailSignInResolver = microsoftEmailSignInResolver;
2704
2794
  exports.oktaEmailSignInResolver = oktaEmailSignInResolver;