@backstage/plugin-auth-node 0.6.3-next.0 → 0.6.3-next.2

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,68 @@
1
1
  # @backstage/plugin-auth-node
2
2
 
3
+ ## 0.6.3-next.2
4
+
5
+ ### Patch Changes
6
+
7
+ - ab53e6f: Added a new `dangerousEntityRefFallback` option to the `signInWithCatalogUser` method in `AuthResolverContext`. The option will cause the provided entity reference to be used as a fallback in case the user is not found in the catalog. It is up to the caller to provide the fallback entity reference.
8
+
9
+ Auth providers that include pre-defined sign-in resolvers are encouraged to define a flag named `dangerouslyAllowSignInWithoutUserInCatalog` in their config, which in turn enables use of the `dangerousEntityRefFallback` option. For example:
10
+
11
+ ```ts
12
+ export const usernameMatchingUserEntityName = createSignInResolverFactory({
13
+ optionsSchema: z
14
+ .object({
15
+ dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(),
16
+ })
17
+ .optional(),
18
+ create(options = {}) {
19
+ return async (
20
+ info: SignInInfo<OAuthAuthenticatorResult<PassportProfile>>,
21
+ ctx,
22
+ ) => {
23
+ const { username } = info.result.fullProfile;
24
+ if (!username) {
25
+ throw new Error('User profile does not contain a username');
26
+ }
27
+
28
+ return ctx.signInWithCatalogUser(
29
+ { entityRef: { name: username } },
30
+ {
31
+ dangerousEntityRefFallback:
32
+ options?.dangerouslyAllowSignInWithoutUserInCatalog
33
+ ? { entityRef: { name: username } }
34
+ : undefined,
35
+ },
36
+ );
37
+ };
38
+ },
39
+ });
40
+ ```
41
+
42
+ - Updated dependencies
43
+ - @backstage/backend-plugin-api@1.3.1-next.2
44
+ - @backstage/catalog-client@1.10.0-next.0
45
+ - @backstage/catalog-model@1.7.3
46
+ - @backstage/config@1.3.2
47
+ - @backstage/errors@1.2.7
48
+ - @backstage/types@1.2.1
49
+
50
+ ## 0.6.3-next.1
51
+
52
+ ### Patch Changes
53
+
54
+ - 332e934: Added the `identity` property to `BackstageSignInResult`.
55
+
56
+ The `prepareBackstageIdentityResponse` function will now also forward the `identity` to the response if present in the provided sign-in result.
57
+
58
+ - Updated dependencies
59
+ - @backstage/backend-plugin-api@1.3.1-next.1
60
+ - @backstage/catalog-client@1.10.0-next.0
61
+ - @backstage/catalog-model@1.7.3
62
+ - @backstage/config@1.3.2
63
+ - @backstage/errors@1.2.7
64
+ - @backstage/types@1.2.1
65
+
3
66
  ## 0.6.3-next.0
4
67
 
5
68
  ### Patch Changes
@@ -24,7 +24,7 @@ function prepareBackstageIdentityResponse(result) {
24
24
  return {
25
25
  ...result,
26
26
  expiresInSeconds: exp,
27
- identity: {
27
+ identity: result.identity ?? {
28
28
  type: "user",
29
29
  userEntityRef: sub,
30
30
  ownershipEntityRefs: ent
@@ -1 +1 @@
1
- {"version":3,"file":"prepareBackstageIdentityResponse.cjs.js","sources":["../../src/identity/prepareBackstageIdentityResponse.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { InputError } from '@backstage/errors';\nimport {\n BackstageIdentityResponse,\n BackstageSignInResult,\n} from '@backstage/plugin-auth-node';\n\nfunction parseJwtPayload(token: string) {\n const [_header, payload, _signature] = token.split('.');\n return JSON.parse(Buffer.from(payload, 'base64').toString());\n}\n\n/**\n * Parses a Backstage-issued token and decorates the\n * {@link @backstage/plugin-auth-node#BackstageIdentityResponse} with identity information sourced from the\n * token.\n *\n * @public\n */\nexport function prepareBackstageIdentityResponse(\n result: BackstageSignInResult,\n): BackstageIdentityResponse {\n if (!result.token) {\n throw new InputError(`Identity response must return a token`);\n }\n\n const { sub, ent = [], exp: expStr } = parseJwtPayload(result.token);\n if (!sub) {\n throw new InputError(\n `Identity response must return a token with subject claim`,\n );\n }\n\n const expAt = Number(expStr);\n\n // Default to 1 hour if no expiration is set, in particular to make testing simpler\n const exp = expAt ? Math.round(expAt - Date.now() / 1000) : undefined;\n if (exp && exp < 0) {\n throw new InputError(`Identity response must not return an expired token`);\n }\n\n return {\n ...result,\n expiresInSeconds: exp,\n identity: {\n type: 'user',\n userEntityRef: sub,\n ownershipEntityRefs: ent,\n },\n };\n}\n"],"names":["InputError"],"mappings":";;;;AAsBA,SAAS,gBAAgB,KAAe,EAAA;AACtC,EAAA,MAAM,CAAC,OAAS,EAAA,OAAA,EAAS,UAAU,CAAI,GAAA,KAAA,CAAM,MAAM,GAAG,CAAA;AACtD,EAAO,OAAA,IAAA,CAAK,MAAM,MAAO,CAAA,IAAA,CAAK,SAAS,QAAQ,CAAA,CAAE,UAAU,CAAA;AAC7D;AASO,SAAS,iCACd,MAC2B,EAAA;AAC3B,EAAI,IAAA,CAAC,OAAO,KAAO,EAAA;AACjB,IAAM,MAAA,IAAIA,kBAAW,CAAuC,qCAAA,CAAA,CAAA;AAAA;AAG9D,EAAM,MAAA,EAAE,GAAK,EAAA,GAAA,GAAM,EAAC,EAAG,KAAK,MAAO,EAAA,GAAI,eAAgB,CAAA,MAAA,CAAO,KAAK,CAAA;AACnE,EAAA,IAAI,CAAC,GAAK,EAAA;AACR,IAAA,MAAM,IAAIA,iBAAA;AAAA,MACR,CAAA,wDAAA;AAAA,KACF;AAAA;AAGF,EAAM,MAAA,KAAA,GAAQ,OAAO,MAAM,CAAA;AAG3B,EAAM,MAAA,GAAA,GAAM,QAAQ,IAAK,CAAA,KAAA,CAAM,QAAQ,IAAK,CAAA,GAAA,EAAQ,GAAA,GAAI,CAAI,GAAA,KAAA,CAAA;AAC5D,EAAI,IAAA,GAAA,IAAO,MAAM,CAAG,EAAA;AAClB,IAAM,MAAA,IAAIA,kBAAW,CAAoD,kDAAA,CAAA,CAAA;AAAA;AAG3E,EAAO,OAAA;AAAA,IACL,GAAG,MAAA;AAAA,IACH,gBAAkB,EAAA,GAAA;AAAA,IAClB,QAAU,EAAA;AAAA,MACR,IAAM,EAAA,MAAA;AAAA,MACN,aAAe,EAAA,GAAA;AAAA,MACf,mBAAqB,EAAA;AAAA;AACvB,GACF;AACF;;;;"}
1
+ {"version":3,"file":"prepareBackstageIdentityResponse.cjs.js","sources":["../../src/identity/prepareBackstageIdentityResponse.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { InputError } from '@backstage/errors';\nimport {\n BackstageIdentityResponse,\n BackstageSignInResult,\n} from '@backstage/plugin-auth-node';\n\nfunction parseJwtPayload(token: string) {\n const [_header, payload, _signature] = token.split('.');\n return JSON.parse(Buffer.from(payload, 'base64').toString());\n}\n\n/**\n * Parses a Backstage-issued token and decorates the\n * {@link @backstage/plugin-auth-node#BackstageIdentityResponse} with identity information sourced from the\n * token.\n *\n * @public\n */\nexport function prepareBackstageIdentityResponse(\n result: BackstageSignInResult,\n): BackstageIdentityResponse {\n if (!result.token) {\n throw new InputError(`Identity response must return a token`);\n }\n\n const { sub, ent = [], exp: expStr } = parseJwtPayload(result.token);\n if (!sub) {\n throw new InputError(\n `Identity response must return a token with subject claim`,\n );\n }\n\n const expAt = Number(expStr);\n\n // Default to 1 hour if no expiration is set, in particular to make testing simpler\n const exp = expAt ? Math.round(expAt - Date.now() / 1000) : undefined;\n if (exp && exp < 0) {\n throw new InputError(`Identity response must not return an expired token`);\n }\n\n return {\n ...result,\n expiresInSeconds: exp,\n identity: result.identity ?? {\n type: 'user',\n userEntityRef: sub,\n ownershipEntityRefs: ent,\n },\n };\n}\n"],"names":["InputError"],"mappings":";;;;AAsBA,SAAS,gBAAgB,KAAe,EAAA;AACtC,EAAA,MAAM,CAAC,OAAS,EAAA,OAAA,EAAS,UAAU,CAAI,GAAA,KAAA,CAAM,MAAM,GAAG,CAAA;AACtD,EAAO,OAAA,IAAA,CAAK,MAAM,MAAO,CAAA,IAAA,CAAK,SAAS,QAAQ,CAAA,CAAE,UAAU,CAAA;AAC7D;AASO,SAAS,iCACd,MAC2B,EAAA;AAC3B,EAAI,IAAA,CAAC,OAAO,KAAO,EAAA;AACjB,IAAM,MAAA,IAAIA,kBAAW,CAAuC,qCAAA,CAAA,CAAA;AAAA;AAG9D,EAAM,MAAA,EAAE,GAAK,EAAA,GAAA,GAAM,EAAC,EAAG,KAAK,MAAO,EAAA,GAAI,eAAgB,CAAA,MAAA,CAAO,KAAK,CAAA;AACnE,EAAA,IAAI,CAAC,GAAK,EAAA;AACR,IAAA,MAAM,IAAIA,iBAAA;AAAA,MACR,CAAA,wDAAA;AAAA,KACF;AAAA;AAGF,EAAM,MAAA,KAAA,GAAQ,OAAO,MAAM,CAAA;AAG3B,EAAM,MAAA,GAAA,GAAM,QAAQ,IAAK,CAAA,KAAA,CAAM,QAAQ,IAAK,CAAA,GAAA,EAAQ,GAAA,GAAI,CAAI,GAAA,KAAA,CAAA;AAC5D,EAAI,IAAA,GAAA,IAAO,MAAM,CAAG,EAAA;AAClB,IAAM,MAAA,IAAIA,kBAAW,CAAoD,kDAAA,CAAA,CAAA;AAAA;AAG3E,EAAO,OAAA;AAAA,IACL,GAAG,MAAA;AAAA,IACH,gBAAkB,EAAA,GAAA;AAAA,IAClB,QAAA,EAAU,OAAO,QAAY,IAAA;AAAA,MAC3B,IAAM,EAAA,MAAA;AAAA,MACN,aAAe,EAAA,GAAA;AAAA,MACf,mBAAqB,EAAA;AAAA;AACvB,GACF;AACF;;;;"}
package/dist/index.d.ts CHANGED
@@ -22,6 +22,11 @@ interface BackstageSignInResult {
22
22
  * The token used to authenticate the user within Backstage.
23
23
  */
24
24
  token: string;
25
+ /**
26
+ * Identity information to pass to the client rather than using the
27
+ * information that's embeeded in the token.
28
+ */
29
+ identity?: BackstageUserIdentity;
25
30
  }
26
31
  /**
27
32
  * Response object containing the {@link BackstageUserIdentity} and the token
@@ -116,9 +121,7 @@ type AuthResolverContext = {
116
121
  /**
117
122
  * Issues a Backstage token using the provided parameters.
118
123
  */
119
- issueToken(params: TokenParams): Promise<{
120
- token: string;
121
- }>;
124
+ issueToken(params: TokenParams): Promise<BackstageSignInResult>;
122
125
  /**
123
126
  * Finds a single user in the catalog using the provided query.
124
127
  *
@@ -131,9 +134,22 @@ type AuthResolverContext = {
131
134
  * Finds a single user in the catalog using the provided query, and then
132
135
  * issues an identity for that user using default ownership resolution.
133
136
  *
137
+ * If the user is not found, an optional `dangerousEntityRefFallback`
138
+ * entity ref can be provided to allow sign-in to proceed by issuing an
139
+ * identity based on the given ref. This bypasses the requirement for the
140
+ * user to exist in the catalog and should be used with caution.
141
+ *
134
142
  * See {@link AuthResolverCatalogUserQuery} for details.
135
143
  */
136
- signInWithCatalogUser(query: AuthResolverCatalogUserQuery): Promise<BackstageSignInResult>;
144
+ signInWithCatalogUser(query: AuthResolverCatalogUserQuery, options?: {
145
+ dangerousEntityRefFallback?: {
146
+ entityRef: string | {
147
+ kind?: string;
148
+ namespace?: string;
149
+ name: string;
150
+ };
151
+ };
152
+ }): Promise<BackstageSignInResult>;
137
153
  /**
138
154
  * Resolves the ownership entity references for the provided entity.
139
155
  * This will use the `AuthOwnershipResolver` if one is installed, and otherwise fall back to the default resolution logic.
@@ -779,13 +795,17 @@ declare namespace commonSignInResolvers {
779
795
  * A common sign-in resolver that looks up the user using their email address
780
796
  * as email of the entity.
781
797
  */
782
- const emailMatchingUserEntityProfileEmail: SignInResolverFactory<unknown, unknown>;
798
+ const emailMatchingUserEntityProfileEmail: SignInResolverFactory<unknown, {
799
+ allowedDomains?: string[] | undefined;
800
+ dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined;
801
+ } | undefined>;
783
802
  /**
784
803
  * A common sign-in resolver that looks up the user using the local part of
785
804
  * their email address as the entity name.
786
805
  */
787
806
  const emailLocalPartMatchingUserEntityName: SignInResolverFactory<unknown, {
788
807
  allowedDomains?: string[] | undefined;
808
+ dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined;
789
809
  } | undefined>;
790
810
  }
791
811
 
@@ -8,7 +8,11 @@ const reEmail = /^([^@+]+)(\+[^@]+)?(@.*)$/;
8
8
  exports.commonSignInResolvers = void 0;
9
9
  ((commonSignInResolvers2) => {
10
10
  commonSignInResolvers2.emailMatchingUserEntityProfileEmail = createSignInResolverFactory.createSignInResolverFactory({
11
- create() {
11
+ optionsSchema: zod.z.object({
12
+ allowedDomains: zod.z.array(zod.z.string()).optional(),
13
+ dangerouslyAllowSignInWithoutUserInCatalog: zod.z.boolean().optional()
14
+ }).optional(),
15
+ create(options = {}) {
12
16
  return async (info, ctx) => {
13
17
  const { profile } = info;
14
18
  if (!profile.email) {
@@ -28,11 +32,16 @@ exports.commonSignInResolvers = void 0;
28
32
  if (m?.length === 4) {
29
33
  const [_, name, _plus, domain] = m;
30
34
  const noPlusEmail = `${name}${domain}`;
31
- return ctx.signInWithCatalogUser({
32
- filter: {
33
- "spec.profile.email": noPlusEmail
35
+ return ctx.signInWithCatalogUser(
36
+ {
37
+ filter: {
38
+ "spec.profile.email": noPlusEmail
39
+ }
40
+ },
41
+ {
42
+ dangerousEntityRefFallback: options?.dangerouslyAllowSignInWithoutUserInCatalog ? { entityRef: { name: noPlusEmail } } : void 0
34
43
  }
35
- });
44
+ );
36
45
  }
37
46
  }
38
47
  throw err;
@@ -42,7 +51,8 @@ exports.commonSignInResolvers = void 0;
42
51
  });
43
52
  commonSignInResolvers2.emailLocalPartMatchingUserEntityName = createSignInResolverFactory.createSignInResolverFactory({
44
53
  optionsSchema: zod.z.object({
45
- allowedDomains: zod.z.array(zod.z.string()).optional()
54
+ allowedDomains: zod.z.array(zod.z.string()).optional(),
55
+ dangerouslyAllowSignInWithoutUserInCatalog: zod.z.boolean().optional()
46
56
  }).optional(),
47
57
  create(options = {}) {
48
58
  const { allowedDomains } = options;
@@ -60,9 +70,12 @@ exports.commonSignInResolvers = void 0;
60
70
  "Sign-in user email is not from an allowed domain"
61
71
  );
62
72
  }
63
- return ctx.signInWithCatalogUser({
64
- entityRef: { name: localPart }
65
- });
73
+ return ctx.signInWithCatalogUser(
74
+ { entityRef: { name: localPart } },
75
+ {
76
+ dangerousEntityRefFallback: options?.dangerouslyAllowSignInWithoutUserInCatalog ? { entityRef: { name: localPart } } : void 0
77
+ }
78
+ );
66
79
  };
67
80
  }
68
81
  });
@@ -1 +1 @@
1
- {"version":3,"file":"commonSignInResolvers.cjs.js","sources":["../../src/sign-in/commonSignInResolvers.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { z } from 'zod';\nimport { createSignInResolverFactory } from './createSignInResolverFactory';\nimport { NotAllowedError } from '@backstage/errors';\n\n// This splits an email \"joe+work@acme.com\" into [\"joe\", \"+work\", \"@acme.com\"]\n// so that we can remove the plus addressing. May output a shorter array:\n// [\"joe\", \"@acme.com\"], if no plus addressing was found.\nconst reEmail = /^([^@+]+)(\\+[^@]+)?(@.*)$/;\n\n/**\n * A collection of common sign-in resolvers that work with any auth provider.\n *\n * @public\n */\nexport namespace commonSignInResolvers {\n /**\n * A common sign-in resolver that looks up the user using their email address\n * as email of the entity.\n */\n export const emailMatchingUserEntityProfileEmail =\n createSignInResolverFactory({\n create() {\n return async (info, ctx) => {\n const { profile } = info;\n\n if (!profile.email) {\n throw new Error(\n 'Login failed, user profile does not contain an email',\n );\n }\n\n try {\n return await ctx.signInWithCatalogUser({\n filter: {\n 'spec.profile.email': profile.email,\n },\n });\n } catch (err) {\n if (err?.name === 'NotFoundError') {\n // Try removing the plus addressing from the email address\n const m = profile.email.match(reEmail);\n if (m?.length === 4) {\n const [_, name, _plus, domain] = m;\n const noPlusEmail = `${name}${domain}`;\n\n return ctx.signInWithCatalogUser({\n filter: {\n 'spec.profile.email': noPlusEmail,\n },\n });\n }\n }\n // Email had no plus addressing or is missing in the catalog, forward failure\n throw err;\n }\n };\n },\n });\n\n /**\n * A common sign-in resolver that looks up the user using the local part of\n * their email address as the entity name.\n */\n export const emailLocalPartMatchingUserEntityName =\n createSignInResolverFactory({\n optionsSchema: z\n .object({\n allowedDomains: z.array(z.string()).optional(),\n })\n .optional(),\n create(options = {}) {\n const { allowedDomains } = options;\n return async (info, ctx) => {\n const { profile } = info;\n\n if (!profile.email) {\n throw new Error(\n 'Login failed, user profile does not contain an email',\n );\n }\n const [localPart] = profile.email.split('@');\n const domain = profile.email.slice(localPart.length + 1);\n\n if (allowedDomains && !allowedDomains.includes(domain)) {\n throw new NotAllowedError(\n 'Sign-in user email is not from an allowed domain',\n );\n }\n\n return ctx.signInWithCatalogUser({\n entityRef: { name: localPart },\n });\n };\n },\n });\n}\n"],"names":["commonSignInResolvers","createSignInResolverFactory","z","NotAllowedError"],"mappings":";;;;;;AAuBA,MAAM,OAAU,GAAA,2BAAA;AAOCA;AAAA,CAAV,CAAUA,sBAAV,KAAA;AAKE,EAAMA,sBAAAA,CAAA,sCACXC,uDAA4B,CAAA;AAAA,IAC1B,MAAS,GAAA;AACP,MAAO,OAAA,OAAO,MAAM,GAAQ,KAAA;AAC1B,QAAM,MAAA,EAAE,SAAY,GAAA,IAAA;AAEpB,QAAI,IAAA,CAAC,QAAQ,KAAO,EAAA;AAClB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAGF,QAAI,IAAA;AACF,UAAO,OAAA,MAAM,IAAI,qBAAsB,CAAA;AAAA,YACrC,MAAQ,EAAA;AAAA,cACN,sBAAsB,OAAQ,CAAA;AAAA;AAChC,WACD,CAAA;AAAA,iBACM,GAAK,EAAA;AACZ,UAAI,IAAA,GAAA,EAAK,SAAS,eAAiB,EAAA;AAEjC,YAAA,MAAM,CAAI,GAAA,OAAA,CAAQ,KAAM,CAAA,KAAA,CAAM,OAAO,CAAA;AACrC,YAAI,IAAA,CAAA,EAAG,WAAW,CAAG,EAAA;AACnB,cAAA,MAAM,CAAC,CAAA,EAAG,IAAM,EAAA,KAAA,EAAO,MAAM,CAAI,GAAA,CAAA;AACjC,cAAA,MAAM,WAAc,GAAA,CAAA,EAAG,IAAI,CAAA,EAAG,MAAM,CAAA,CAAA;AAEpC,cAAA,OAAO,IAAI,qBAAsB,CAAA;AAAA,gBAC/B,MAAQ,EAAA;AAAA,kBACN,oBAAsB,EAAA;AAAA;AACxB,eACD,CAAA;AAAA;AACH;AAGF,UAAM,MAAA,GAAA;AAAA;AACR,OACF;AAAA;AACF,GACD,CAAA;AAMI,EAAMD,sBAAAA,CAAA,uCACXC,uDAA4B,CAAA;AAAA,IAC1B,aAAA,EAAeC,MACZ,MAAO,CAAA;AAAA,MACN,gBAAgBA,KAAE,CAAA,KAAA,CAAMA,MAAE,MAAO,EAAC,EAAE,QAAS;AAAA,KAC9C,EACA,QAAS,EAAA;AAAA,IACZ,MAAA,CAAO,OAAU,GAAA,EAAI,EAAA;AACnB,MAAM,MAAA,EAAE,gBAAmB,GAAA,OAAA;AAC3B,MAAO,OAAA,OAAO,MAAM,GAAQ,KAAA;AAC1B,QAAM,MAAA,EAAE,SAAY,GAAA,IAAA;AAEpB,QAAI,IAAA,CAAC,QAAQ,KAAO,EAAA;AAClB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAA,MAAM,CAAC,SAAS,CAAA,GAAI,OAAQ,CAAA,KAAA,CAAM,MAAM,GAAG,CAAA;AAC3C,QAAA,MAAM,SAAS,OAAQ,CAAA,KAAA,CAAM,KAAM,CAAA,SAAA,CAAU,SAAS,CAAC,CAAA;AAEvD,QAAA,IAAI,cAAkB,IAAA,CAAC,cAAe,CAAA,QAAA,CAAS,MAAM,CAAG,EAAA;AACtD,UAAA,MAAM,IAAIC,sBAAA;AAAA,YACR;AAAA,WACF;AAAA;AAGF,QAAA,OAAO,IAAI,qBAAsB,CAAA;AAAA,UAC/B,SAAA,EAAW,EAAE,IAAA,EAAM,SAAU;AAAA,SAC9B,CAAA;AAAA,OACH;AAAA;AACF,GACD,CAAA;AAAA,CAhFY,EAAAH,6BAAA,KAAAA,6BAAA,GAAA,EAAA,CAAA,CAAA;;"}
1
+ {"version":3,"file":"commonSignInResolvers.cjs.js","sources":["../../src/sign-in/commonSignInResolvers.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { z } from 'zod';\nimport { createSignInResolverFactory } from './createSignInResolverFactory';\nimport { NotAllowedError } from '@backstage/errors';\n\n// This splits an email \"joe+work@acme.com\" into [\"joe\", \"+work\", \"@acme.com\"]\n// so that we can remove the plus addressing. May output a shorter array:\n// [\"joe\", \"@acme.com\"], if no plus addressing was found.\nconst reEmail = /^([^@+]+)(\\+[^@]+)?(@.*)$/;\n\n/**\n * A collection of common sign-in resolvers that work with any auth provider.\n *\n * @public\n */\nexport namespace commonSignInResolvers {\n /**\n * A common sign-in resolver that looks up the user using their email address\n * as email of the entity.\n */\n export const emailMatchingUserEntityProfileEmail =\n createSignInResolverFactory({\n optionsSchema: z\n .object({\n allowedDomains: z.array(z.string()).optional(),\n dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(),\n })\n .optional(),\n create(options = {}) {\n return async (info, ctx) => {\n const { profile } = info;\n\n if (!profile.email) {\n throw new Error(\n 'Login failed, user profile does not contain an email',\n );\n }\n\n try {\n return await ctx.signInWithCatalogUser({\n filter: {\n 'spec.profile.email': profile.email,\n },\n });\n } catch (err) {\n if (err?.name === 'NotFoundError') {\n // Try removing the plus addressing from the email address\n const m = profile.email.match(reEmail);\n if (m?.length === 4) {\n const [_, name, _plus, domain] = m;\n const noPlusEmail = `${name}${domain}`;\n\n return ctx.signInWithCatalogUser(\n {\n filter: {\n 'spec.profile.email': noPlusEmail,\n },\n },\n {\n dangerousEntityRefFallback:\n options?.dangerouslyAllowSignInWithoutUserInCatalog\n ? { entityRef: { name: noPlusEmail } }\n : undefined,\n },\n );\n }\n }\n // Email had no plus addressing or is missing in the catalog, forward failure\n throw err;\n }\n };\n },\n });\n\n /**\n * A common sign-in resolver that looks up the user using the local part of\n * their email address as the entity name.\n */\n export const emailLocalPartMatchingUserEntityName =\n createSignInResolverFactory({\n optionsSchema: z\n .object({\n allowedDomains: z.array(z.string()).optional(),\n dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(),\n })\n .optional(),\n create(options = {}) {\n const { allowedDomains } = options;\n return async (info, ctx) => {\n const { profile } = info;\n\n if (!profile.email) {\n throw new Error(\n 'Login failed, user profile does not contain an email',\n );\n }\n const [localPart] = profile.email.split('@');\n const domain = profile.email.slice(localPart.length + 1);\n\n if (allowedDomains && !allowedDomains.includes(domain)) {\n throw new NotAllowedError(\n 'Sign-in user email is not from an allowed domain',\n );\n }\n return ctx.signInWithCatalogUser(\n { entityRef: { name: localPart } },\n {\n dangerousEntityRefFallback:\n options?.dangerouslyAllowSignInWithoutUserInCatalog\n ? { entityRef: { name: localPart } }\n : undefined,\n },\n );\n };\n },\n });\n}\n"],"names":["commonSignInResolvers","createSignInResolverFactory","z","NotAllowedError"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.cjs.js","sources":["../src/types.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { EntityFilterQuery } from '@backstage/catalog-client';\nimport { Entity } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { JsonValue } from '@backstage/types';\nimport { Request, Response } from 'express';\n\n/**\n * A representation of a successful Backstage sign-in.\n *\n * Compared to the {@link BackstageIdentityResponse} this type omits\n * the decoded identity information embedded in the token.\n *\n * @public\n */\nexport interface BackstageSignInResult {\n /**\n * The token used to authenticate the user within Backstage.\n */\n token: string;\n}\n\n/**\n * Response object containing the {@link BackstageUserIdentity} and the token\n * from the authentication provider.\n *\n * @public\n */\nexport interface BackstageIdentityResponse extends BackstageSignInResult {\n /**\n * The number of seconds until the token expires. If not set, it can be assumed that the token does not expire.\n */\n expiresInSeconds?: number;\n\n /**\n * A plaintext description of the identity that is encapsulated within the token.\n */\n identity: BackstageUserIdentity;\n}\n\n/**\n * User identity information within Backstage.\n *\n * @public\n */\nexport type BackstageUserIdentity = {\n /**\n * The type of identity that this structure represents. In the frontend app\n * this will currently always be 'user'.\n */\n type: 'user';\n\n /**\n * The entityRef of the user in the catalog.\n * For example User:default/sandra\n */\n userEntityRef: string;\n\n /**\n * The user and group entities that the user claims ownership through\n */\n ownershipEntityRefs: string[];\n};\n\n/**\n * A query for a single user in the catalog.\n *\n * If `entityRef` is used, the default kind is `'User'`.\n *\n * If `annotations` are used, all annotations must be present and\n * match the provided value exactly. Only entities of kind `'User'` will be considered.\n *\n * If `filter` are used, only entities of kind `'User'` will be considered unless it is explicitly specified differently in the filter.\n *\n * Regardless of the query method, the query must match exactly one entity\n * in the catalog, or an error will be thrown.\n *\n * @public\n */\nexport type AuthResolverCatalogUserQuery =\n | {\n entityRef:\n | string\n | {\n kind?: string;\n namespace?: string;\n name: string;\n };\n }\n | {\n annotations: Record<string, string>;\n }\n | {\n filter: EntityFilterQuery;\n };\n\n/**\n * Parameters used to issue new Backstage Tokens\n *\n * @public\n */\nexport type TokenParams = {\n /**\n * The claims that will be embedded within the token. At a minimum, this should include\n * the subject claim, `sub`. It is common to also list entity ownership relations in the\n * `ent` list. Additional claims may also be added at the developer's discretion except\n * for the following list, which will be overwritten by the TokenIssuer: `iss`, `aud`,\n * `iat`, and `exp`. The Backstage team also maintains the right add new claims in the future\n * without listing the change as a \"breaking change\".\n */\n claims: {\n /** The token subject, i.e. User ID */\n sub: string;\n /** A list of entity references that the user claims ownership through */\n ent?: string[];\n } & Record<string, JsonValue>;\n};\n\n/**\n * The context that is used for auth processing.\n *\n * @public\n */\nexport type AuthResolverContext = {\n /**\n * Issues a Backstage token using the provided parameters.\n */\n issueToken(params: TokenParams): Promise<{ token: string }>;\n\n /**\n * Finds a single user in the catalog using the provided query.\n *\n * See {@link AuthResolverCatalogUserQuery} for details.\n */\n findCatalogUser(\n query: AuthResolverCatalogUserQuery,\n ): Promise<{ entity: Entity }>;\n\n /**\n * Finds a single user in the catalog using the provided query, and then\n * issues an identity for that user using default ownership resolution.\n *\n * See {@link AuthResolverCatalogUserQuery} for details.\n */\n signInWithCatalogUser(\n query: AuthResolverCatalogUserQuery,\n ): Promise<BackstageSignInResult>;\n\n /**\n * Resolves the ownership entity references for the provided entity.\n * This will use the `AuthOwnershipResolver` if one is installed, and otherwise fall back to the default resolution logic.\n */\n resolveOwnershipEntityRefs(\n entity: Entity,\n ): Promise<{ ownershipEntityRefs: string[] }>;\n};\n\n/**\n * Resolver interface for resolving the ownership entity references for entity\n *\n * @public\n */\nexport interface AuthOwnershipResolver {\n resolveOwnershipEntityRefs(\n entity: Entity,\n ): Promise<{ ownershipEntityRefs: string[] }>;\n}\n\n/**\n * Any Auth provider needs to implement this interface which handles the routes in the\n * auth backend. Any auth API requests from the frontend reaches these methods.\n *\n * The routes in the auth backend API are tied to these methods like below\n *\n * `/auth/[provider]/start -> start`\n * `/auth/[provider]/handler/frame -> frameHandler`\n * `/auth/[provider]/refresh -> refresh`\n * `/auth/[provider]/logout -> logout`\n *\n * @public\n */\nexport interface AuthProviderRouteHandlers {\n /**\n * Handles the start route of the API. This initiates a sign in request with an auth provider.\n *\n * Request\n * - scopes for the auth request (Optional)\n * Response\n * - redirect to the auth provider for the user to sign in or consent.\n * - sets a nonce cookie and also pass the nonce as 'state' query parameter in the redirect request\n */\n start(req: Request, res: Response): Promise<void>;\n\n /**\n * Once the user signs in or consents in the OAuth screen, the auth provider redirects to the\n * callbackURL which is handled by this method.\n *\n * Request\n * - to contain a nonce cookie and a 'state' query parameter\n * Response\n * - postMessage to the window with a payload that contains accessToken, expiryInSeconds?, idToken? and scope.\n * - sets a refresh token cookie if the auth provider supports refresh tokens\n */\n frameHandler(req: Request, res: Response): Promise<void>;\n\n /**\n * (Optional) If the auth provider supports refresh tokens then this method handles\n * requests to get a new access token.\n *\n * Other types of providers may also use this method to implement its own logic to create new sessions\n * upon request. For example, this can be used to create a new session for a provider that handles requests\n * from an authenticating proxy.\n *\n * Request\n * - to contain a refresh token cookie and scope (Optional) query parameter.\n * Response\n * - payload with accessToken, expiryInSeconds?, idToken?, scope and user profile information.\n */\n refresh?(req: Request, res: Response): Promise<void>;\n\n /**\n * (Optional) Handles sign out requests\n *\n * Response\n * - removes the refresh token cookie\n */\n logout?(req: Request, res: Response): Promise<void>;\n}\n\n/**\n * @public\n * @deprecated Use top-level properties passed to `AuthProviderFactory` instead\n */\nexport type AuthProviderConfig = {\n /**\n * The protocol://domain[:port] where the app is hosted. This is used to construct the\n * callbackURL to redirect to once the user signs in to the auth provider.\n */\n baseUrl: string;\n\n /**\n * The base URL of the app as provided by app.baseUrl\n */\n appUrl: string;\n\n /**\n * A function that is called to check whether an origin is allowed to receive the authentication result.\n */\n isOriginAllowed: (origin: string) => boolean;\n\n /**\n * The function used to resolve cookie configuration based on the auth provider options.\n */\n cookieConfigurer?: CookieConfigurer;\n};\n\n/** @public */\nexport type AuthProviderFactory = (options: {\n providerId: string;\n /** @deprecated Use top-level properties instead */\n globalConfig: AuthProviderConfig;\n config: Config;\n logger: LoggerService;\n resolverContext: AuthResolverContext;\n /**\n * The protocol://domain[:port] where the app is hosted. This is used to construct the\n * callbackURL to redirect to once the user signs in to the auth provider.\n */\n baseUrl: string;\n\n /**\n * The base URL of the app as provided by app.baseUrl\n */\n appUrl: string;\n\n /**\n * A function that is called to check whether an origin is allowed to receive the authentication result.\n */\n isOriginAllowed: (origin: string) => boolean;\n\n /**\n * The function used to resolve cookie configuration based on the auth provider options.\n */\n cookieConfigurer?: CookieConfigurer;\n}) => AuthProviderRouteHandlers;\n\n/** @public */\nexport type ClientAuthResponse<TProviderInfo> = {\n providerInfo: TProviderInfo;\n profile: ProfileInfo;\n backstageIdentity?: BackstageIdentityResponse;\n};\n\n/**\n * Type of sign in information context. Includes the profile information and\n * authentication result which contains auth related information.\n *\n * @public\n */\nexport type SignInInfo<TAuthResult> = {\n /**\n * The simple profile passed down for use in the frontend.\n */\n profile: ProfileInfo;\n\n /**\n * The authentication result that was received from the authentication\n * provider.\n */\n result: TAuthResult;\n};\n\n/**\n * Describes the function which handles the result of a successful\n * authentication. Must return a valid {@link @backstage/plugin-auth-node#BackstageSignInResult}.\n *\n * @public\n */\nexport type SignInResolver<TAuthResult> = (\n info: SignInInfo<TAuthResult>,\n context: AuthResolverContext,\n) => Promise<BackstageSignInResult>;\n\n/**\n * Describes the function that transforms the result of a successful\n * authentication into a {@link ProfileInfo} object.\n *\n * This function may optionally throw an error in order to reject authentication.\n *\n * @public\n */\nexport type ProfileTransform<TResult> = (\n result: TResult,\n context: AuthResolverContext,\n) => Promise<{ profile: ProfileInfo }>;\n\n/**\n * Used to display login information to user, i.e. sidebar popup.\n *\n * It is also temporarily used as the profile of the signed-in user's Backstage\n * identity, but we want to replace that with data from identity and/org catalog\n * service\n *\n * @public\n */\nexport type ProfileInfo = {\n /**\n * Email ID of the signed in user.\n */\n email?: string;\n /**\n * Display name that can be presented to the signed in user.\n */\n displayName?: string;\n /**\n * URL to an image that can be used as the display image or avatar of the\n * signed in user.\n */\n picture?: string;\n};\n\n/**\n * The callback used to resolve the cookie configuration for auth providers that use cookies.\n * @public\n */\nexport type CookieConfigurer = (ctx: {\n /** ID of the auth provider that this configuration applies to */\n providerId: string;\n /** The externally reachable base URL of the auth-backend plugin */\n baseUrl: string;\n /** The configured callback URL of the auth provider */\n callbackUrl: string;\n /** The origin URL of the app */\n appOrigin: string;\n}) => {\n domain: string;\n path: string;\n secure: boolean;\n sameSite?: 'none' | 'lax' | 'strict';\n};\n\n/**\n * Core properties of various token types.\n *\n * @public\n */\nexport const tokenTypes = Object.freeze({\n user: Object.freeze({\n typParam: 'vnd.backstage.user',\n audClaim: 'backstage',\n }),\n limitedUser: Object.freeze({\n typParam: 'vnd.backstage.limited-user',\n }),\n plugin: Object.freeze({\n typParam: 'vnd.backstage.plugin',\n }),\n});\n"],"names":[],"mappings":";;AAkZa,MAAA,UAAA,GAAa,OAAO,MAAO,CAAA;AAAA,EACtC,IAAA,EAAM,OAAO,MAAO,CAAA;AAAA,IAClB,QAAU,EAAA,oBAAA;AAAA,IACV,QAAU,EAAA;AAAA,GACX,CAAA;AAAA,EACD,WAAA,EAAa,OAAO,MAAO,CAAA;AAAA,IACzB,QAAU,EAAA;AAAA,GACX,CAAA;AAAA,EACD,MAAA,EAAQ,OAAO,MAAO,CAAA;AAAA,IACpB,QAAU,EAAA;AAAA,GACX;AACH,CAAC;;;;"}
1
+ {"version":3,"file":"types.cjs.js","sources":["../src/types.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { EntityFilterQuery } from '@backstage/catalog-client';\nimport { Entity } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { JsonValue } from '@backstage/types';\nimport { Request, Response } from 'express';\n\n/**\n * A representation of a successful Backstage sign-in.\n *\n * Compared to the {@link BackstageIdentityResponse} this type omits\n * the decoded identity information embedded in the token.\n *\n * @public\n */\nexport interface BackstageSignInResult {\n /**\n * The token used to authenticate the user within Backstage.\n */\n token: string;\n\n /**\n * Identity information to pass to the client rather than using the\n * information that's embeeded in the token.\n */\n identity?: BackstageUserIdentity;\n}\n\n/**\n * Response object containing the {@link BackstageUserIdentity} and the token\n * from the authentication provider.\n *\n * @public\n */\nexport interface BackstageIdentityResponse extends BackstageSignInResult {\n /**\n * The number of seconds until the token expires. If not set, it can be assumed that the token does not expire.\n */\n expiresInSeconds?: number;\n\n /**\n * A plaintext description of the identity that is encapsulated within the token.\n */\n identity: BackstageUserIdentity;\n}\n\n/**\n * User identity information within Backstage.\n *\n * @public\n */\nexport type BackstageUserIdentity = {\n /**\n * The type of identity that this structure represents. In the frontend app\n * this will currently always be 'user'.\n */\n type: 'user';\n\n /**\n * The entityRef of the user in the catalog.\n * For example User:default/sandra\n */\n userEntityRef: string;\n\n /**\n * The user and group entities that the user claims ownership through\n */\n ownershipEntityRefs: string[];\n};\n\n/**\n * A query for a single user in the catalog.\n *\n * If `entityRef` is used, the default kind is `'User'`.\n *\n * If `annotations` are used, all annotations must be present and\n * match the provided value exactly. Only entities of kind `'User'` will be considered.\n *\n * If `filter` are used, only entities of kind `'User'` will be considered unless it is explicitly specified differently in the filter.\n *\n * Regardless of the query method, the query must match exactly one entity\n * in the catalog, or an error will be thrown.\n *\n * @public\n */\nexport type AuthResolverCatalogUserQuery =\n | {\n entityRef:\n | string\n | {\n kind?: string;\n namespace?: string;\n name: string;\n };\n }\n | {\n annotations: Record<string, string>;\n }\n | {\n filter: EntityFilterQuery;\n };\n\n/**\n * Parameters used to issue new Backstage Tokens\n *\n * @public\n */\nexport type TokenParams = {\n /**\n * The claims that will be embedded within the token. At a minimum, this should include\n * the subject claim, `sub`. It is common to also list entity ownership relations in the\n * `ent` list. Additional claims may also be added at the developer's discretion except\n * for the following list, which will be overwritten by the TokenIssuer: `iss`, `aud`,\n * `iat`, and `exp`. The Backstage team also maintains the right add new claims in the future\n * without listing the change as a \"breaking change\".\n */\n claims: {\n /** The token subject, i.e. User ID */\n sub: string;\n /** A list of entity references that the user claims ownership through */\n ent?: string[];\n } & Record<string, JsonValue>;\n};\n\n/**\n * The context that is used for auth processing.\n *\n * @public\n */\nexport type AuthResolverContext = {\n /**\n * Issues a Backstage token using the provided parameters.\n */\n issueToken(params: TokenParams): Promise<BackstageSignInResult>;\n\n /**\n * Finds a single user in the catalog using the provided query.\n *\n * See {@link AuthResolverCatalogUserQuery} for details.\n */\n findCatalogUser(\n query: AuthResolverCatalogUserQuery,\n ): Promise<{ entity: Entity }>;\n\n /**\n * Finds a single user in the catalog using the provided query, and then\n * issues an identity for that user using default ownership resolution.\n *\n * If the user is not found, an optional `dangerousEntityRefFallback`\n * entity ref can be provided to allow sign-in to proceed by issuing an\n * identity based on the given ref. This bypasses the requirement for the\n * user to exist in the catalog and should be used with caution.\n *\n * See {@link AuthResolverCatalogUserQuery} for details.\n */\n signInWithCatalogUser(\n query: AuthResolverCatalogUserQuery,\n options?: {\n dangerousEntityRefFallback?: {\n entityRef:\n | string\n | {\n kind?: string;\n namespace?: string;\n name: string;\n };\n };\n },\n ): Promise<BackstageSignInResult>;\n\n /**\n * Resolves the ownership entity references for the provided entity.\n * This will use the `AuthOwnershipResolver` if one is installed, and otherwise fall back to the default resolution logic.\n */\n resolveOwnershipEntityRefs(\n entity: Entity,\n ): Promise<{ ownershipEntityRefs: string[] }>;\n};\n\n/**\n * Resolver interface for resolving the ownership entity references for entity\n *\n * @public\n */\nexport interface AuthOwnershipResolver {\n resolveOwnershipEntityRefs(\n entity: Entity,\n ): Promise<{ ownershipEntityRefs: string[] }>;\n}\n\n/**\n * Any Auth provider needs to implement this interface which handles the routes in the\n * auth backend. Any auth API requests from the frontend reaches these methods.\n *\n * The routes in the auth backend API are tied to these methods like below\n *\n * `/auth/[provider]/start -> start`\n * `/auth/[provider]/handler/frame -> frameHandler`\n * `/auth/[provider]/refresh -> refresh`\n * `/auth/[provider]/logout -> logout`\n *\n * @public\n */\nexport interface AuthProviderRouteHandlers {\n /**\n * Handles the start route of the API. This initiates a sign in request with an auth provider.\n *\n * Request\n * - scopes for the auth request (Optional)\n * Response\n * - redirect to the auth provider for the user to sign in or consent.\n * - sets a nonce cookie and also pass the nonce as 'state' query parameter in the redirect request\n */\n start(req: Request, res: Response): Promise<void>;\n\n /**\n * Once the user signs in or consents in the OAuth screen, the auth provider redirects to the\n * callbackURL which is handled by this method.\n *\n * Request\n * - to contain a nonce cookie and a 'state' query parameter\n * Response\n * - postMessage to the window with a payload that contains accessToken, expiryInSeconds?, idToken? and scope.\n * - sets a refresh token cookie if the auth provider supports refresh tokens\n */\n frameHandler(req: Request, res: Response): Promise<void>;\n\n /**\n * (Optional) If the auth provider supports refresh tokens then this method handles\n * requests to get a new access token.\n *\n * Other types of providers may also use this method to implement its own logic to create new sessions\n * upon request. For example, this can be used to create a new session for a provider that handles requests\n * from an authenticating proxy.\n *\n * Request\n * - to contain a refresh token cookie and scope (Optional) query parameter.\n * Response\n * - payload with accessToken, expiryInSeconds?, idToken?, scope and user profile information.\n */\n refresh?(req: Request, res: Response): Promise<void>;\n\n /**\n * (Optional) Handles sign out requests\n *\n * Response\n * - removes the refresh token cookie\n */\n logout?(req: Request, res: Response): Promise<void>;\n}\n\n/**\n * @public\n * @deprecated Use top-level properties passed to `AuthProviderFactory` instead\n */\nexport type AuthProviderConfig = {\n /**\n * The protocol://domain[:port] where the app is hosted. This is used to construct the\n * callbackURL to redirect to once the user signs in to the auth provider.\n */\n baseUrl: string;\n\n /**\n * The base URL of the app as provided by app.baseUrl\n */\n appUrl: string;\n\n /**\n * A function that is called to check whether an origin is allowed to receive the authentication result.\n */\n isOriginAllowed: (origin: string) => boolean;\n\n /**\n * The function used to resolve cookie configuration based on the auth provider options.\n */\n cookieConfigurer?: CookieConfigurer;\n};\n\n/** @public */\nexport type AuthProviderFactory = (options: {\n providerId: string;\n /** @deprecated Use top-level properties instead */\n globalConfig: AuthProviderConfig;\n config: Config;\n logger: LoggerService;\n resolverContext: AuthResolverContext;\n /**\n * The protocol://domain[:port] where the app is hosted. This is used to construct the\n * callbackURL to redirect to once the user signs in to the auth provider.\n */\n baseUrl: string;\n\n /**\n * The base URL of the app as provided by app.baseUrl\n */\n appUrl: string;\n\n /**\n * A function that is called to check whether an origin is allowed to receive the authentication result.\n */\n isOriginAllowed: (origin: string) => boolean;\n\n /**\n * The function used to resolve cookie configuration based on the auth provider options.\n */\n cookieConfigurer?: CookieConfigurer;\n}) => AuthProviderRouteHandlers;\n\n/** @public */\nexport type ClientAuthResponse<TProviderInfo> = {\n providerInfo: TProviderInfo;\n profile: ProfileInfo;\n backstageIdentity?: BackstageIdentityResponse;\n};\n\n/**\n * Type of sign in information context. Includes the profile information and\n * authentication result which contains auth related information.\n *\n * @public\n */\nexport type SignInInfo<TAuthResult> = {\n /**\n * The simple profile passed down for use in the frontend.\n */\n profile: ProfileInfo;\n\n /**\n * The authentication result that was received from the authentication\n * provider.\n */\n result: TAuthResult;\n};\n\n/**\n * Describes the function which handles the result of a successful\n * authentication. Must return a valid {@link @backstage/plugin-auth-node#BackstageSignInResult}.\n *\n * @public\n */\nexport type SignInResolver<TAuthResult> = (\n info: SignInInfo<TAuthResult>,\n context: AuthResolverContext,\n) => Promise<BackstageSignInResult>;\n\n/**\n * Describes the function that transforms the result of a successful\n * authentication into a {@link ProfileInfo} object.\n *\n * This function may optionally throw an error in order to reject authentication.\n *\n * @public\n */\nexport type ProfileTransform<TResult> = (\n result: TResult,\n context: AuthResolverContext,\n) => Promise<{ profile: ProfileInfo }>;\n\n/**\n * Used to display login information to user, i.e. sidebar popup.\n *\n * It is also temporarily used as the profile of the signed-in user's Backstage\n * identity, but we want to replace that with data from identity and/org catalog\n * service\n *\n * @public\n */\nexport type ProfileInfo = {\n /**\n * Email ID of the signed in user.\n */\n email?: string;\n /**\n * Display name that can be presented to the signed in user.\n */\n displayName?: string;\n /**\n * URL to an image that can be used as the display image or avatar of the\n * signed in user.\n */\n picture?: string;\n};\n\n/**\n * The callback used to resolve the cookie configuration for auth providers that use cookies.\n * @public\n */\nexport type CookieConfigurer = (ctx: {\n /** ID of the auth provider that this configuration applies to */\n providerId: string;\n /** The externally reachable base URL of the auth-backend plugin */\n baseUrl: string;\n /** The configured callback URL of the auth provider */\n callbackUrl: string;\n /** The origin URL of the app */\n appOrigin: string;\n}) => {\n domain: string;\n path: string;\n secure: boolean;\n sameSite?: 'none' | 'lax' | 'strict';\n};\n\n/**\n * Core properties of various token types.\n *\n * @public\n */\nexport const tokenTypes = Object.freeze({\n user: Object.freeze({\n typParam: 'vnd.backstage.user',\n audClaim: 'backstage',\n }),\n limitedUser: Object.freeze({\n typParam: 'vnd.backstage.limited-user',\n }),\n plugin: Object.freeze({\n typParam: 'vnd.backstage.plugin',\n }),\n});\n"],"names":[],"mappings":";;AAwaa,MAAA,UAAA,GAAa,OAAO,MAAO,CAAA;AAAA,EACtC,IAAA,EAAM,OAAO,MAAO,CAAA;AAAA,IAClB,QAAU,EAAA,oBAAA;AAAA,IACV,QAAU,EAAA;AAAA,GACX,CAAA;AAAA,EACD,WAAA,EAAa,OAAO,MAAO,CAAA;AAAA,IACzB,QAAU,EAAA;AAAA,GACX,CAAA;AAAA,EACD,MAAA,EAAQ,OAAO,MAAO,CAAA;AAAA,IACpB,QAAU,EAAA;AAAA,GACX;AACH,CAAC;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-auth-node",
3
- "version": "0.6.3-next.0",
3
+ "version": "0.6.3-next.2",
4
4
  "backstage": {
5
5
  "role": "node-library",
6
6
  "pluginId": "auth",
@@ -37,7 +37,7 @@
37
37
  "test": "backstage-cli package test"
38
38
  },
39
39
  "dependencies": {
40
- "@backstage/backend-plugin-api": "1.3.1-next.0",
40
+ "@backstage/backend-plugin-api": "1.3.1-next.2",
41
41
  "@backstage/catalog-client": "1.10.0-next.0",
42
42
  "@backstage/catalog-model": "1.7.3",
43
43
  "@backstage/config": "1.3.2",
@@ -54,9 +54,9 @@
54
54
  "zod-validation-error": "^3.4.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@backstage/backend-defaults": "0.9.1-next.0",
58
- "@backstage/backend-test-utils": "1.5.0-next.0",
59
- "@backstage/cli": "0.32.1-next.0",
57
+ "@backstage/backend-defaults": "0.10.0-next.3",
58
+ "@backstage/backend-test-utils": "1.5.0-next.3",
59
+ "@backstage/cli": "0.32.1-next.3",
60
60
  "cookie-parser": "^1.4.6",
61
61
  "express-promise-router": "^4.1.1",
62
62
  "lodash": "^4.17.21",