@backstage/plugin-auth-backend 0.15.0-next.3 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @backstage/plugin-auth-backend
2
2
 
3
+ ## 0.15.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9d4040777e: **BREAKING**: Removed all directly exported auth provider factories, option types, and sign-in resolvers. For example: `AwsAlbProviderOptions`, `bitbucketUserIdSignInResolver`, `createGithubProvider`. These are all still accessible via the `providers` export. For example, use `providers.github.create()` rather than `createGithubProvider()`, and `providers.bitbucket.resolvers.userIdMatchingUserEntityAnnotation()` rather than `bitbucketUserIdSignInResolver`.
8
+
9
+ **BREAKING**: Removed the exported `AuthProviderFactoryOptions` type as well as the deprecated option fields of the `AuthProviderFactory` callback. This includes the `tokenManager`, `tokenIssuer`, `discovery`, and `catalogApi` fields. Existing usage of these should be replaced with the new utilities in the `resolverContext` field. The deprecated `TokenIssuer` type is now also removed, since it is no longer used.
10
+
11
+ **BREAKING**: Removed `getEntityClaims`, use `getDefaultOwnershipEntityRefs` instead.
12
+
13
+ **DEPRECATION**: Deprecated `AtlassianAuthProvider` as it was unintentionally exported.
14
+
15
+ - fe8e025af5: Allowed post method on /refresh path
16
+
17
+ ### Patch Changes
18
+
19
+ - 3cedfd8365: add Cloudflare Access auth provider to auth-backend
20
+ - f2cf79d62e: Added an option for the auth backend router to select the algorithm for the JWT token signing keys
21
+ - 8e03db907a: Auth provider now also export createAuthProviderIntegration
22
+ - a70869e775: Updated dependency `msw` to `^0.43.0`.
23
+ - 4e9a90e307: Updated dependency `luxon` to `^3.0.0`.
24
+ - 8006d0f9bf: Updated dependency `msw` to `^0.44.0`.
25
+ - 679b32172e: Updated dependency `knex` to `^2.0.0`.
26
+ - 859346bfbb: Updated dependency `google-auth-library` to `^8.0.0`.
27
+ - 3a014730dc: Add new config option for okta auth server and IDP
28
+ - Updated dependencies
29
+ - @backstage/backend-common@0.14.1
30
+ - @backstage/catalog-model@1.1.0
31
+ - @backstage/catalog-client@1.0.4
32
+ - @backstage/plugin-auth-node@0.2.3
33
+ - @backstage/errors@1.1.0
34
+
3
35
  ## 0.15.0-next.3
4
36
 
5
37
  ### Minor Changes
package/config.d.ts CHANGED
@@ -116,6 +116,9 @@ export interface Config {
116
116
  issuer?: string;
117
117
  region: string;
118
118
  };
119
+ cfaccess?: {
120
+ teamName: string;
121
+ };
119
122
  };
120
123
  };
121
124
  }
package/dist/index.cjs.js CHANGED
@@ -995,6 +995,143 @@ const bitbucket = createAuthProviderIntegration({
995
995
  }
996
996
  });
997
997
 
998
+ const commonByEmailLocalPartResolver = async (info, ctx) => {
999
+ const { profile } = info;
1000
+ if (!profile.email) {
1001
+ throw new Error("Login failed, user profile does not contain an email");
1002
+ }
1003
+ const [localPart] = profile.email.split("@");
1004
+ return ctx.signInWithCatalogUser({
1005
+ entityRef: { name: localPart }
1006
+ });
1007
+ };
1008
+ const commonByEmailResolver = async (info, ctx) => {
1009
+ const { profile } = info;
1010
+ if (!profile.email) {
1011
+ throw new Error("Login failed, user profile does not contain an email");
1012
+ }
1013
+ return ctx.signInWithCatalogUser({
1014
+ filter: {
1015
+ "spec.profile.email": profile.email
1016
+ }
1017
+ });
1018
+ };
1019
+
1020
+ const CF_JWT_HEADER = "cf-access-jwt-assertion";
1021
+ const COOKIE_AUTH_NAME = "CF_Authorization";
1022
+ const CACHE_PREFIX = "providers/cloudflare-access/profile-v1";
1023
+ class CloudflareAccessAuthProvider {
1024
+ constructor(options) {
1025
+ this.teamName = options.teamName;
1026
+ this.authHandler = options.authHandler;
1027
+ this.signInResolver = options.signInResolver;
1028
+ this.resolverContext = options.resolverContext;
1029
+ this.jwtKeySet = jose.createRemoteJWKSet(new URL(`https://${this.teamName}.cloudflareaccess.com/cdn-cgi/access/certs`));
1030
+ this.cache = options.cache;
1031
+ }
1032
+ frameHandler() {
1033
+ return Promise.resolve();
1034
+ }
1035
+ async refresh(req, res) {
1036
+ const result = await this.getResult(req);
1037
+ const response = await this.handleResult(result);
1038
+ res.json(response);
1039
+ }
1040
+ start() {
1041
+ return Promise.resolve();
1042
+ }
1043
+ async getIdentityProfile(jwt) {
1044
+ const headers = new fetch.Headers();
1045
+ headers.set(CF_JWT_HEADER, jwt);
1046
+ headers.set("cookie", `${COOKIE_AUTH_NAME}=${jwt}`);
1047
+ try {
1048
+ const res = await fetch__default["default"](`https://${this.teamName}.cloudflareaccess.com/cdn-cgi/access/get-identity`, { headers });
1049
+ if (!res.ok) {
1050
+ throw errors.ResponseError.fromResponse(res);
1051
+ }
1052
+ const cfIdentity = await res.json();
1053
+ return cfIdentity;
1054
+ } catch (err) {
1055
+ throw new errors.ForwardedError("getIdentityProfile failed", err);
1056
+ }
1057
+ }
1058
+ async getResult(req) {
1059
+ var _a, _b;
1060
+ let jwt = req.header(CF_JWT_HEADER);
1061
+ if (!jwt) {
1062
+ jwt = req.cookies.CF_Authorization;
1063
+ }
1064
+ if (!jwt) {
1065
+ throw new errors.AuthenticationError(`Missing ${CF_JWT_HEADER} from Cloudflare Access`);
1066
+ }
1067
+ const verifyResult = await jose.jwtVerify(jwt, this.jwtKeySet, {
1068
+ issuer: `https://${this.teamName}.cloudflareaccess.com`
1069
+ });
1070
+ const sub = verifyResult.payload.sub;
1071
+ const cfAccessResultStr = await ((_a = this.cache) == null ? void 0 : _a.get(`${CACHE_PREFIX}/${sub}`));
1072
+ if (typeof cfAccessResultStr === "string") {
1073
+ return JSON.parse(cfAccessResultStr);
1074
+ }
1075
+ const claims = verifyResult.payload;
1076
+ try {
1077
+ const cfIdentity = await this.getIdentityProfile(jwt);
1078
+ const cfAccessResult = {
1079
+ claims,
1080
+ cfIdentity,
1081
+ expiresInSeconds: claims.exp - claims.iat
1082
+ };
1083
+ (_b = this.cache) == null ? void 0 : _b.set(`${CACHE_PREFIX}/${sub}`, JSON.stringify(cfAccessResult));
1084
+ return cfAccessResult;
1085
+ } catch (err) {
1086
+ throw new errors.ForwardedError("Failed to populate access identity information", err);
1087
+ }
1088
+ }
1089
+ async handleResult(result) {
1090
+ const { profile } = await this.authHandler(result, this.resolverContext);
1091
+ const backstageIdentity = await this.signInResolver({
1092
+ result,
1093
+ profile
1094
+ }, this.resolverContext);
1095
+ return {
1096
+ providerInfo: {
1097
+ expiresInSeconds: result.expiresInSeconds,
1098
+ claims: result.claims,
1099
+ cfAccessIdentityProfile: result.cfIdentity
1100
+ },
1101
+ backstageIdentity: prepareBackstageIdentityResponse(backstageIdentity),
1102
+ profile
1103
+ };
1104
+ }
1105
+ }
1106
+ const cfAccess = createAuthProviderIntegration({
1107
+ create(options) {
1108
+ return ({ config, resolverContext }) => {
1109
+ const teamName = config.getString("teamName");
1110
+ if (!options.signIn.resolver) {
1111
+ throw new Error("SignInResolver is required to use this authentication provider");
1112
+ }
1113
+ const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ claims, cfIdentity }) => {
1114
+ return {
1115
+ profile: {
1116
+ email: claims.email,
1117
+ displayName: cfIdentity.name
1118
+ }
1119
+ };
1120
+ };
1121
+ return new CloudflareAccessAuthProvider({
1122
+ teamName,
1123
+ signInResolver: options == null ? void 0 : options.signIn.resolver,
1124
+ authHandler,
1125
+ resolverContext,
1126
+ ...options.cache && { cache: options.cache }
1127
+ });
1128
+ };
1129
+ },
1130
+ resolvers: {
1131
+ emailMatchingUserEntityProfileEmail: () => commonByEmailResolver
1132
+ }
1133
+ });
1134
+
998
1135
  const IAP_JWT_HEADER = "x-goog-iap-jwt-assertion";
999
1136
 
1000
1137
  function createTokenValidator(audience, mockClient) {
@@ -1314,28 +1451,6 @@ const gitlab = createAuthProviderIntegration({
1314
1451
  }
1315
1452
  });
1316
1453
 
1317
- const commonByEmailLocalPartResolver = async (info, ctx) => {
1318
- const { profile } = info;
1319
- if (!profile.email) {
1320
- throw new Error("Login failed, user profile does not contain an email");
1321
- }
1322
- const [localPart] = profile.email.split("@");
1323
- return ctx.signInWithCatalogUser({
1324
- entityRef: { name: localPart }
1325
- });
1326
- };
1327
- const commonByEmailResolver = async (info, ctx) => {
1328
- const { profile } = info;
1329
- if (!profile.email) {
1330
- throw new Error("Login failed, user profile does not contain an email");
1331
- }
1332
- return ctx.signInWithCatalogUser({
1333
- filter: {
1334
- "spec.profile.email": profile.email
1335
- }
1336
- });
1337
- };
1338
-
1339
1454
  class GoogleAuthProvider {
1340
1455
  constructor(options) {
1341
1456
  this.authHandler = options.authHandler;
@@ -2221,6 +2336,7 @@ const providers = Object.freeze({
2221
2336
  auth0,
2222
2337
  awsAlb,
2223
2338
  bitbucket,
2339
+ cfAccess,
2224
2340
  gcpIap,
2225
2341
  github,
2226
2342
  gitlab,