@backstage/plugin-auth-backend 0.28.0-next.1 → 0.28.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,42 @@
1
1
  # @backstage/plugin-auth-backend
2
2
 
3
+ ## 0.28.0
4
+
5
+ ### Minor Changes
6
+
7
+ - d7c67cd: **BREAKING**: The setting `auth.omitIdentityTokenOwnershipClaim` has had its default value switched to `true`.
8
+
9
+ With this setting Backstage user tokens issued by the `auth` backend will no longer contain an `ent` claim - the one with the user's ownership entity refs. This means that tokens issued in large orgs no longer risk hitting HTTP header size limits.
10
+
11
+ To get ownership info for the current user, code should use the `userInfo` core service. In practice code will typically already conform to this since the `ent` claim has not been readily exposed in any other way for quite some time. But code which explicitly decodes Backstage tokens - which is strongly discouraged - may be affected by this change.
12
+
13
+ The setting will remain for some time to allow it to be set back to `false` if need be, but it will be removed entirely in a future release.
14
+
15
+ ### Patch Changes
16
+
17
+ - 482ceed: Migrated from `assertError` to `toError` for error handling.
18
+ - dc87ac1: Fixed CIMD redirect URI matching to allow any port for localhost addresses per RFC 8252 Section 7.3. Native CLI clients use ephemeral ports for OAuth callbacks, which are now accepted when the registered redirect URI uses a localhost address.
19
+ - Updated dependencies
20
+ - @backstage/backend-plugin-api@1.9.0
21
+ - @backstage/errors@1.3.0
22
+ - @backstage/plugin-auth-node@0.7.0
23
+ - @backstage/catalog-model@1.8.0
24
+ - @backstage/plugin-catalog-node@2.2.0
25
+ - @backstage/config@1.3.7
26
+
27
+ ## 0.28.0-next.2
28
+
29
+ ### Patch Changes
30
+
31
+ - 482ceed: Migrated from `assertError` to `toError` for error handling.
32
+ - Updated dependencies
33
+ - @backstage/errors@1.3.0-next.0
34
+ - @backstage/plugin-auth-node@0.7.0-next.2
35
+ - @backstage/plugin-catalog-node@2.2.0-next.2
36
+ - @backstage/backend-plugin-api@1.9.0-next.2
37
+ - @backstage/catalog-model@1.7.8-next.0
38
+ - @backstage/config@1.3.7-next.0
39
+
3
40
  ## 0.28.0-next.1
4
41
 
5
42
  ### Patch Changes
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var firestore = require('@google-cloud/firestore');
4
+ var errors = require('@backstage/errors');
4
5
 
5
6
  const DEFAULT_TIMEOUT_MS = 1e4;
6
7
  const DEFAULT_DOCUMENT_PATH = "sessions";
@@ -26,14 +27,11 @@ class FirestoreKeyStore {
26
27
  try {
27
28
  await keyStore.verify();
28
29
  } catch (error) {
30
+ const err = errors.toError(error);
29
31
  if (process.env.NODE_ENV !== "development") {
30
- throw new Error(
31
- `Failed to connect to database: ${error.message}`
32
- );
32
+ throw new Error(`Failed to connect to database: ${err.message}`);
33
33
  }
34
- logger?.warn(
35
- `Failed to connect to database: ${error.message}`
36
- );
34
+ logger?.warn(`Failed to connect to database: ${err.message}`);
37
35
  }
38
36
  }
39
37
  async addKey(key) {
@@ -1 +1 @@
1
- {"version":3,"file":"FirestoreKeyStore.cjs.js","sources":["../../src/identity/FirestoreKeyStore.ts"],"sourcesContent":["/*\n * Copyright 2021 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 {\n DocumentData,\n Firestore,\n QuerySnapshot,\n Settings,\n WriteResult,\n} from '@google-cloud/firestore';\n\nimport { AnyJWK, KeyStore, StoredKey } from './types';\n\nexport type FirestoreKeyStoreSettings = Settings & Options;\n\ntype Options = {\n path?: string;\n timeout?: number;\n};\n\nexport const DEFAULT_TIMEOUT_MS = 10000;\nexport const DEFAULT_DOCUMENT_PATH = 'sessions';\n\nexport class FirestoreKeyStore implements KeyStore {\n static async create(\n settings?: FirestoreKeyStoreSettings,\n ): Promise<FirestoreKeyStore> {\n const { path, timeout, ...firestoreSettings } = settings ?? {};\n const database = new Firestore(firestoreSettings);\n\n return new FirestoreKeyStore(\n database,\n path ?? DEFAULT_DOCUMENT_PATH,\n timeout ?? DEFAULT_TIMEOUT_MS,\n );\n }\n\n private readonly database: Firestore;\n private readonly path: string;\n private readonly timeout: number;\n\n private constructor(database: Firestore, path: string, timeout: number) {\n this.database = database;\n this.path = path;\n this.timeout = timeout;\n }\n\n static async verifyConnection(\n keyStore: FirestoreKeyStore,\n logger?: LoggerService,\n ): Promise<void> {\n try {\n await keyStore.verify();\n } catch (error) {\n if (process.env.NODE_ENV !== 'development') {\n throw new Error(\n `Failed to connect to database: ${(error as Error).message}`,\n );\n }\n logger?.warn(\n `Failed to connect to database: ${(error as Error).message}`,\n );\n }\n }\n\n async addKey(key: AnyJWK): Promise<void> {\n await this.withTimeout<WriteResult>(\n this.database.collection(this.path).doc(key.kid).set({\n kid: key.kid,\n key: key,\n }),\n );\n }\n\n async listKeys(): Promise<{ items: StoredKey[] }> {\n const keys = await this.withTimeout<QuerySnapshot<DocumentData>>(\n this.database.collection(this.path).get(),\n );\n\n return {\n items: keys.docs.map(doc => {\n const { key } = doc.data();\n\n return {\n createdAt: doc.createTime.toDate(),\n key: typeof key === 'string' ? JSON.parse(key) : key,\n };\n }),\n };\n }\n\n async removeKeys(kids: string[]): Promise<void> {\n // This is probably really slow, but it's done async in the background\n for (const kid of kids) {\n await this.withTimeout<WriteResult>(\n this.database.collection(this.path).doc(kid).delete(),\n );\n }\n\n /**\n * This could be achieved with batching but there's a couple of limitations with that:\n *\n * - A batched write can contain a maximum of 500 operations\n * https://firebase.google.com/docs/firestore/manage-data/transactions#batched-writes\n *\n * - The \"in\" operator can combine a maximum of 10 equality clauses\n * https://firebase.google.com/docs/firestore/query-data/queries#in_not-in_and_array-contains-any\n *\n * Example:\n *\n * const batch = this.database.batch();\n * const docs = await this.database\n * .collection(this.path)\n * .where('kid', 'in', kids)\n * .get();\n * docs.forEach(doc => {\n * batch.delete(doc.ref);\n * });\n * await batch.commit();\n *\n */\n }\n\n /**\n * Helper function to allow us to modify the timeout used when\n * performing Firestore database operations.\n *\n * The reason for this is that it seems that there's no other\n * practical solution to change the default timeout of 10mins\n * that Firestore has.\n *\n */\n private async withTimeout<T>(operation: Promise<T>): Promise<T> {\n const timer = new Promise<never>((_, reject) =>\n setTimeout(() => {\n reject(new Error(`Operation timed out after ${this.timeout}ms`));\n }, this.timeout),\n );\n return Promise.race<T>([operation, timer]);\n }\n\n /**\n * Used to verify that the database is reachable.\n */\n private async verify(): Promise<void> {\n await this.withTimeout(this.database.collection(this.path).limit(1).get());\n }\n}\n"],"names":["Firestore"],"mappings":";;;;AAkCO,MAAM,kBAAA,GAAqB;AAC3B,MAAM,qBAAA,GAAwB;AAE9B,MAAM,iBAAA,CAAsC;AAAA,EACjD,aAAa,OACX,QAAA,EAC4B;AAC5B,IAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,GAAG,iBAAA,EAAkB,GAAI,YAAY,EAAC;AAC7D,IAAA,MAAM,QAAA,GAAW,IAAIA,mBAAA,CAAU,iBAAiB,CAAA;AAEhD,IAAA,OAAO,IAAI,iBAAA;AAAA,MACT,QAAA;AAAA,MACA,IAAA,IAAQ,qBAAA;AAAA,MACR,OAAA,IAAW;AAAA,KACb;AAAA,EACF;AAAA,EAEiB,QAAA;AAAA,EACA,IAAA;AAAA,EACA,OAAA;AAAA,EAET,WAAA,CAAY,QAAA,EAAqB,IAAA,EAAc,OAAA,EAAiB;AACtE,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AAAA,EACjB;AAAA,EAEA,aAAa,gBAAA,CACX,QAAA,EACA,MAAA,EACe;AACf,IAAA,IAAI;AACF,MAAA,MAAM,SAAS,MAAA,EAAO;AAAA,IACxB,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,aAAA,EAAe;AAC1C,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,+BAAA,EAAmC,MAAgB,OAAO,CAAA;AAAA,SAC5D;AAAA,MACF;AACA,MAAA,MAAA,EAAQ,IAAA;AAAA,QACN,CAAA,+BAAA,EAAmC,MAAgB,OAAO,CAAA;AAAA,OAC5D;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,GAAA,EAA4B;AACvC,IAAA,MAAM,IAAA,CAAK,WAAA;AAAA,MACT,IAAA,CAAK,QAAA,CAAS,UAAA,CAAW,IAAA,CAAK,IAAI,EAAE,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA,CAAE,GAAA,CAAI;AAAA,QACnD,KAAK,GAAA,CAAI,GAAA;AAAA,QACT;AAAA,OACD;AAAA,KACH;AAAA,EACF;AAAA,EAEA,MAAM,QAAA,GAA4C;AAChD,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,WAAA;AAAA,MACtB,KAAK,QAAA,CAAS,UAAA,CAAW,IAAA,CAAK,IAAI,EAAE,GAAA;AAAI,KAC1C;AAEA,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,CAAA,GAAA,KAAO;AAC1B,QAAA,MAAM,EAAE,GAAA,EAAI,GAAI,GAAA,CAAI,IAAA,EAAK;AAEzB,QAAA,OAAO;AAAA,UACL,SAAA,EAAW,GAAA,CAAI,UAAA,CAAW,MAAA,EAAO;AAAA,UACjC,KAAK,OAAO,GAAA,KAAQ,WAAW,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,GAAI;AAAA,SACnD;AAAA,MACF,CAAC;AAAA,KACH;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,IAAA,EAA+B;AAE9C,IAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,MAAA,MAAM,IAAA,CAAK,WAAA;AAAA,QACT,IAAA,CAAK,SAAS,UAAA,CAAW,IAAA,CAAK,IAAI,CAAA,CAAE,GAAA,CAAI,GAAG,CAAA,CAAE,MAAA;AAAO,OACtD;AAAA,IACF;AAAA,EAwBF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,YAAe,SAAA,EAAmC;AAC9D,IAAA,MAAM,QAAQ,IAAI,OAAA;AAAA,MAAe,CAAC,CAAA,EAAG,MAAA,KACnC,UAAA,CAAW,MAAM;AACf,QAAA,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,IAAA,CAAK,OAAO,IAAI,CAAC,CAAA;AAAA,MACjE,CAAA,EAAG,KAAK,OAAO;AAAA,KACjB;AACA,IAAA,OAAO,OAAA,CAAQ,IAAA,CAAQ,CAAC,SAAA,EAAW,KAAK,CAAC,CAAA;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,MAAA,GAAwB;AACpC,IAAA,MAAM,IAAA,CAAK,WAAA,CAAY,IAAA,CAAK,QAAA,CAAS,UAAA,CAAW,IAAA,CAAK,IAAI,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,CAAE,GAAA,EAAK,CAAA;AAAA,EAC3E;AACF;;;;;;"}
1
+ {"version":3,"file":"FirestoreKeyStore.cjs.js","sources":["../../src/identity/FirestoreKeyStore.ts"],"sourcesContent":["/*\n * Copyright 2021 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 {\n DocumentData,\n Firestore,\n QuerySnapshot,\n Settings,\n WriteResult,\n} from '@google-cloud/firestore';\n\nimport { toError } from '@backstage/errors';\nimport { AnyJWK, KeyStore, StoredKey } from './types';\n\nexport type FirestoreKeyStoreSettings = Settings & Options;\n\ntype Options = {\n path?: string;\n timeout?: number;\n};\n\nexport const DEFAULT_TIMEOUT_MS = 10000;\nexport const DEFAULT_DOCUMENT_PATH = 'sessions';\n\nexport class FirestoreKeyStore implements KeyStore {\n static async create(\n settings?: FirestoreKeyStoreSettings,\n ): Promise<FirestoreKeyStore> {\n const { path, timeout, ...firestoreSettings } = settings ?? {};\n const database = new Firestore(firestoreSettings);\n\n return new FirestoreKeyStore(\n database,\n path ?? DEFAULT_DOCUMENT_PATH,\n timeout ?? DEFAULT_TIMEOUT_MS,\n );\n }\n\n private readonly database: Firestore;\n private readonly path: string;\n private readonly timeout: number;\n\n private constructor(database: Firestore, path: string, timeout: number) {\n this.database = database;\n this.path = path;\n this.timeout = timeout;\n }\n\n static async verifyConnection(\n keyStore: FirestoreKeyStore,\n logger?: LoggerService,\n ): Promise<void> {\n try {\n await keyStore.verify();\n } catch (error) {\n const err = toError(error);\n if (process.env.NODE_ENV !== 'development') {\n throw new Error(`Failed to connect to database: ${err.message}`);\n }\n logger?.warn(`Failed to connect to database: ${err.message}`);\n }\n }\n\n async addKey(key: AnyJWK): Promise<void> {\n await this.withTimeout<WriteResult>(\n this.database.collection(this.path).doc(key.kid).set({\n kid: key.kid,\n key: key,\n }),\n );\n }\n\n async listKeys(): Promise<{ items: StoredKey[] }> {\n const keys = await this.withTimeout<QuerySnapshot<DocumentData>>(\n this.database.collection(this.path).get(),\n );\n\n return {\n items: keys.docs.map(doc => {\n const { key } = doc.data();\n\n return {\n createdAt: doc.createTime.toDate(),\n key: typeof key === 'string' ? JSON.parse(key) : key,\n };\n }),\n };\n }\n\n async removeKeys(kids: string[]): Promise<void> {\n // This is probably really slow, but it's done async in the background\n for (const kid of kids) {\n await this.withTimeout<WriteResult>(\n this.database.collection(this.path).doc(kid).delete(),\n );\n }\n\n /**\n * This could be achieved with batching but there's a couple of limitations with that:\n *\n * - A batched write can contain a maximum of 500 operations\n * https://firebase.google.com/docs/firestore/manage-data/transactions#batched-writes\n *\n * - The \"in\" operator can combine a maximum of 10 equality clauses\n * https://firebase.google.com/docs/firestore/query-data/queries#in_not-in_and_array-contains-any\n *\n * Example:\n *\n * const batch = this.database.batch();\n * const docs = await this.database\n * .collection(this.path)\n * .where('kid', 'in', kids)\n * .get();\n * docs.forEach(doc => {\n * batch.delete(doc.ref);\n * });\n * await batch.commit();\n *\n */\n }\n\n /**\n * Helper function to allow us to modify the timeout used when\n * performing Firestore database operations.\n *\n * The reason for this is that it seems that there's no other\n * practical solution to change the default timeout of 10mins\n * that Firestore has.\n *\n */\n private async withTimeout<T>(operation: Promise<T>): Promise<T> {\n const timer = new Promise<never>((_, reject) =>\n setTimeout(() => {\n reject(new Error(`Operation timed out after ${this.timeout}ms`));\n }, this.timeout),\n );\n return Promise.race<T>([operation, timer]);\n }\n\n /**\n * Used to verify that the database is reachable.\n */\n private async verify(): Promise<void> {\n await this.withTimeout(this.database.collection(this.path).limit(1).get());\n }\n}\n"],"names":["Firestore","toError"],"mappings":"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"}
@@ -62,13 +62,14 @@ function bindProviderRouters(targetRouter, options) {
62
62
  }
63
63
  targetRouter.use(`/${providerId}`, r);
64
64
  } catch (e) {
65
- errors.assertError(e);
66
65
  if (process.env.NODE_ENV !== "development") {
67
66
  throw new Error(
68
- `Failed to initialize ${providerId} auth provider, ${e.message}`
67
+ `Failed to initialize ${providerId} auth provider, ${errors.toError(e).message}`
69
68
  );
70
69
  }
71
- logger.warn(`Skipping ${providerId} auth provider, ${e.message}`);
70
+ logger.warn(
71
+ `Skipping ${providerId} auth provider, ${errors.toError(e).message}`
72
+ );
72
73
  targetRouter.use(`/${providerId}`, () => {
73
74
  throw new errors.NotFoundError(
74
75
  `Auth provider registered for '${providerId}' is misconfigured. This could mean the configs under auth.providers.${providerId} are missing or the environment variables used are not defined. Check the auth backend plugin logs when the backend starts to see more details.`
@@ -1 +1 @@
1
- {"version":3,"file":"router.cjs.js","sources":["../../src/providers/router.ts"],"sourcesContent":["/*\n * Copyright 2024 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 { AuthService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { assertError, NotFoundError } from '@backstage/errors';\nimport {\n AuthOwnershipResolver,\n AuthProviderFactory,\n} from '@backstage/plugin-auth-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { Minimatch } from 'minimatch';\nimport { CatalogAuthResolverContext } from '../lib/resolvers/CatalogAuthResolverContext';\nimport { TokenIssuer } from '../identity/types';\nimport { UserInfoDatabase } from '../database/UserInfoDatabase';\n\nexport type ProviderFactories = { [s: string]: AuthProviderFactory };\n\nexport function bindProviderRouters(\n targetRouter: express.Router,\n options: {\n providers: ProviderFactories;\n appUrl: string;\n baseUrl: string;\n config: Config;\n logger: LoggerService;\n auth: AuthService;\n tokenIssuer: TokenIssuer;\n userInfo: UserInfoDatabase;\n ownershipResolver?: AuthOwnershipResolver;\n catalog: CatalogService;\n },\n) {\n const {\n providers,\n appUrl,\n baseUrl,\n config,\n logger,\n auth,\n tokenIssuer,\n catalog,\n ownershipResolver,\n userInfo,\n } = options;\n\n const providersConfig = config.getOptionalConfig('auth.providers');\n\n const isOriginAllowed = createOriginFilter(config);\n\n for (const [providerId, providerFactory] of Object.entries(providers)) {\n if (providersConfig?.has(providerId)) {\n logger.info(`Configuring auth provider: ${providerId}`);\n try {\n const provider = providerFactory({\n providerId,\n appUrl,\n baseUrl,\n isOriginAllowed,\n globalConfig: {\n baseUrl,\n appUrl,\n isOriginAllowed,\n },\n config: providersConfig.getConfig(providerId),\n logger,\n resolverContext: CatalogAuthResolverContext.create({\n logger,\n catalog,\n tokenIssuer,\n auth,\n ownershipResolver,\n userInfo,\n }),\n });\n\n const r = Router();\n\n r.get('/start', provider.start.bind(provider));\n r.get('/handler/frame', provider.frameHandler.bind(provider));\n r.post('/handler/frame', provider.frameHandler.bind(provider));\n if (provider.logout) {\n r.post('/logout', provider.logout.bind(provider));\n }\n if (provider.refresh) {\n r.get('/refresh', provider.refresh.bind(provider));\n r.post('/refresh', provider.refresh.bind(provider));\n }\n\n targetRouter.use(`/${providerId}`, r);\n } catch (e) {\n assertError(e);\n if (process.env.NODE_ENV !== 'development') {\n throw new Error(\n `Failed to initialize ${providerId} auth provider, ${e.message}`,\n );\n }\n\n logger.warn(`Skipping ${providerId} auth provider, ${e.message}`);\n\n targetRouter.use(`/${providerId}`, () => {\n // If the user added the provider under auth.providers but the clientId and clientSecret etc. were not found.\n throw new NotFoundError(\n `Auth provider registered for '${providerId}' is misconfigured. This could mean the configs under ` +\n `auth.providers.${providerId} are missing or the environment variables used are not defined. ` +\n `Check the auth backend plugin logs when the backend starts to see more details.`,\n );\n });\n }\n } else {\n targetRouter.use(`/${providerId}`, () => {\n throw new NotFoundError(\n `No auth provider registered for '${providerId}'`,\n );\n });\n }\n }\n}\n\nexport function createOriginFilter(\n config: Config,\n): (origin: string) => boolean {\n const appUrl = config.getString('app.baseUrl');\n const { origin: appOrigin } = new URL(appUrl);\n\n const allowedOrigins = config.getOptionalStringArray(\n 'auth.experimentalExtraAllowedOrigins',\n );\n\n const allowedOriginPatterns =\n allowedOrigins?.map(\n pattern => new Minimatch(pattern, { nocase: true, noglobstar: true }),\n ) ?? [];\n\n return origin => {\n if (origin === appOrigin) {\n return true;\n }\n return allowedOriginPatterns.some(pattern => pattern.match(origin));\n };\n}\n"],"names":["CatalogAuthResolverContext","Router","assertError","NotFoundError","Minimatch"],"mappings":"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"}
1
+ {"version":3,"file":"router.cjs.js","sources":["../../src/providers/router.ts"],"sourcesContent":["/*\n * Copyright 2024 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 { AuthService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { NotFoundError, toError } from '@backstage/errors';\nimport {\n AuthOwnershipResolver,\n AuthProviderFactory,\n} from '@backstage/plugin-auth-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { Minimatch } from 'minimatch';\nimport { CatalogAuthResolverContext } from '../lib/resolvers/CatalogAuthResolverContext';\nimport { TokenIssuer } from '../identity/types';\nimport { UserInfoDatabase } from '../database/UserInfoDatabase';\n\nexport type ProviderFactories = { [s: string]: AuthProviderFactory };\n\nexport function bindProviderRouters(\n targetRouter: express.Router,\n options: {\n providers: ProviderFactories;\n appUrl: string;\n baseUrl: string;\n config: Config;\n logger: LoggerService;\n auth: AuthService;\n tokenIssuer: TokenIssuer;\n userInfo: UserInfoDatabase;\n ownershipResolver?: AuthOwnershipResolver;\n catalog: CatalogService;\n },\n) {\n const {\n providers,\n appUrl,\n baseUrl,\n config,\n logger,\n auth,\n tokenIssuer,\n catalog,\n ownershipResolver,\n userInfo,\n } = options;\n\n const providersConfig = config.getOptionalConfig('auth.providers');\n\n const isOriginAllowed = createOriginFilter(config);\n\n for (const [providerId, providerFactory] of Object.entries(providers)) {\n if (providersConfig?.has(providerId)) {\n logger.info(`Configuring auth provider: ${providerId}`);\n try {\n const provider = providerFactory({\n providerId,\n appUrl,\n baseUrl,\n isOriginAllowed,\n globalConfig: {\n baseUrl,\n appUrl,\n isOriginAllowed,\n },\n config: providersConfig.getConfig(providerId),\n logger,\n resolverContext: CatalogAuthResolverContext.create({\n logger,\n catalog,\n tokenIssuer,\n auth,\n ownershipResolver,\n userInfo,\n }),\n });\n\n const r = Router();\n\n r.get('/start', provider.start.bind(provider));\n r.get('/handler/frame', provider.frameHandler.bind(provider));\n r.post('/handler/frame', provider.frameHandler.bind(provider));\n if (provider.logout) {\n r.post('/logout', provider.logout.bind(provider));\n }\n if (provider.refresh) {\n r.get('/refresh', provider.refresh.bind(provider));\n r.post('/refresh', provider.refresh.bind(provider));\n }\n\n targetRouter.use(`/${providerId}`, r);\n } catch (e) {\n if (process.env.NODE_ENV !== 'development') {\n throw new Error(\n `Failed to initialize ${providerId} auth provider, ${\n toError(e).message\n }`,\n );\n }\n\n logger.warn(\n `Skipping ${providerId} auth provider, ${toError(e).message}`,\n );\n\n targetRouter.use(`/${providerId}`, () => {\n // If the user added the provider under auth.providers but the clientId and clientSecret etc. were not found.\n throw new NotFoundError(\n `Auth provider registered for '${providerId}' is misconfigured. This could mean the configs under ` +\n `auth.providers.${providerId} are missing or the environment variables used are not defined. ` +\n `Check the auth backend plugin logs when the backend starts to see more details.`,\n );\n });\n }\n } else {\n targetRouter.use(`/${providerId}`, () => {\n throw new NotFoundError(\n `No auth provider registered for '${providerId}'`,\n );\n });\n }\n }\n}\n\nexport function createOriginFilter(\n config: Config,\n): (origin: string) => boolean {\n const appUrl = config.getString('app.baseUrl');\n const { origin: appOrigin } = new URL(appUrl);\n\n const allowedOrigins = config.getOptionalStringArray(\n 'auth.experimentalExtraAllowedOrigins',\n );\n\n const allowedOriginPatterns =\n allowedOrigins?.map(\n pattern => new Minimatch(pattern, { nocase: true, noglobstar: true }),\n ) ?? [];\n\n return origin => {\n if (origin === appOrigin) {\n return true;\n }\n return allowedOriginPatterns.some(pattern => pattern.match(origin));\n };\n}\n"],"names":["CatalogAuthResolverContext","Router","toError","NotFoundError","Minimatch"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CimdClient.cjs.js","sources":["../../src/service/CimdClient.ts"],"sourcesContent":["/*\n * Copyright 2025 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, isError } from '@backstage/errors';\nimport { lookup } from 'node:dns/promises';\nimport ipaddr from 'ipaddr.js';\n\nconst FETCH_TIMEOUT_MS = 10000;\nconst MAX_RESPONSE_BYTES = 64 * 1024;\n\n/** Auth methods that require a client secret - forbidden for CIMD clients */\nconst FORBIDDEN_AUTH_METHODS = [\n 'client_secret_basic',\n 'client_secret_post',\n 'client_secret_jwt',\n];\n\n/**\n * Raw metadata document from a CIMD URL.\n * Note: client_secret fields are included for validation (must NOT be present).\n */\ninterface CimdMetadata {\n client_id: string;\n client_name?: string;\n redirect_uris: string[];\n response_types?: string[];\n grant_types?: string[];\n scope?: string;\n token_endpoint_auth_method?: string;\n client_secret?: string;\n client_secret_expires_at?: number;\n}\n\n/** Validated CIMD client info */\nexport interface CimdClientInfo {\n clientId: string;\n clientName: string;\n redirectUris: string[];\n responseTypes: string[];\n grantTypes: string[];\n scope?: string;\n}\n\n/**\n * Validates and parses a CIMD URL per the IETF draft specification.\n * Requires HTTPS for production, but allows HTTP for localhost (development).\n *\n * @see https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/\n * @throws InputError if the URL is invalid per the CIMD spec\n */\nexport function validateCimdUrl(clientId: string): URL {\n // Per IETF draft: MUST NOT contain single-dot or double-dot path segments\n // Check before URL parsing since the URL constructor normalizes these away\n if (/\\/\\.\\.?(\\/|$)/.test(clientId)) {\n throw new InputError(\n 'Invalid client_id: path must not contain dot segments',\n );\n }\n\n let url: URL;\n try {\n url = new URL(clientId);\n } catch {\n throw new InputError('Invalid client_id: not a valid URL');\n }\n\n const isHttps = url.protocol === 'https:';\n const isLocalHttp =\n url.protocol === 'http:' &&\n (url.hostname === 'localhost' || url.hostname === '127.0.0.1') &&\n process.env.NODE_ENV === 'development';\n\n if (!isHttps && !isLocalHttp) {\n throw new InputError(\n 'Invalid client_id: must use HTTPS (or HTTP for localhost in development)',\n );\n }\n\n if (url.pathname === '' || url.pathname === '/') {\n throw new InputError('Invalid client_id: must have a path component');\n }\n\n if (url.hash) {\n throw new InputError('Invalid client_id: must not contain a fragment');\n }\n\n if (url.username || url.password) {\n throw new InputError('Invalid client_id: must not contain credentials');\n }\n\n // Per IETF draft: SHOULD NOT include a query string\n // We reject this for stricter compliance and security\n if (url.search) {\n throw new InputError('Invalid client_id: must not contain a query string');\n }\n\n return url;\n}\n\n/**\n * Checks if a client_id is a valid CIMD URL.\n * Requires HTTPS for production, but allows HTTP for localhost (development).\n */\nexport function isCimdUrl(clientId: string): boolean {\n try {\n validateCimdUrl(clientId);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * SSRF (Server-Side Request Forgery) Protection\n *\n * When fetching CIMD metadata from client-provided URLs, we must prevent\n * attackers from tricking Backstage into accessing internal resources.\n * For example, an attacker could provide a URL that resolves to:\n * - 127.0.0.1 (localhost services)\n * - 10.x.x.x, 172.16-31.x.x, 192.168.x.x (internal network)\n * - Cloud metadata endpoints (169.254.169.254)\n *\n * We use ipaddr.js to check if resolved IPs are in non-public ranges.\n * Only 'unicast' (public internet) addresses are allowed.\n *\n * @see https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/\n * Section 5.1 - Security Considerations\n */\nfunction isNonPublicIp(ip: string): boolean {\n try {\n const addr = ipaddr.parse(ip);\n const range = addr.range();\n // Only allow public unicast addresses\n return range !== 'unicast';\n } catch {\n // If we can't parse the IP, treat it as non-public and block it\n return true;\n }\n}\n\nasync function validateHostNotPrivate(hostname: string): Promise<void> {\n try {\n const addresses = await lookup(hostname, { all: true });\n const nonPublicAddr = addresses.find(addr => isNonPublicIp(addr.address));\n if (nonPublicAddr) {\n throw new InputError('Invalid client_id URL');\n }\n } catch (error) {\n if (isError(error) && error.name === 'InputError') throw error;\n throw new InputError('Failed to fetch client metadata');\n }\n}\n\nfunction validateMetadata(\n metadata: CimdMetadata,\n expectedClientId: string,\n): void {\n if (metadata.client_id !== expectedClientId) {\n throw new InputError('Client ID mismatch in metadata document');\n }\n\n if (\n !Array.isArray(metadata.redirect_uris) ||\n metadata.redirect_uris.length === 0\n ) {\n throw new InputError('Metadata must include at least one redirect_uri');\n }\n\n for (const uri of metadata.redirect_uris) {\n if (!URL.canParse(uri)) {\n throw new InputError(`Invalid redirect_uri in metadata: ${uri}`);\n }\n }\n\n if (\n metadata.client_secret !== undefined ||\n metadata.client_secret_expires_at !== undefined\n ) {\n throw new InputError('Client metadata must not contain client_secret');\n }\n\n if (\n metadata.token_endpoint_auth_method &&\n FORBIDDEN_AUTH_METHODS.includes(metadata.token_endpoint_auth_method)\n ) {\n throw new InputError('Client metadata uses forbidden auth method');\n }\n}\n\n/**\n * Fetches and validates a CIMD metadata document.\n * @throws InputError if fetching or validation fails\n */\nexport async function fetchCimdMetadata(opts: {\n clientId: string;\n validatedUrl?: URL;\n}): Promise<CimdClientInfo> {\n const url = opts.validatedUrl ?? validateCimdUrl(opts.clientId);\n\n // Skip SSRF validation for localhost in development only\n const isLocalhostDev =\n (url.hostname === 'localhost' || url.hostname === '127.0.0.1') &&\n process.env.NODE_ENV === 'development';\n if (!isLocalhostDev) {\n await validateHostNotPrivate(url.hostname);\n }\n\n let response: Response;\n try {\n response = await fetch(url.toString(), {\n method: 'GET',\n headers: { Accept: 'application/json' },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n redirect: 'error',\n });\n } catch {\n throw new InputError('Failed to fetch client metadata');\n }\n\n if (!response.ok) {\n throw new InputError('Failed to fetch client metadata');\n }\n\n const contentLength = Number(response.headers.get('content-length'));\n if (contentLength > MAX_RESPONSE_BYTES) {\n throw new InputError('Client metadata document too large');\n }\n\n let metadata: CimdMetadata;\n try {\n metadata = await response.json();\n } catch {\n throw new InputError('Invalid client metadata document');\n }\n\n validateMetadata(metadata, opts.clientId);\n\n return {\n clientId: metadata.client_id,\n clientName: metadata.client_name || metadata.client_id,\n redirectUris: metadata.redirect_uris,\n responseTypes: metadata.response_types || ['code'],\n grantTypes: metadata.grant_types || ['authorization_code'],\n scope: metadata.scope,\n };\n}\n"],"names":["InputError","ipaddr","lookup","isError"],"mappings":"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"}
1
+ {"version":3,"file":"CimdClient.cjs.js","sources":["../../src/service/CimdClient.ts"],"sourcesContent":["/*\n * Copyright 2025 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, isError } from '@backstage/errors';\nimport { lookup } from 'node:dns/promises';\nimport ipaddr from 'ipaddr.js';\n\nconst FETCH_TIMEOUT_MS = 10000;\nconst MAX_RESPONSE_BYTES = 64 * 1024;\n\n/** Auth methods that require a client secret - forbidden for CIMD clients */\nconst FORBIDDEN_AUTH_METHODS = [\n 'client_secret_basic',\n 'client_secret_post',\n 'client_secret_jwt',\n];\n\n/**\n * Raw metadata document from a CIMD URL.\n * Note: client_secret fields are included for validation (must NOT be present).\n */\ninterface CimdMetadata {\n client_id: string;\n client_name?: string;\n redirect_uris: string[];\n response_types?: string[];\n grant_types?: string[];\n scope?: string;\n token_endpoint_auth_method?: string;\n client_secret?: string;\n client_secret_expires_at?: number;\n}\n\n/** Validated CIMD client info */\nexport interface CimdClientInfo {\n clientId: string;\n clientName: string;\n redirectUris: string[];\n responseTypes: string[];\n grantTypes: string[];\n scope?: string;\n}\n\n/**\n * Validates and parses a CIMD URL per the IETF draft specification.\n * Requires HTTPS for production, but allows HTTP for localhost (development).\n *\n * @see https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/\n * @throws InputError if the URL is invalid per the CIMD spec\n */\nexport function validateCimdUrl(clientId: string): URL {\n // Per IETF draft: MUST NOT contain single-dot or double-dot path segments\n // Check before URL parsing since the URL constructor normalizes these away\n if (/\\/\\.\\.?(\\/|$)/.test(clientId)) {\n throw new InputError(\n 'Invalid client_id: path must not contain dot segments',\n );\n }\n\n let url: URL;\n try {\n url = new URL(clientId);\n } catch {\n throw new InputError('Invalid client_id: not a valid URL');\n }\n\n const isHttps = url.protocol === 'https:';\n const isLocalHttp =\n url.protocol === 'http:' &&\n (url.hostname === 'localhost' || url.hostname === '127.0.0.1') &&\n process.env.NODE_ENV === 'development';\n\n if (!isHttps && !isLocalHttp) {\n throw new InputError(\n 'Invalid client_id: must use HTTPS (or HTTP for localhost in development)',\n );\n }\n\n if (url.pathname === '' || url.pathname === '/') {\n throw new InputError('Invalid client_id: must have a path component');\n }\n\n if (url.hash) {\n throw new InputError('Invalid client_id: must not contain a fragment');\n }\n\n if (url.username || url.password) {\n throw new InputError('Invalid client_id: must not contain credentials');\n }\n\n // Per IETF draft: SHOULD NOT include a query string\n // We reject this for stricter compliance and security\n if (url.search) {\n throw new InputError('Invalid client_id: must not contain a query string');\n }\n\n return url;\n}\n\n/**\n * SSRF (Server-Side Request Forgery) Protection\n *\n * When fetching CIMD metadata from client-provided URLs, we must prevent\n * attackers from tricking Backstage into accessing internal resources.\n * For example, an attacker could provide a URL that resolves to:\n * - 127.0.0.1 (localhost services)\n * - 10.x.x.x, 172.16-31.x.x, 192.168.x.x (internal network)\n * - Cloud metadata endpoints (169.254.169.254)\n *\n * We use ipaddr.js to check if resolved IPs are in non-public ranges.\n * Only 'unicast' (public internet) addresses are allowed.\n *\n * @see https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/\n * Section 5.1 - Security Considerations\n */\nfunction isNonPublicIp(ip: string): boolean {\n try {\n const addr = ipaddr.parse(ip);\n const range = addr.range();\n // Only allow public unicast addresses\n return range !== 'unicast';\n } catch {\n // If we can't parse the IP, treat it as non-public and block it\n return true;\n }\n}\n\nasync function validateHostNotPrivate(hostname: string): Promise<void> {\n try {\n const addresses = await lookup(hostname, { all: true });\n const nonPublicAddr = addresses.find(addr => isNonPublicIp(addr.address));\n if (nonPublicAddr) {\n throw new InputError('Invalid client_id URL');\n }\n } catch (error) {\n if (isError(error) && error.name === 'InputError') throw error;\n throw new InputError('Failed to fetch client metadata');\n }\n}\n\nfunction validateMetadata(\n metadata: CimdMetadata,\n expectedClientId: string,\n): void {\n if (metadata.client_id !== expectedClientId) {\n throw new InputError('Client ID mismatch in metadata document');\n }\n\n if (\n !Array.isArray(metadata.redirect_uris) ||\n metadata.redirect_uris.length === 0\n ) {\n throw new InputError('Metadata must include at least one redirect_uri');\n }\n\n for (const uri of metadata.redirect_uris) {\n if (!URL.canParse(uri)) {\n throw new InputError(`Invalid redirect_uri in metadata: ${uri}`);\n }\n }\n\n if (\n metadata.client_secret !== undefined ||\n metadata.client_secret_expires_at !== undefined\n ) {\n throw new InputError('Client metadata must not contain client_secret');\n }\n\n if (\n metadata.token_endpoint_auth_method &&\n FORBIDDEN_AUTH_METHODS.includes(metadata.token_endpoint_auth_method)\n ) {\n throw new InputError('Client metadata uses forbidden auth method');\n }\n}\n\n/**\n * Fetches and validates a CIMD metadata document.\n * @throws InputError if fetching or validation fails\n */\nexport async function fetchCimdMetadata(opts: {\n clientId: string;\n validatedUrl?: URL;\n}): Promise<CimdClientInfo> {\n const url = opts.validatedUrl ?? validateCimdUrl(opts.clientId);\n\n // Skip SSRF validation for localhost in development only\n const isLocalhostDev =\n (url.hostname === 'localhost' || url.hostname === '127.0.0.1') &&\n process.env.NODE_ENV === 'development';\n if (!isLocalhostDev) {\n await validateHostNotPrivate(url.hostname);\n }\n\n let response: Response;\n try {\n response = await fetch(url.toString(), {\n method: 'GET',\n headers: { Accept: 'application/json' },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n redirect: 'error',\n });\n } catch {\n throw new InputError('Failed to fetch client metadata');\n }\n\n if (!response.ok) {\n throw new InputError('Failed to fetch client metadata');\n }\n\n const contentLength = Number(response.headers.get('content-length'));\n if (contentLength > MAX_RESPONSE_BYTES) {\n throw new InputError('Client metadata document too large');\n }\n\n let metadata: CimdMetadata;\n try {\n metadata = await response.json();\n } catch {\n throw new InputError('Invalid client metadata document');\n }\n\n validateMetadata(metadata, opts.clientId);\n\n return {\n clientId: metadata.client_id,\n clientName: metadata.client_name || metadata.client_id,\n redirectUris: metadata.redirect_uris,\n responseTypes: metadata.response_types || ['code'],\n grantTypes: metadata.grant_types || ['authorization_code'],\n scope: metadata.scope,\n };\n}\n"],"names":["InputError","ipaddr","lookup","isError"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-auth-backend",
3
- "version": "0.28.0-next.1",
3
+ "version": "0.28.0",
4
4
  "description": "A Backstage backend plugin that handles authentication",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",
@@ -47,13 +47,13 @@
47
47
  "test": "backstage-cli package test"
48
48
  },
49
49
  "dependencies": {
50
- "@backstage/backend-plugin-api": "1.9.0-next.1",
51
- "@backstage/catalog-model": "1.7.7",
52
- "@backstage/config": "1.3.6",
53
- "@backstage/errors": "1.2.7",
54
- "@backstage/plugin-auth-node": "0.7.0-next.1",
55
- "@backstage/plugin-catalog-node": "2.1.1-next.1",
56
- "@backstage/types": "1.2.2",
50
+ "@backstage/backend-plugin-api": "^1.9.0",
51
+ "@backstage/catalog-model": "^1.8.0",
52
+ "@backstage/config": "^1.3.7",
53
+ "@backstage/errors": "^1.3.0",
54
+ "@backstage/plugin-auth-node": "^0.7.0",
55
+ "@backstage/plugin-catalog-node": "^2.2.0",
56
+ "@backstage/types": "^1.2.2",
57
57
  "@google-cloud/firestore": "^7.0.0",
58
58
  "connect-session-knex": "^4.0.0",
59
59
  "cookie-parser": "^1.4.5",
@@ -73,11 +73,11 @@
73
73
  "zod-validation-error": "^5.0.0"
74
74
  },
75
75
  "devDependencies": {
76
- "@backstage/backend-defaults": "0.16.1-next.1",
77
- "@backstage/backend-test-utils": "1.11.2-next.1",
78
- "@backstage/cli": "0.36.1-next.1",
79
- "@backstage/plugin-auth-backend-module-google-provider": "0.3.14-next.1",
80
- "@backstage/plugin-auth-backend-module-guest-provider": "0.2.18-next.1",
76
+ "@backstage/backend-defaults": "^0.17.0",
77
+ "@backstage/backend-test-utils": "^1.11.2",
78
+ "@backstage/cli": "^0.36.1",
79
+ "@backstage/plugin-auth-backend-module-google-provider": "^0.3.14",
80
+ "@backstage/plugin-auth-backend-module-guest-provider": "^0.2.18",
81
81
  "@types/cookie-parser": "^1.4.2",
82
82
  "@types/express": "^4.17.6",
83
83
  "@types/express-session": "^1.17.2",