@backstage/backend-defaults 0.5.3-next.3 → 0.6.0-next.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/config.d.ts +5 -10
  3. package/dist/auth.cjs.js +1 -0
  4. package/dist/auth.cjs.js.map +1 -1
  5. package/dist/auth.d.ts +27 -1
  6. package/dist/cache.d.ts +0 -1
  7. package/dist/entrypoints/auth/DefaultAuthService.cjs.js +4 -1
  8. package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -1
  9. package/dist/entrypoints/auth/authServiceFactory.cjs.js +30 -9
  10. package/dist/entrypoints/auth/authServiceFactory.cjs.js.map +1 -1
  11. package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js +6 -10
  12. package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js.map +1 -1
  13. package/dist/entrypoints/cache/CacheManager.cjs.js +28 -28
  14. package/dist/entrypoints/cache/CacheManager.cjs.js.map +1 -1
  15. package/dist/entrypoints/httpAuth/httpAuthServiceFactory.cjs.js +22 -9
  16. package/dist/entrypoints/httpAuth/httpAuthServiceFactory.cjs.js.map +1 -1
  17. package/dist/entrypoints/httpRouter/http/createAuthIntegrationRouter.cjs.js.map +1 -0
  18. package/dist/entrypoints/httpRouter/http/createCookieAuthRefreshMiddleware.cjs.js.map +1 -0
  19. package/dist/entrypoints/httpRouter/http/createCredentialsBarrier.cjs.js.map +1 -0
  20. package/dist/entrypoints/httpRouter/http/createLifecycleMiddleware.cjs.js.map +1 -0
  21. package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js +4 -4
  22. package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js.map +1 -1
  23. package/dist/entrypoints/rootHttpRouter/http/MiddlewareFactory.cjs.js +36 -13
  24. package/dist/entrypoints/rootHttpRouter/http/MiddlewareFactory.cjs.js.map +1 -1
  25. package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js +156 -0
  26. package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js.map +1 -0
  27. package/dist/entrypoints/urlReader/lib/UrlReaders.cjs.js +2 -0
  28. package/dist/entrypoints/urlReader/lib/UrlReaders.cjs.js.map +1 -1
  29. package/dist/httpAuth.cjs.js +1 -0
  30. package/dist/httpAuth.cjs.js.map +1 -1
  31. package/dist/httpAuth.d.ts +36 -2
  32. package/dist/httpRouter.cjs.js +8 -0
  33. package/dist/httpRouter.cjs.js.map +1 -1
  34. package/dist/httpRouter.d.ts +62 -1
  35. package/dist/package.json.cjs.js +8 -8
  36. package/dist/urlReader.cjs.js +2 -0
  37. package/dist/urlReader.cjs.js.map +1 -1
  38. package/dist/urlReader.d.ts +23 -2
  39. package/package.json +24 -24
  40. package/dist/entrypoints/httpRouter/createAuthIntegrationRouter.cjs.js.map +0 -1
  41. package/dist/entrypoints/httpRouter/createCookieAuthRefreshMiddleware.cjs.js.map +0 -1
  42. package/dist/entrypoints/httpRouter/createCredentialsBarrier.cjs.js.map +0 -1
  43. package/dist/entrypoints/httpRouter/createLifecycleMiddleware.cjs.js.map +0 -1
  44. /package/dist/entrypoints/httpRouter/{createAuthIntegrationRouter.cjs.js → http/createAuthIntegrationRouter.cjs.js} +0 -0
  45. /package/dist/entrypoints/httpRouter/{createCookieAuthRefreshMiddleware.cjs.js → http/createCookieAuthRefreshMiddleware.cjs.js} +0 -0
  46. /package/dist/entrypoints/httpRouter/{createCredentialsBarrier.cjs.js → http/createCredentialsBarrier.cjs.js} +0 -0
  47. /package/dist/entrypoints/httpRouter/{createLifecycleMiddleware.cjs.js → http/createLifecycleMiddleware.cjs.js} +0 -0
@@ -7,15 +7,13 @@ var cookie = require('cookie');
7
7
  const FIVE_MINUTES_MS = 5 * 60 * 1e3;
8
8
  const BACKSTAGE_AUTH_COOKIE = "backstage-auth";
9
9
  function getTokenFromRequest(req) {
10
+ let token;
10
11
  const authHeader = req.headers.authorization;
11
12
  if (typeof authHeader === "string") {
12
13
  const matches = authHeader.match(/^Bearer[ ]+(\S+)$/i);
13
- const token = matches?.[1];
14
- if (token) {
15
- return token;
16
- }
14
+ token = matches?.[1];
17
15
  }
18
- return void 0;
16
+ return { token };
19
17
  }
20
18
  function getCookieFromRequest(req) {
21
19
  const cookieHeader = req.headers.cookie;
@@ -37,20 +35,30 @@ class DefaultHttpAuthService {
37
35
  #auth;
38
36
  #discovery;
39
37
  #pluginId;
40
- constructor(auth, discovery, pluginId) {
38
+ #getToken;
39
+ constructor(auth, discovery, pluginId, getToken) {
41
40
  this.#auth = auth;
42
41
  this.#discovery = discovery;
43
42
  this.#pluginId = pluginId;
43
+ this.#getToken = getToken ?? getTokenFromRequest;
44
+ }
45
+ static create(options) {
46
+ return new DefaultHttpAuthService(
47
+ options.auth,
48
+ options.discovery,
49
+ options.pluginId,
50
+ options.getTokenFromRequest
51
+ );
44
52
  }
45
53
  async #extractCredentialsFromRequest(req) {
46
- const token = getTokenFromRequest(req);
54
+ const { token } = this.#getToken(req);
47
55
  if (!token) {
48
56
  return await this.#auth.getNoneCredentials();
49
57
  }
50
58
  return await this.#auth.authenticate(token);
51
59
  }
52
60
  async #extractLimitedCredentialsFromRequest(req) {
53
- const token = getTokenFromRequest(req);
61
+ const { token } = this.#getToken(req);
54
62
  if (token) {
55
63
  return await this.#auth.authenticate(token, {
56
64
  allowLimitedAccess: true
@@ -184,9 +192,14 @@ const httpAuthServiceFactory = backendPluginApi.createServiceFactory({
184
192
  plugin: backendPluginApi.coreServices.pluginMetadata
185
193
  },
186
194
  async factory({ auth, discovery, plugin }) {
187
- return new DefaultHttpAuthService(auth, discovery, plugin.getId());
195
+ return DefaultHttpAuthService.create({
196
+ auth,
197
+ discovery,
198
+ pluginId: plugin.getId()
199
+ });
188
200
  }
189
201
  });
190
202
 
203
+ exports.DefaultHttpAuthService = DefaultHttpAuthService;
191
204
  exports.httpAuthServiceFactory = httpAuthServiceFactory;
192
205
  //# sourceMappingURL=httpAuthServiceFactory.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"httpAuthServiceFactory.cjs.js","sources":["../../../src/entrypoints/httpAuth/httpAuthServiceFactory.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 {\n AuthService,\n BackstageCredentials,\n BackstagePrincipalTypes,\n BackstageUserPrincipal,\n DiscoveryService,\n HttpAuthService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError, NotAllowedError } from '@backstage/errors';\nimport { parse as parseCookie } from 'cookie';\nimport { Request, Response } from 'express';\n\nconst FIVE_MINUTES_MS = 5 * 60 * 1000;\n\nconst BACKSTAGE_AUTH_COOKIE = 'backstage-auth';\n\nfunction getTokenFromRequest(req: Request) {\n // TODO: support multiple auth headers (iterate rawHeaders)\n const authHeader = req.headers.authorization;\n if (typeof authHeader === 'string') {\n const matches = authHeader.match(/^Bearer[ ]+(\\S+)$/i);\n const token = matches?.[1];\n if (token) {\n return token;\n }\n }\n\n return undefined;\n}\n\nfunction getCookieFromRequest(req: Request) {\n const cookieHeader = req.headers.cookie;\n if (cookieHeader) {\n const cookies = parseCookie(cookieHeader);\n const token = cookies[BACKSTAGE_AUTH_COOKIE];\n if (token) {\n return token;\n }\n }\n\n return undefined;\n}\n\nfunction willExpireSoon(expiresAt: Date) {\n return Date.now() + FIVE_MINUTES_MS > expiresAt.getTime();\n}\n\nconst credentialsSymbol = Symbol('backstage-credentials');\nconst limitedCredentialsSymbol = Symbol('backstage-limited-credentials');\n\ntype RequestWithCredentials = Request & {\n [credentialsSymbol]?: Promise<BackstageCredentials>;\n [limitedCredentialsSymbol]?: Promise<BackstageCredentials>;\n};\n\nclass DefaultHttpAuthService implements HttpAuthService {\n readonly #auth: AuthService;\n readonly #discovery: DiscoveryService;\n readonly #pluginId: string;\n\n constructor(\n auth: AuthService,\n discovery: DiscoveryService,\n pluginId: string,\n ) {\n this.#auth = auth;\n this.#discovery = discovery;\n this.#pluginId = pluginId;\n }\n\n async #extractCredentialsFromRequest(req: Request) {\n const token = getTokenFromRequest(req);\n if (!token) {\n return await this.#auth.getNoneCredentials();\n }\n\n return await this.#auth.authenticate(token);\n }\n\n async #extractLimitedCredentialsFromRequest(req: Request) {\n const token = getTokenFromRequest(req);\n if (token) {\n return await this.#auth.authenticate(token, {\n allowLimitedAccess: true,\n });\n }\n\n const cookie = getCookieFromRequest(req);\n if (cookie) {\n return await this.#auth.authenticate(cookie, {\n allowLimitedAccess: true,\n });\n }\n\n return await this.#auth.getNoneCredentials();\n }\n\n async #getCredentials(req: RequestWithCredentials) {\n return (req[credentialsSymbol] ??=\n this.#extractCredentialsFromRequest(req));\n }\n\n async #getLimitedCredentials(req: RequestWithCredentials) {\n return (req[limitedCredentialsSymbol] ??=\n this.#extractLimitedCredentialsFromRequest(req));\n }\n\n async credentials<TAllowed extends keyof BackstagePrincipalTypes = 'unknown'>(\n req: Request,\n options?: {\n allow?: Array<TAllowed>;\n allowLimitedAccess?: boolean;\n },\n ): Promise<BackstageCredentials<BackstagePrincipalTypes[TAllowed]>> {\n // Limited and full credentials are treated as two separate cases, this lets\n // us avoid internal dependencies between the AuthService and\n // HttpAuthService implementations\n const credentials = options?.allowLimitedAccess\n ? await this.#getLimitedCredentials(req)\n : await this.#getCredentials(req);\n\n const allowed = options?.allow;\n if (!allowed) {\n return credentials as any;\n }\n\n if (this.#auth.isPrincipal(credentials, 'none')) {\n if (allowed.includes('none' as TAllowed)) {\n return credentials as any;\n }\n\n throw new AuthenticationError('Missing credentials');\n } else if (this.#auth.isPrincipal(credentials, 'user')) {\n if (allowed.includes('user' as TAllowed)) {\n return credentials as any;\n }\n\n throw new NotAllowedError(\n `This endpoint does not allow 'user' credentials`,\n );\n } else if (this.#auth.isPrincipal(credentials, 'service')) {\n if (allowed.includes('service' as TAllowed)) {\n return credentials as any;\n }\n\n throw new NotAllowedError(\n `This endpoint does not allow 'service' credentials`,\n );\n }\n\n throw new NotAllowedError(\n 'Unknown principal type, this should never happen',\n );\n }\n\n async issueUserCookie(\n res: Response,\n options?: { credentials?: BackstageCredentials },\n ): Promise<{ expiresAt: Date }> {\n if (res.headersSent) {\n throw new Error('Failed to issue user cookie, headers were already sent');\n }\n\n let credentials: BackstageCredentials<BackstageUserPrincipal>;\n if (options?.credentials) {\n if (this.#auth.isPrincipal(options.credentials, 'none')) {\n res.clearCookie(\n BACKSTAGE_AUTH_COOKIE,\n await this.#getCookieOptions(res.req),\n );\n return { expiresAt: new Date() };\n }\n if (!this.#auth.isPrincipal(options.credentials, 'user')) {\n throw new AuthenticationError(\n 'Refused to issue cookie for non-user principal',\n );\n }\n credentials = options.credentials;\n } else {\n credentials = await this.credentials(res.req, { allow: ['user'] });\n }\n\n const existingExpiresAt = await this.#existingCookieExpiration(res.req);\n if (existingExpiresAt && !willExpireSoon(existingExpiresAt)) {\n return { expiresAt: existingExpiresAt };\n }\n\n const { token, expiresAt } = await this.#auth.getLimitedUserToken(\n credentials,\n );\n if (!token) {\n throw new Error('User credentials is unexpectedly missing token');\n }\n\n res.cookie(BACKSTAGE_AUTH_COOKIE, token, {\n ...(await this.#getCookieOptions(res.req)),\n expires: expiresAt,\n });\n\n return { expiresAt };\n }\n\n async #getCookieOptions(_req: Request): Promise<{\n domain: string;\n httpOnly: true;\n secure: boolean;\n priority: 'high';\n sameSite: 'none' | 'lax';\n }> {\n // TODO: eventually we should read from `${req.protocol}://${req.hostname}`\n // once https://github.com/backstage/backstage/issues/24169 has landed\n const externalBaseUrlStr = await this.#discovery.getExternalBaseUrl(\n this.#pluginId,\n );\n const externalBaseUrl = new URL(externalBaseUrlStr);\n\n const secure =\n externalBaseUrl.protocol === 'https:' ||\n externalBaseUrl.hostname === 'localhost';\n\n return {\n domain: externalBaseUrl.hostname,\n httpOnly: true,\n secure,\n priority: 'high',\n sameSite: secure ? 'none' : 'lax',\n };\n }\n\n async #existingCookieExpiration(req: Request): Promise<Date | undefined> {\n const existingCookie = getCookieFromRequest(req);\n if (!existingCookie) {\n return undefined;\n }\n\n try {\n const existingCredentials = await this.#auth.authenticate(\n existingCookie,\n {\n allowLimitedAccess: true,\n },\n );\n if (!this.#auth.isPrincipal(existingCredentials, 'user')) {\n return undefined;\n }\n\n return existingCredentials.expiresAt;\n } catch (error) {\n if (error.name === 'AuthenticationError') {\n return undefined;\n }\n throw error;\n }\n }\n}\n\n/**\n * Authentication of HTTP requests.\n *\n * See {@link @backstage/code-plugin-api#HttpAuthService}\n * and {@link https://backstage.io/docs/backend-system/core-services/http-auth | the service docs}\n * for more information.\n *\n * @public\n */\nexport const httpAuthServiceFactory = createServiceFactory({\n service: coreServices.httpAuth,\n deps: {\n auth: coreServices.auth,\n discovery: coreServices.discovery,\n plugin: coreServices.pluginMetadata,\n },\n async factory({ auth, discovery, plugin }) {\n return new DefaultHttpAuthService(auth, discovery, plugin.getId());\n },\n});\n"],"names":["parseCookie","AuthenticationError","NotAllowedError","createServiceFactory","coreServices"],"mappings":";;;;;;AA8BA,MAAM,eAAA,GAAkB,IAAI,EAAK,GAAA,GAAA;AAEjC,MAAM,qBAAwB,GAAA,gBAAA;AAE9B,SAAS,oBAAoB,GAAc,EAAA;AAEzC,EAAM,MAAA,UAAA,GAAa,IAAI,OAAQ,CAAA,aAAA;AAC/B,EAAI,IAAA,OAAO,eAAe,QAAU,EAAA;AAClC,IAAM,MAAA,OAAA,GAAU,UAAW,CAAA,KAAA,CAAM,oBAAoB,CAAA;AACrD,IAAM,MAAA,KAAA,GAAQ,UAAU,CAAC,CAAA;AACzB,IAAA,IAAI,KAAO,EAAA;AACT,MAAO,OAAA,KAAA;AAAA;AACT;AAGF,EAAO,OAAA,KAAA,CAAA;AACT;AAEA,SAAS,qBAAqB,GAAc,EAAA;AAC1C,EAAM,MAAA,YAAA,GAAe,IAAI,OAAQ,CAAA,MAAA;AACjC,EAAA,IAAI,YAAc,EAAA;AAChB,IAAM,MAAA,OAAA,GAAUA,aAAY,YAAY,CAAA;AACxC,IAAM,MAAA,KAAA,GAAQ,QAAQ,qBAAqB,CAAA;AAC3C,IAAA,IAAI,KAAO,EAAA;AACT,MAAO,OAAA,KAAA;AAAA;AACT;AAGF,EAAO,OAAA,KAAA,CAAA;AACT;AAEA,SAAS,eAAe,SAAiB,EAAA;AACvC,EAAA,OAAO,IAAK,CAAA,GAAA,EAAQ,GAAA,eAAA,GAAkB,UAAU,OAAQ,EAAA;AAC1D;AAEA,MAAM,iBAAA,GAAoB,OAAO,uBAAuB,CAAA;AACxD,MAAM,wBAAA,GAA2B,OAAO,+BAA+B,CAAA;AAOvE,MAAM,sBAAkD,CAAA;AAAA,EAC7C,KAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EAET,WAAA,CACE,IACA,EAAA,SAAA,EACA,QACA,EAAA;AACA,IAAA,IAAA,CAAK,KAAQ,GAAA,IAAA;AACb,IAAA,IAAA,CAAK,UAAa,GAAA,SAAA;AAClB,IAAA,IAAA,CAAK,SAAY,GAAA,QAAA;AAAA;AACnB,EAEA,MAAM,+BAA+B,GAAc,EAAA;AACjD,IAAM,MAAA,KAAA,GAAQ,oBAAoB,GAAG,CAAA;AACrC,IAAA,IAAI,CAAC,KAAO,EAAA;AACV,MAAO,OAAA,MAAM,IAAK,CAAA,KAAA,CAAM,kBAAmB,EAAA;AAAA;AAG7C,IAAA,OAAO,MAAM,IAAA,CAAK,KAAM,CAAA,YAAA,CAAa,KAAK,CAAA;AAAA;AAC5C,EAEA,MAAM,sCAAsC,GAAc,EAAA;AACxD,IAAM,MAAA,KAAA,GAAQ,oBAAoB,GAAG,CAAA;AACrC,IAAA,IAAI,KAAO,EAAA;AACT,MAAA,OAAO,MAAM,IAAA,CAAK,KAAM,CAAA,YAAA,CAAa,KAAO,EAAA;AAAA,QAC1C,kBAAoB,EAAA;AAAA,OACrB,CAAA;AAAA;AAGH,IAAM,MAAA,MAAA,GAAS,qBAAqB,GAAG,CAAA;AACvC,IAAA,IAAI,MAAQ,EAAA;AACV,MAAA,OAAO,MAAM,IAAA,CAAK,KAAM,CAAA,YAAA,CAAa,MAAQ,EAAA;AAAA,QAC3C,kBAAoB,EAAA;AAAA,OACrB,CAAA;AAAA;AAGH,IAAO,OAAA,MAAM,IAAK,CAAA,KAAA,CAAM,kBAAmB,EAAA;AAAA;AAC7C,EAEA,MAAM,gBAAgB,GAA6B,EAAA;AACjD,IAAA,OAAQ,GAAI,CAAA,iBAAiB,CAC3B,KAAA,IAAA,CAAK,+BAA+B,GAAG,CAAA;AAAA;AAC3C,EAEA,MAAM,uBAAuB,GAA6B,EAAA;AACxD,IAAA,OAAQ,GAAI,CAAA,wBAAwB,CAClC,KAAA,IAAA,CAAK,sCAAsC,GAAG,CAAA;AAAA;AAClD,EAEA,MAAM,WACJ,CAAA,GAAA,EACA,OAIkE,EAAA;AAIlE,IAAM,MAAA,WAAA,GAAc,OAAS,EAAA,kBAAA,GACzB,MAAM,IAAA,CAAK,sBAAuB,CAAA,GAAG,CACrC,GAAA,MAAM,IAAK,CAAA,eAAA,CAAgB,GAAG,CAAA;AAElC,IAAA,MAAM,UAAU,OAAS,EAAA,KAAA;AACzB,IAAA,IAAI,CAAC,OAAS,EAAA;AACZ,MAAO,OAAA,WAAA;AAAA;AAGT,IAAA,IAAI,IAAK,CAAA,KAAA,CAAM,WAAY,CAAA,WAAA,EAAa,MAAM,CAAG,EAAA;AAC/C,MAAI,IAAA,OAAA,CAAQ,QAAS,CAAA,MAAkB,CAAG,EAAA;AACxC,QAAO,OAAA,WAAA;AAAA;AAGT,MAAM,MAAA,IAAIC,2BAAoB,qBAAqB,CAAA;AAAA,eAC1C,IAAK,CAAA,KAAA,CAAM,WAAY,CAAA,WAAA,EAAa,MAAM,CAAG,EAAA;AACtD,MAAI,IAAA,OAAA,CAAQ,QAAS,CAAA,MAAkB,CAAG,EAAA;AACxC,QAAO,OAAA,WAAA;AAAA;AAGT,MAAA,MAAM,IAAIC,sBAAA;AAAA,QACR,CAAA,+CAAA;AAAA,OACF;AAAA,eACS,IAAK,CAAA,KAAA,CAAM,WAAY,CAAA,WAAA,EAAa,SAAS,CAAG,EAAA;AACzD,MAAI,IAAA,OAAA,CAAQ,QAAS,CAAA,SAAqB,CAAG,EAAA;AAC3C,QAAO,OAAA,WAAA;AAAA;AAGT,MAAA,MAAM,IAAIA,sBAAA;AAAA,QACR,CAAA,kDAAA;AAAA,OACF;AAAA;AAGF,IAAA,MAAM,IAAIA,sBAAA;AAAA,MACR;AAAA,KACF;AAAA;AACF,EAEA,MAAM,eACJ,CAAA,GAAA,EACA,OAC8B,EAAA;AAC9B,IAAA,IAAI,IAAI,WAAa,EAAA;AACnB,MAAM,MAAA,IAAI,MAAM,wDAAwD,CAAA;AAAA;AAG1E,IAAI,IAAA,WAAA;AACJ,IAAA,IAAI,SAAS,WAAa,EAAA;AACxB,MAAA,IAAI,KAAK,KAAM,CAAA,WAAA,CAAY,OAAQ,CAAA,WAAA,EAAa,MAAM,CAAG,EAAA;AACvD,QAAI,GAAA,CAAA,WAAA;AAAA,UACF,qBAAA;AAAA,UACA,MAAM,IAAA,CAAK,iBAAkB,CAAA,GAAA,CAAI,GAAG;AAAA,SACtC;AACA,QAAA,OAAO,EAAE,SAAA,kBAAe,IAAA,IAAA,EAAO,EAAA;AAAA;AAEjC,MAAA,IAAI,CAAC,IAAK,CAAA,KAAA,CAAM,YAAY,OAAQ,CAAA,WAAA,EAAa,MAAM,CAAG,EAAA;AACxD,QAAA,MAAM,IAAID,0BAAA;AAAA,UACR;AAAA,SACF;AAAA;AAEF,MAAA,WAAA,GAAc,OAAQ,CAAA,WAAA;AAAA,KACjB,MAAA;AACL,MAAc,WAAA,GAAA,MAAM,IAAK,CAAA,WAAA,CAAY,GAAI,CAAA,GAAA,EAAK,EAAE,KAAO,EAAA,CAAC,MAAM,CAAA,EAAG,CAAA;AAAA;AAGnE,IAAA,MAAM,iBAAoB,GAAA,MAAM,IAAK,CAAA,yBAAA,CAA0B,IAAI,GAAG,CAAA;AACtE,IAAA,IAAI,iBAAqB,IAAA,CAAC,cAAe,CAAA,iBAAiB,CAAG,EAAA;AAC3D,MAAO,OAAA,EAAE,WAAW,iBAAkB,EAAA;AAAA;AAGxC,IAAA,MAAM,EAAE,KAAO,EAAA,SAAA,EAAc,GAAA,MAAM,KAAK,KAAM,CAAA,mBAAA;AAAA,MAC5C;AAAA,KACF;AACA,IAAA,IAAI,CAAC,KAAO,EAAA;AACV,MAAM,MAAA,IAAI,MAAM,gDAAgD,CAAA;AAAA;AAGlE,IAAI,GAAA,CAAA,MAAA,CAAO,uBAAuB,KAAO,EAAA;AAAA,MACvC,GAAI,MAAM,IAAK,CAAA,iBAAA,CAAkB,IAAI,GAAG,CAAA;AAAA,MACxC,OAAS,EAAA;AAAA,KACV,CAAA;AAED,IAAA,OAAO,EAAE,SAAU,EAAA;AAAA;AACrB,EAEA,MAAM,kBAAkB,IAMrB,EAAA;AAGD,IAAM,MAAA,kBAAA,GAAqB,MAAM,IAAA,CAAK,UAAW,CAAA,kBAAA;AAAA,MAC/C,IAAK,CAAA;AAAA,KACP;AACA,IAAM,MAAA,eAAA,GAAkB,IAAI,GAAA,CAAI,kBAAkB,CAAA;AAElD,IAAA,MAAM,MACJ,GAAA,eAAA,CAAgB,QAAa,KAAA,QAAA,IAC7B,gBAAgB,QAAa,KAAA,WAAA;AAE/B,IAAO,OAAA;AAAA,MACL,QAAQ,eAAgB,CAAA,QAAA;AAAA,MACxB,QAAU,EAAA,IAAA;AAAA,MACV,MAAA;AAAA,MACA,QAAU,EAAA,MAAA;AAAA,MACV,QAAA,EAAU,SAAS,MAAS,GAAA;AAAA,KAC9B;AAAA;AACF,EAEA,MAAM,0BAA0B,GAAyC,EAAA;AACvE,IAAM,MAAA,cAAA,GAAiB,qBAAqB,GAAG,CAAA;AAC/C,IAAA,IAAI,CAAC,cAAgB,EAAA;AACnB,MAAO,OAAA,KAAA,CAAA;AAAA;AAGT,IAAI,IAAA;AACF,MAAM,MAAA,mBAAA,GAAsB,MAAM,IAAA,CAAK,KAAM,CAAA,YAAA;AAAA,QAC3C,cAAA;AAAA,QACA;AAAA,UACE,kBAAoB,EAAA;AAAA;AACtB,OACF;AACA,MAAA,IAAI,CAAC,IAAK,CAAA,KAAA,CAAM,WAAY,CAAA,mBAAA,EAAqB,MAAM,CAAG,EAAA;AACxD,QAAO,OAAA,KAAA,CAAA;AAAA;AAGT,MAAA,OAAO,mBAAoB,CAAA,SAAA;AAAA,aACpB,KAAO,EAAA;AACd,MAAI,IAAA,KAAA,CAAM,SAAS,qBAAuB,EAAA;AACxC,QAAO,OAAA,KAAA,CAAA;AAAA;AAET,MAAM,MAAA,KAAA;AAAA;AACR;AAEJ;AAWO,MAAM,yBAAyBE,qCAAqB,CAAA;AAAA,EACzD,SAASC,6BAAa,CAAA,QAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,MAAMA,6BAAa,CAAA,IAAA;AAAA,IACnB,WAAWA,6BAAa,CAAA,SAAA;AAAA,IACxB,QAAQA,6BAAa,CAAA;AAAA,GACvB;AAAA,EACA,MAAM,OAAQ,CAAA,EAAE,IAAM,EAAA,SAAA,EAAW,QAAU,EAAA;AACzC,IAAA,OAAO,IAAI,sBAAuB,CAAA,IAAA,EAAM,SAAW,EAAA,MAAA,CAAO,OAAO,CAAA;AAAA;AAErE,CAAC;;;;"}
1
+ {"version":3,"file":"httpAuthServiceFactory.cjs.js","sources":["../../../src/entrypoints/httpAuth/httpAuthServiceFactory.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 {\n AuthService,\n BackstageCredentials,\n BackstagePrincipalTypes,\n BackstageUserPrincipal,\n DiscoveryService,\n HttpAuthService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError, NotAllowedError } from '@backstage/errors';\nimport { parse as parseCookie } from 'cookie';\nimport { Request, Response } from 'express';\n\nconst FIVE_MINUTES_MS = 5 * 60 * 1000;\n\nconst BACKSTAGE_AUTH_COOKIE = 'backstage-auth';\n\nfunction getTokenFromRequest(req: Request) {\n let token: string | undefined;\n const authHeader = req.headers.authorization;\n if (typeof authHeader === 'string') {\n const matches = authHeader.match(/^Bearer[ ]+(\\S+)$/i);\n token = matches?.[1];\n }\n\n return { token };\n}\n\nfunction getCookieFromRequest(req: Request) {\n const cookieHeader = req.headers.cookie;\n if (cookieHeader) {\n const cookies = parseCookie(cookieHeader);\n const token = cookies[BACKSTAGE_AUTH_COOKIE];\n if (token) {\n return token;\n }\n }\n\n return undefined;\n}\n\nfunction willExpireSoon(expiresAt: Date) {\n return Date.now() + FIVE_MINUTES_MS > expiresAt.getTime();\n}\n\nconst credentialsSymbol = Symbol('backstage-credentials');\nconst limitedCredentialsSymbol = Symbol('backstage-limited-credentials');\n\ntype RequestWithCredentials = Request & {\n [credentialsSymbol]?: Promise<BackstageCredentials>;\n [limitedCredentialsSymbol]?: Promise<BackstageCredentials>;\n};\n\n/**\n * @public\n * Options for creating a DefaultHttpAuthService.\n */\nexport interface DefaultHttpAuthServiceOptions {\n auth: AuthService;\n discovery: DiscoveryService;\n pluginId: string;\n /**\n * Optionally override logic for extracting the token from the request.\n */\n getTokenFromRequest?: (req: Request) => { token?: string };\n}\n\n/**\n * @public\n * DefaultHttpAuthService is the default implementation of the HttpAuthService\n */\nexport class DefaultHttpAuthService implements HttpAuthService {\n readonly #auth: AuthService;\n readonly #discovery: DiscoveryService;\n readonly #pluginId: string;\n readonly #getToken: (req: Request) => { token?: string };\n\n private constructor(\n auth: AuthService,\n discovery: DiscoveryService,\n pluginId: string,\n getToken?: (req: Request) => { token?: string },\n ) {\n this.#auth = auth;\n this.#discovery = discovery;\n this.#pluginId = pluginId;\n this.#getToken = getToken ?? getTokenFromRequest;\n }\n\n static create(\n options: DefaultHttpAuthServiceOptions,\n ): DefaultHttpAuthService {\n return new DefaultHttpAuthService(\n options.auth,\n options.discovery,\n options.pluginId,\n options.getTokenFromRequest,\n );\n }\n\n async #extractCredentialsFromRequest(req: Request) {\n const { token } = this.#getToken(req);\n if (!token) {\n return await this.#auth.getNoneCredentials();\n }\n\n return await this.#auth.authenticate(token);\n }\n\n async #extractLimitedCredentialsFromRequest(req: Request) {\n const { token } = this.#getToken(req);\n if (token) {\n return await this.#auth.authenticate(token, {\n allowLimitedAccess: true,\n });\n }\n\n const cookie = getCookieFromRequest(req);\n if (cookie) {\n return await this.#auth.authenticate(cookie, {\n allowLimitedAccess: true,\n });\n }\n\n return await this.#auth.getNoneCredentials();\n }\n\n async #getCredentials(req: RequestWithCredentials) {\n return (req[credentialsSymbol] ??=\n this.#extractCredentialsFromRequest(req));\n }\n\n async #getLimitedCredentials(req: RequestWithCredentials) {\n return (req[limitedCredentialsSymbol] ??=\n this.#extractLimitedCredentialsFromRequest(req));\n }\n\n async credentials<TAllowed extends keyof BackstagePrincipalTypes = 'unknown'>(\n req: Request,\n options?: {\n allow?: Array<TAllowed>;\n allowLimitedAccess?: boolean;\n },\n ): Promise<BackstageCredentials<BackstagePrincipalTypes[TAllowed]>> {\n // Limited and full credentials are treated as two separate cases, this lets\n // us avoid internal dependencies between the AuthService and\n // HttpAuthService implementations\n const credentials = options?.allowLimitedAccess\n ? await this.#getLimitedCredentials(req)\n : await this.#getCredentials(req);\n\n const allowed = options?.allow;\n if (!allowed) {\n return credentials as any;\n }\n\n if (this.#auth.isPrincipal(credentials, 'none')) {\n if (allowed.includes('none' as TAllowed)) {\n return credentials as any;\n }\n\n throw new AuthenticationError('Missing credentials');\n } else if (this.#auth.isPrincipal(credentials, 'user')) {\n if (allowed.includes('user' as TAllowed)) {\n return credentials as any;\n }\n\n throw new NotAllowedError(\n `This endpoint does not allow 'user' credentials`,\n );\n } else if (this.#auth.isPrincipal(credentials, 'service')) {\n if (allowed.includes('service' as TAllowed)) {\n return credentials as any;\n }\n\n throw new NotAllowedError(\n `This endpoint does not allow 'service' credentials`,\n );\n }\n\n throw new NotAllowedError(\n 'Unknown principal type, this should never happen',\n );\n }\n\n async issueUserCookie(\n res: Response,\n options?: { credentials?: BackstageCredentials },\n ): Promise<{ expiresAt: Date }> {\n if (res.headersSent) {\n throw new Error('Failed to issue user cookie, headers were already sent');\n }\n\n let credentials: BackstageCredentials<BackstageUserPrincipal>;\n if (options?.credentials) {\n if (this.#auth.isPrincipal(options.credentials, 'none')) {\n res.clearCookie(\n BACKSTAGE_AUTH_COOKIE,\n await this.#getCookieOptions(res.req),\n );\n return { expiresAt: new Date() };\n }\n if (!this.#auth.isPrincipal(options.credentials, 'user')) {\n throw new AuthenticationError(\n 'Refused to issue cookie for non-user principal',\n );\n }\n credentials = options.credentials;\n } else {\n credentials = await this.credentials(res.req, { allow: ['user'] });\n }\n\n const existingExpiresAt = await this.#existingCookieExpiration(res.req);\n if (existingExpiresAt && !willExpireSoon(existingExpiresAt)) {\n return { expiresAt: existingExpiresAt };\n }\n\n const { token, expiresAt } = await this.#auth.getLimitedUserToken(\n credentials,\n );\n if (!token) {\n throw new Error('User credentials is unexpectedly missing token');\n }\n\n res.cookie(BACKSTAGE_AUTH_COOKIE, token, {\n ...(await this.#getCookieOptions(res.req)),\n expires: expiresAt,\n });\n\n return { expiresAt };\n }\n\n async #getCookieOptions(_req: Request): Promise<{\n domain: string;\n httpOnly: true;\n secure: boolean;\n priority: 'high';\n sameSite: 'none' | 'lax';\n }> {\n // TODO: eventually we should read from `${req.protocol}://${req.hostname}`\n // once https://github.com/backstage/backstage/issues/24169 has landed\n const externalBaseUrlStr = await this.#discovery.getExternalBaseUrl(\n this.#pluginId,\n );\n const externalBaseUrl = new URL(externalBaseUrlStr);\n\n const secure =\n externalBaseUrl.protocol === 'https:' ||\n externalBaseUrl.hostname === 'localhost';\n\n return {\n domain: externalBaseUrl.hostname,\n httpOnly: true,\n secure,\n priority: 'high',\n sameSite: secure ? 'none' : 'lax',\n };\n }\n\n async #existingCookieExpiration(req: Request): Promise<Date | undefined> {\n const existingCookie = getCookieFromRequest(req);\n if (!existingCookie) {\n return undefined;\n }\n\n try {\n const existingCredentials = await this.#auth.authenticate(\n existingCookie,\n {\n allowLimitedAccess: true,\n },\n );\n if (!this.#auth.isPrincipal(existingCredentials, 'user')) {\n return undefined;\n }\n\n return existingCredentials.expiresAt;\n } catch (error) {\n if (error.name === 'AuthenticationError') {\n return undefined;\n }\n throw error;\n }\n }\n}\n\n/**\n * Authentication of HTTP requests.\n *\n * See {@link @backstage/code-plugin-api#HttpAuthService}\n * and {@link https://backstage.io/docs/backend-system/core-services/http-auth | the service docs}\n * for more information.\n *\n * @public\n */\nexport const httpAuthServiceFactory = createServiceFactory({\n service: coreServices.httpAuth,\n deps: {\n auth: coreServices.auth,\n discovery: coreServices.discovery,\n plugin: coreServices.pluginMetadata,\n },\n async factory({ auth, discovery, plugin }) {\n return DefaultHttpAuthService.create({\n auth,\n discovery,\n pluginId: plugin.getId(),\n });\n },\n});\n"],"names":["parseCookie","AuthenticationError","NotAllowedError","createServiceFactory","coreServices"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createAuthIntegrationRouter.cjs.js","sources":["../../../../src/entrypoints/httpRouter/http/createAuthIntegrationRouter.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 } from '@backstage/backend-plugin-api';\nimport express from 'express';\nimport Router from 'express-promise-router';\n\n/**\n * @public\n */\nexport function createAuthIntegrationRouter(options: {\n auth: AuthService;\n}): express.Router {\n const router = Router();\n\n router.get('/.backstage/auth/v1/jwks.json', async (_req, res) => {\n const { keys } = await options.auth.listPublicServiceKeys();\n\n res.json({ keys });\n });\n\n return router;\n}\n"],"names":["Router"],"mappings":";;;;;;;;AAuBO,SAAS,4BAA4B,OAEzB,EAAA;AACjB,EAAA,MAAM,SAASA,uBAAO,EAAA;AAEtB,EAAA,MAAA,CAAO,GAAI,CAAA,+BAAA,EAAiC,OAAO,IAAA,EAAM,GAAQ,KAAA;AAC/D,IAAA,MAAM,EAAE,IAAK,EAAA,GAAI,MAAM,OAAA,CAAQ,KAAK,qBAAsB,EAAA;AAE1D,IAAI,GAAA,CAAA,IAAA,CAAK,EAAE,IAAA,EAAM,CAAA;AAAA,GAClB,CAAA;AAED,EAAO,OAAA,MAAA;AACT;;;;"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createCookieAuthRefreshMiddleware.cjs.js","sources":["../../../../src/entrypoints/httpRouter/http/createCookieAuthRefreshMiddleware.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, HttpAuthService } from '@backstage/backend-plugin-api';\nimport Router from 'express-promise-router';\n\nconst WELL_KNOWN_COOKIE_PATH_V1 = '/.backstage/auth/v1/cookie';\n\n/**\n * @public\n * Creates a middleware that can be used to refresh the cookie for the user.\n */\nexport function createCookieAuthRefreshMiddleware(options: {\n auth: AuthService;\n httpAuth: HttpAuthService;\n}) {\n const { auth, httpAuth } = options;\n const router = Router();\n\n // Endpoint that sets the cookie for the user\n router.get(WELL_KNOWN_COOKIE_PATH_V1, async (_, res) => {\n const { expiresAt } = await httpAuth.issueUserCookie(res);\n res.json({ expiresAt: expiresAt.toISOString() });\n });\n\n // Endpoint that removes the cookie for the user\n router.delete(WELL_KNOWN_COOKIE_PATH_V1, async (_, res) => {\n const credentials = await auth.getNoneCredentials();\n await httpAuth.issueUserCookie(res, { credentials });\n res.status(204).end();\n });\n\n return router;\n}\n"],"names":["Router"],"mappings":";;;;;;;;AAmBA,MAAM,yBAA4B,GAAA,4BAAA;AAM3B,SAAS,kCAAkC,OAG/C,EAAA;AACD,EAAM,MAAA,EAAE,IAAM,EAAA,QAAA,EAAa,GAAA,OAAA;AAC3B,EAAA,MAAM,SAASA,uBAAO,EAAA;AAGtB,EAAA,MAAA,CAAO,GAAI,CAAA,yBAAA,EAA2B,OAAO,CAAA,EAAG,GAAQ,KAAA;AACtD,IAAA,MAAM,EAAE,SAAU,EAAA,GAAI,MAAM,QAAA,CAAS,gBAAgB,GAAG,CAAA;AACxD,IAAA,GAAA,CAAI,KAAK,EAAE,SAAA,EAAW,SAAU,CAAA,WAAA,IAAe,CAAA;AAAA,GAChD,CAAA;AAGD,EAAA,MAAA,CAAO,MAAO,CAAA,yBAAA,EAA2B,OAAO,CAAA,EAAG,GAAQ,KAAA;AACzD,IAAM,MAAA,WAAA,GAAc,MAAM,IAAA,CAAK,kBAAmB,EAAA;AAClD,IAAA,MAAM,QAAS,CAAA,eAAA,CAAgB,GAAK,EAAA,EAAE,aAAa,CAAA;AACnD,IAAI,GAAA,CAAA,MAAA,CAAO,GAAG,CAAA,CAAE,GAAI,EAAA;AAAA,GACrB,CAAA;AAED,EAAO,OAAA,MAAA;AACT;;;;"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createCredentialsBarrier.cjs.js","sources":["../../../../src/entrypoints/httpRouter/http/createCredentialsBarrier.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 {\n HttpAuthService,\n HttpRouterServiceAuthPolicy,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { RequestHandler } from 'express';\nimport { pathToRegexp } from 'path-to-regexp';\n\nexport function createPathPolicyPredicate(policyPath: string) {\n if (policyPath === '/' || policyPath === '*') {\n return () => true;\n }\n\n const { regexp: pathRegex } = pathToRegexp(policyPath, {\n end: false,\n });\n\n return (path: string): boolean => {\n return pathRegex.test(path);\n };\n}\n\n/**\n * @public\n */\nexport function createCredentialsBarrier(options: {\n httpAuth: HttpAuthService;\n config: RootConfigService;\n}): {\n middleware: RequestHandler;\n addAuthPolicy: (policy: HttpRouterServiceAuthPolicy) => void;\n} {\n const { httpAuth, config } = options;\n\n const disableDefaultAuthPolicy = config.getOptionalBoolean(\n 'backend.auth.dangerouslyDisableDefaultAuthPolicy',\n );\n\n if (disableDefaultAuthPolicy) {\n return {\n middleware: (_req, _res, next) => next(),\n addAuthPolicy: () => {},\n };\n }\n\n const unauthenticatedPredicates = new Array<(path: string) => boolean>();\n const cookiePredicates = new Array<(path: string) => boolean>();\n\n const middleware: RequestHandler = (req, _, next) => {\n const allowsUnauthenticated = unauthenticatedPredicates.some(predicate =>\n predicate(req.path),\n );\n\n if (allowsUnauthenticated) {\n next();\n return;\n }\n\n const allowsCookie = cookiePredicates.some(predicate =>\n predicate(req.path),\n );\n\n httpAuth\n .credentials(req, {\n allow: ['user', 'service'],\n allowLimitedAccess: allowsCookie,\n })\n .then(\n () => next(),\n err => next(err),\n );\n };\n\n const addAuthPolicy = (policy: HttpRouterServiceAuthPolicy) => {\n if (policy.allow === 'unauthenticated') {\n unauthenticatedPredicates.push(createPathPolicyPredicate(policy.path));\n } else if (policy.allow === 'user-cookie') {\n cookiePredicates.push(createPathPolicyPredicate(policy.path));\n } else {\n throw new Error('Invalid auth policy');\n }\n };\n\n return { middleware, addAuthPolicy };\n}\n"],"names":["pathToRegexp"],"mappings":";;;;AAwBO,SAAS,0BAA0B,UAAoB,EAAA;AAC5D,EAAI,IAAA,UAAA,KAAe,GAAO,IAAA,UAAA,KAAe,GAAK,EAAA;AAC5C,IAAA,OAAO,MAAM,IAAA;AAAA;AAGf,EAAA,MAAM,EAAE,MAAA,EAAQ,SAAU,EAAA,GAAIA,0BAAa,UAAY,EAAA;AAAA,IACrD,GAAK,EAAA;AAAA,GACN,CAAA;AAED,EAAA,OAAO,CAAC,IAA0B,KAAA;AAChC,IAAO,OAAA,SAAA,CAAU,KAAK,IAAI,CAAA;AAAA,GAC5B;AACF;AAKO,SAAS,yBAAyB,OAMvC,EAAA;AACA,EAAM,MAAA,EAAE,QAAU,EAAA,MAAA,EAAW,GAAA,OAAA;AAE7B,EAAA,MAAM,2BAA2B,MAAO,CAAA,kBAAA;AAAA,IACtC;AAAA,GACF;AAEA,EAAA,IAAI,wBAA0B,EAAA;AAC5B,IAAO,OAAA;AAAA,MACL,UAAY,EAAA,CAAC,IAAM,EAAA,IAAA,EAAM,SAAS,IAAK,EAAA;AAAA,MACvC,eAAe,MAAM;AAAA;AAAC,KACxB;AAAA;AAGF,EAAM,MAAA,yBAAA,GAA4B,IAAI,KAAiC,EAAA;AACvE,EAAM,MAAA,gBAAA,GAAmB,IAAI,KAAiC,EAAA;AAE9D,EAAA,MAAM,UAA6B,GAAA,CAAC,GAAK,EAAA,CAAA,EAAG,IAAS,KAAA;AACnD,IAAA,MAAM,wBAAwB,yBAA0B,CAAA,IAAA;AAAA,MAAK,CAAA,SAAA,KAC3D,SAAU,CAAA,GAAA,CAAI,IAAI;AAAA,KACpB;AAEA,IAAA,IAAI,qBAAuB,EAAA;AACzB,MAAK,IAAA,EAAA;AACL,MAAA;AAAA;AAGF,IAAA,MAAM,eAAe,gBAAiB,CAAA,IAAA;AAAA,MAAK,CAAA,SAAA,KACzC,SAAU,CAAA,GAAA,CAAI,IAAI;AAAA,KACpB;AAEA,IAAA,QAAA,CACG,YAAY,GAAK,EAAA;AAAA,MAChB,KAAA,EAAO,CAAC,MAAA,EAAQ,SAAS,CAAA;AAAA,MACzB,kBAAoB,EAAA;AAAA,KACrB,CACA,CAAA,IAAA;AAAA,MACC,MAAM,IAAK,EAAA;AAAA,MACX,CAAA,GAAA,KAAO,KAAK,GAAG;AAAA,KACjB;AAAA,GACJ;AAEA,EAAM,MAAA,aAAA,GAAgB,CAAC,MAAwC,KAAA;AAC7D,IAAI,IAAA,MAAA,CAAO,UAAU,iBAAmB,EAAA;AACtC,MAAA,yBAAA,CAA0B,IAAK,CAAA,yBAAA,CAA0B,MAAO,CAAA,IAAI,CAAC,CAAA;AAAA,KACvE,MAAA,IAAW,MAAO,CAAA,KAAA,KAAU,aAAe,EAAA;AACzC,MAAA,gBAAA,CAAiB,IAAK,CAAA,yBAAA,CAA0B,MAAO,CAAA,IAAI,CAAC,CAAA;AAAA,KACvD,MAAA;AACL,MAAM,MAAA,IAAI,MAAM,qBAAqB,CAAA;AAAA;AACvC,GACF;AAEA,EAAO,OAAA,EAAE,YAAY,aAAc,EAAA;AACrC;;;;;"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createLifecycleMiddleware.cjs.js","sources":["../../../../src/entrypoints/httpRouter/http/createLifecycleMiddleware.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 { LifecycleService } from '@backstage/backend-plugin-api';\nimport { ServiceUnavailableError } from '@backstage/errors';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { RequestHandler } from 'express';\n\nexport const DEFAULT_TIMEOUT = { seconds: 5 };\n\n/**\n * Options for {@link createLifecycleMiddleware}.\n * @public\n */\nexport interface LifecycleMiddlewareOptions {\n lifecycle: LifecycleService;\n /**\n * The maximum time that paused requests will wait for the service to start, before returning an error.\n *\n * Defaults to 5 seconds.\n */\n startupRequestPauseTimeout?: HumanDuration;\n}\n\n/**\n * Creates a middleware that pauses requests until the service has started.\n *\n * @remarks\n *\n * Requests that arrive before the service has started will be paused until startup is complete.\n * If the service does not start within the provided timeout, the request will be rejected with a\n * {@link @backstage/errors#ServiceUnavailableError}.\n *\n * If the service is shutting down, all requests will be rejected with a\n * {@link @backstage/errors#ServiceUnavailableError}.\n *\n * @public\n */\nexport function createLifecycleMiddleware(\n options: LifecycleMiddlewareOptions,\n): RequestHandler {\n const { lifecycle, startupRequestPauseTimeout = DEFAULT_TIMEOUT } = options;\n\n let state: 'init' | 'up' | 'down' = 'init';\n const waiting = new Set<{\n next: (err?: Error) => void;\n timeout: NodeJS.Timeout;\n }>();\n\n lifecycle.addStartupHook(async () => {\n if (state === 'init') {\n state = 'up';\n for (const item of waiting) {\n clearTimeout(item.timeout);\n item.next();\n }\n waiting.clear();\n }\n });\n\n lifecycle.addShutdownHook(async () => {\n state = 'down';\n\n for (const item of waiting) {\n clearTimeout(item.timeout);\n item.next(new ServiceUnavailableError('Service is shutting down'));\n }\n waiting.clear();\n });\n\n const timeoutMs = durationToMilliseconds(startupRequestPauseTimeout);\n\n return (_req, _res, next) => {\n if (state === 'up') {\n next();\n return;\n } else if (state === 'down') {\n next(new ServiceUnavailableError('Service is shutting down'));\n return;\n }\n\n const item = {\n next,\n timeout: setTimeout(() => {\n if (waiting.delete(item)) {\n next(new ServiceUnavailableError('Service has not started up yet'));\n }\n }, timeoutMs),\n };\n\n waiting.add(item);\n };\n}\n"],"names":["ServiceUnavailableError","durationToMilliseconds"],"mappings":";;;;;AAqBa,MAAA,eAAA,GAAkB,EAAE,OAAA,EAAS,CAAE;AA8BrC,SAAS,0BACd,OACgB,EAAA;AAChB,EAAA,MAAM,EAAE,SAAA,EAAW,0BAA6B,GAAA,eAAA,EAAoB,GAAA,OAAA;AAEpE,EAAA,IAAI,KAAgC,GAAA,MAAA;AACpC,EAAM,MAAA,OAAA,uBAAc,GAGjB,EAAA;AAEH,EAAA,SAAA,CAAU,eAAe,YAAY;AACnC,IAAA,IAAI,UAAU,MAAQ,EAAA;AACpB,MAAQ,KAAA,GAAA,IAAA;AACR,MAAA,KAAA,MAAW,QAAQ,OAAS,EAAA;AAC1B,QAAA,YAAA,CAAa,KAAK,OAAO,CAAA;AACzB,QAAA,IAAA,CAAK,IAAK,EAAA;AAAA;AAEZ,MAAA,OAAA,CAAQ,KAAM,EAAA;AAAA;AAChB,GACD,CAAA;AAED,EAAA,SAAA,CAAU,gBAAgB,YAAY;AACpC,IAAQ,KAAA,GAAA,MAAA;AAER,IAAA,KAAA,MAAW,QAAQ,OAAS,EAAA;AAC1B,MAAA,YAAA,CAAa,KAAK,OAAO,CAAA;AACzB,MAAA,IAAA,CAAK,IAAK,CAAA,IAAIA,8BAAwB,CAAA,0BAA0B,CAAC,CAAA;AAAA;AAEnE,IAAA,OAAA,CAAQ,KAAM,EAAA;AAAA,GACf,CAAA;AAED,EAAM,MAAA,SAAA,GAAYC,6BAAuB,0BAA0B,CAAA;AAEnE,EAAO,OAAA,CAAC,IAAM,EAAA,IAAA,EAAM,IAAS,KAAA;AAC3B,IAAA,IAAI,UAAU,IAAM,EAAA;AAClB,MAAK,IAAA,EAAA;AACL,MAAA;AAAA,KACF,MAAA,IAAW,UAAU,MAAQ,EAAA;AAC3B,MAAK,IAAA,CAAA,IAAID,8BAAwB,CAAA,0BAA0B,CAAC,CAAA;AAC5D,MAAA;AAAA;AAGF,IAAA,MAAM,IAAO,GAAA;AAAA,MACX,IAAA;AAAA,MACA,OAAA,EAAS,WAAW,MAAM;AACxB,QAAI,IAAA,OAAA,CAAQ,MAAO,CAAA,IAAI,CAAG,EAAA;AACxB,UAAK,IAAA,CAAA,IAAIA,8BAAwB,CAAA,gCAAgC,CAAC,CAAA;AAAA;AACpE,SACC,SAAS;AAAA,KACd;AAEA,IAAA,OAAA,CAAQ,IAAI,IAAI,CAAA;AAAA,GAClB;AACF;;;;;"}
@@ -2,10 +2,10 @@
2
2
 
3
3
  var Router = require('express-promise-router');
4
4
  var backendPluginApi = require('@backstage/backend-plugin-api');
5
- var createLifecycleMiddleware = require('./createLifecycleMiddleware.cjs.js');
6
- var createCredentialsBarrier = require('./createCredentialsBarrier.cjs.js');
7
- var createAuthIntegrationRouter = require('./createAuthIntegrationRouter.cjs.js');
8
- var createCookieAuthRefreshMiddleware = require('./createCookieAuthRefreshMiddleware.cjs.js');
5
+ var createAuthIntegrationRouter = require('./http/createAuthIntegrationRouter.cjs.js');
6
+ var createCredentialsBarrier = require('./http/createCredentialsBarrier.cjs.js');
7
+ var createLifecycleMiddleware = require('./http/createLifecycleMiddleware.cjs.js');
8
+ var createCookieAuthRefreshMiddleware = require('./http/createCookieAuthRefreshMiddleware.cjs.js');
9
9
 
10
10
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
11
11
 
@@ -1 +1 @@
1
- {"version":3,"file":"httpRouterServiceFactory.cjs.js","sources":["../../../src/entrypoints/httpRouter/httpRouterServiceFactory.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 { Handler } from 'express';\nimport PromiseRouter from 'express-promise-router';\nimport {\n coreServices,\n createServiceFactory,\n HttpRouterServiceAuthPolicy,\n} from '@backstage/backend-plugin-api';\nimport { createLifecycleMiddleware } from './createLifecycleMiddleware';\nimport { createCredentialsBarrier } from './createCredentialsBarrier';\nimport { createAuthIntegrationRouter } from './createAuthIntegrationRouter';\nimport { createCookieAuthRefreshMiddleware } from './createCookieAuthRefreshMiddleware';\n\n/**\n * HTTP route registration for plugins.\n *\n * See {@link @backstage/code-plugin-api#HttpRouterService}\n * and {@link https://backstage.io/docs/backend-system/core-services/http-router | the service docs}\n * for more information.\n *\n * @public\n */\nexport const httpRouterServiceFactory = createServiceFactory({\n service: coreServices.httpRouter,\n initialization: 'always',\n deps: {\n plugin: coreServices.pluginMetadata,\n config: coreServices.rootConfig,\n lifecycle: coreServices.lifecycle,\n rootHttpRouter: coreServices.rootHttpRouter,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n },\n async factory({ auth, httpAuth, config, plugin, rootHttpRouter, lifecycle }) {\n const router = PromiseRouter();\n\n rootHttpRouter.use(`/api/${plugin.getId()}`, router);\n\n const credentialsBarrier = createCredentialsBarrier({\n httpAuth,\n config,\n });\n\n router.use(createAuthIntegrationRouter({ auth }));\n router.use(createLifecycleMiddleware({ lifecycle }));\n router.use(credentialsBarrier.middleware);\n router.use(createCookieAuthRefreshMiddleware({ auth, httpAuth }));\n\n return {\n use(handler: Handler): void {\n router.use(handler);\n },\n addAuthPolicy(policy: HttpRouterServiceAuthPolicy): void {\n credentialsBarrier.addAuthPolicy(policy);\n },\n };\n },\n});\n"],"names":["createServiceFactory","coreServices","PromiseRouter","createCredentialsBarrier","createAuthIntegrationRouter","createLifecycleMiddleware","createCookieAuthRefreshMiddleware"],"mappings":";;;;;;;;;;;;;AAqCO,MAAM,2BAA2BA,qCAAqB,CAAA;AAAA,EAC3D,SAASC,6BAAa,CAAA,UAAA;AAAA,EACtB,cAAgB,EAAA,QAAA;AAAA,EAChB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,cAAA;AAAA,IACrB,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,IACxB,gBAAgBA,6BAAa,CAAA,cAAA;AAAA,IAC7B,MAAMA,6BAAa,CAAA,IAAA;AAAA,IACnB,UAAUA,6BAAa,CAAA;AAAA,GACzB;AAAA,EACA,MAAM,QAAQ,EAAE,IAAA,EAAM,UAAU,MAAQ,EAAA,MAAA,EAAQ,cAAgB,EAAA,SAAA,EAAa,EAAA;AAC3E,IAAA,MAAM,SAASC,uBAAc,EAAA;AAE7B,IAAA,cAAA,CAAe,IAAI,CAAQ,KAAA,EAAA,MAAA,CAAO,KAAM,EAAC,IAAI,MAAM,CAAA;AAEnD,IAAA,MAAM,qBAAqBC,iDAAyB,CAAA;AAAA,MAClD,QAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAA,CAAO,GAAI,CAAAC,uDAAA,CAA4B,EAAE,IAAA,EAAM,CAAC,CAAA;AAChD,IAAA,MAAA,CAAO,GAAI,CAAAC,mDAAA,CAA0B,EAAE,SAAA,EAAW,CAAC,CAAA;AACnD,IAAO,MAAA,CAAA,GAAA,CAAI,mBAAmB,UAAU,CAAA;AACxC,IAAA,MAAA,CAAO,IAAIC,mEAAkC,CAAA,EAAE,IAAM,EAAA,QAAA,EAAU,CAAC,CAAA;AAEhE,IAAO,OAAA;AAAA,MACL,IAAI,OAAwB,EAAA;AAC1B,QAAA,MAAA,CAAO,IAAI,OAAO,CAAA;AAAA,OACpB;AAAA,MACA,cAAc,MAA2C,EAAA;AACvD,QAAA,kBAAA,CAAmB,cAAc,MAAM,CAAA;AAAA;AACzC,KACF;AAAA;AAEJ,CAAC;;;;"}
1
+ {"version":3,"file":"httpRouterServiceFactory.cjs.js","sources":["../../../src/entrypoints/httpRouter/httpRouterServiceFactory.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 { Handler } from 'express';\nimport PromiseRouter from 'express-promise-router';\nimport {\n coreServices,\n createServiceFactory,\n HttpRouterServiceAuthPolicy,\n} from '@backstage/backend-plugin-api';\nimport {\n createLifecycleMiddleware,\n createCookieAuthRefreshMiddleware,\n createCredentialsBarrier,\n createAuthIntegrationRouter,\n} from './http';\n\n/**\n * HTTP route registration for plugins.\n *\n * See {@link @backstage/code-plugin-api#HttpRouterService}\n * and {@link https://backstage.io/docs/backend-system/core-services/http-router | the service docs}\n * for more information.\n *\n * @public\n */\nexport const httpRouterServiceFactory = createServiceFactory({\n service: coreServices.httpRouter,\n initialization: 'always',\n deps: {\n plugin: coreServices.pluginMetadata,\n config: coreServices.rootConfig,\n lifecycle: coreServices.lifecycle,\n rootHttpRouter: coreServices.rootHttpRouter,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n },\n async factory({ auth, httpAuth, config, plugin, rootHttpRouter, lifecycle }) {\n const router = PromiseRouter();\n\n rootHttpRouter.use(`/api/${plugin.getId()}`, router);\n\n const credentialsBarrier = createCredentialsBarrier({\n httpAuth,\n config,\n });\n\n router.use(createAuthIntegrationRouter({ auth }));\n router.use(createLifecycleMiddleware({ lifecycle }));\n router.use(credentialsBarrier.middleware);\n router.use(createCookieAuthRefreshMiddleware({ auth, httpAuth }));\n\n return {\n use(handler: Handler): void {\n router.use(handler);\n },\n addAuthPolicy(policy: HttpRouterServiceAuthPolicy): void {\n credentialsBarrier.addAuthPolicy(policy);\n },\n };\n },\n});\n"],"names":["createServiceFactory","coreServices","PromiseRouter","createCredentialsBarrier","createAuthIntegrationRouter","createLifecycleMiddleware","createCookieAuthRefreshMiddleware"],"mappings":";;;;;;;;;;;;;AAuCO,MAAM,2BAA2BA,qCAAqB,CAAA;AAAA,EAC3D,SAASC,6BAAa,CAAA,UAAA;AAAA,EACtB,cAAgB,EAAA,QAAA;AAAA,EAChB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,cAAA;AAAA,IACrB,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,IACxB,gBAAgBA,6BAAa,CAAA,cAAA;AAAA,IAC7B,MAAMA,6BAAa,CAAA,IAAA;AAAA,IACnB,UAAUA,6BAAa,CAAA;AAAA,GACzB;AAAA,EACA,MAAM,QAAQ,EAAE,IAAA,EAAM,UAAU,MAAQ,EAAA,MAAA,EAAQ,cAAgB,EAAA,SAAA,EAAa,EAAA;AAC3E,IAAA,MAAM,SAASC,uBAAc,EAAA;AAE7B,IAAA,cAAA,CAAe,IAAI,CAAQ,KAAA,EAAA,MAAA,CAAO,KAAM,EAAC,IAAI,MAAM,CAAA;AAEnD,IAAA,MAAM,qBAAqBC,iDAAyB,CAAA;AAAA,MAClD,QAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAA,CAAO,GAAI,CAAAC,uDAAA,CAA4B,EAAE,IAAA,EAAM,CAAC,CAAA;AAChD,IAAA,MAAA,CAAO,GAAI,CAAAC,mDAAA,CAA0B,EAAE,SAAA,EAAW,CAAC,CAAA;AACnD,IAAO,MAAA,CAAA,GAAA,CAAI,mBAAmB,UAAU,CAAA;AACxC,IAAA,MAAA,CAAO,IAAIC,mEAAkC,CAAA,EAAE,IAAM,EAAA,QAAA,EAAU,CAAC,CAAA;AAEhE,IAAO,OAAA;AAAA,MACL,IAAI,OAAwB,EAAA;AAC1B,QAAA,MAAA,CAAO,IAAI,OAAO,CAAA;AAAA,OACpB;AAAA,MACA,cAAc,MAA2C,EAAA;AACvD,QAAA,kBAAA,CAAmB,cAAc,MAAM,CAAA;AAAA;AACzC,KACF;AAAA;AAEJ,CAAC;;;;"}
@@ -2,7 +2,6 @@
2
2
 
3
3
  var cors = require('cors');
4
4
  var helmet = require('helmet');
5
- var morgan = require('morgan');
6
5
  var compression = require('compression');
7
6
  var readHelmetOptions = require('./readHelmetOptions.cjs.js');
8
7
  var readCorsOptions = require('./readCorsOptions.cjs.js');
@@ -13,9 +12,30 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
13
12
 
14
13
  var cors__default = /*#__PURE__*/_interopDefaultCompat(cors);
15
14
  var helmet__default = /*#__PURE__*/_interopDefaultCompat(helmet);
16
- var morgan__default = /*#__PURE__*/_interopDefaultCompat(morgan);
17
15
  var compression__default = /*#__PURE__*/_interopDefaultCompat(compression);
18
16
 
17
+ function getLogMeta(req, res) {
18
+ const referrer = req.headers.referer ?? req.headers.referrer;
19
+ const userAgent = req.headers["user-agent"];
20
+ const contentLength = Number(res.getHeader("content-length"));
21
+ const meta = {
22
+ date: (/* @__PURE__ */ new Date()).toISOString(),
23
+ method: req.method,
24
+ url: req.originalUrl ?? req.url,
25
+ status: res.statusCode,
26
+ httpVersion: `${req.httpVersionMajor}.${req.httpVersionMinor}`
27
+ };
28
+ if (userAgent) {
29
+ meta.userAgent = userAgent;
30
+ }
31
+ if (isFinite(contentLength)) {
32
+ meta.contentLength = contentLength;
33
+ }
34
+ if (referrer) {
35
+ meta.referrer = Array.isArray(referrer) ? referrer.join(", ") : referrer;
36
+ }
37
+ return meta;
38
+ }
19
39
  class MiddlewareFactory {
20
40
  #config;
21
41
  #logger;
@@ -67,17 +87,20 @@ class MiddlewareFactory {
67
87
  * @returns An Express request handler
68
88
  */
69
89
  logging() {
70
- const logger = this.#logger.child({
71
- type: "incomingRequest"
72
- });
73
- const customMorganFormat = '[:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"';
74
- return morgan__default.default(customMorganFormat, {
75
- stream: {
76
- write(message) {
77
- logger.info(message.trimEnd());
78
- }
79
- }
80
- });
90
+ const logger = this.#logger;
91
+ return (req, res, next) => {
92
+ res.on("finish", () => {
93
+ const meta = getLogMeta(req, res);
94
+ logger.info(
95
+ `[${meta.date}] "${meta.method} ${meta.url} HTTP/${meta.httpVersion}" ${meta.status} ${meta.contentLength ?? 0} "${meta.referrer ?? "-"}" "${meta.userAgent ?? "-"}"`,
96
+ {
97
+ type: "incomingRequest",
98
+ ...meta
99
+ }
100
+ );
101
+ });
102
+ next();
103
+ };
81
104
  }
82
105
  /**
83
106
  * Returns a middleware that implements the helmet library.
@@ -1 +1 @@
1
- {"version":3,"file":"MiddlewareFactory.cjs.js","sources":["../../../../src/entrypoints/rootHttpRouter/http/MiddlewareFactory.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 {\n RootConfigService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport {\n Request,\n Response,\n ErrorRequestHandler,\n NextFunction,\n RequestHandler,\n} from 'express';\nimport cors from 'cors';\nimport helmet from 'helmet';\nimport morgan from 'morgan';\nimport compression from 'compression';\nimport { readHelmetOptions } from './readHelmetOptions';\nimport { readCorsOptions } from './readCorsOptions';\nimport {\n AuthenticationError,\n ConflictError,\n ErrorResponseBody,\n InputError,\n NotAllowedError,\n NotFoundError,\n NotModifiedError,\n ServiceUnavailableError,\n serializeError,\n} from '@backstage/errors';\nimport { NotImplementedError } from '@backstage/errors';\nimport { applyInternalErrorFilter } from './applyInternalErrorFilter';\n\n/**\n * Options used to create a {@link MiddlewareFactory}.\n *\n * @public\n */\nexport interface MiddlewareFactoryOptions {\n config: RootConfigService;\n logger: LoggerService;\n}\n\n/**\n * Options passed to the {@link MiddlewareFactory.error} middleware.\n *\n * @public\n */\nexport interface MiddlewareFactoryErrorOptions {\n /**\n * Whether error response bodies should show error stack traces or not.\n *\n * If not specified, by default shows stack traces only in development mode.\n */\n showStackTraces?: boolean;\n\n /**\n * Whether any 4xx errors should be logged or not.\n *\n * If not specified, default to only logging 5xx errors.\n */\n logAllErrors?: boolean;\n}\n\n/**\n * A utility to configure common middleware.\n *\n * @public\n */\nexport class MiddlewareFactory {\n #config: RootConfigService;\n #logger: LoggerService;\n\n /**\n * Creates a new {@link MiddlewareFactory}.\n */\n static create(options: MiddlewareFactoryOptions) {\n return new MiddlewareFactory(options);\n }\n\n private constructor(options: MiddlewareFactoryOptions) {\n this.#config = options.config;\n this.#logger = options.logger;\n }\n\n /**\n * Returns a middleware that unconditionally produces a 404 error response.\n *\n * @remarks\n *\n * Typically you want to place this middleware at the end of the chain, such\n * that it's the last one attempted after no other routes matched.\n *\n * @returns An Express request handler\n */\n notFound(): RequestHandler {\n return (_req: Request, res: Response) => {\n res.status(404).end();\n };\n }\n\n /**\n * Returns the compression middleware.\n *\n * @remarks\n *\n * The middleware will attempt to compress response bodies for all requests\n * that traverse through the middleware.\n */\n compression(): RequestHandler {\n return compression();\n }\n\n /**\n * Returns a request logging middleware.\n *\n * @remarks\n *\n * Typically you want to place this middleware at the start of the chain, such\n * that it always logs requests whether they are \"caught\" by handlers farther\n * down or not.\n *\n * @returns An Express request handler\n */\n logging(): RequestHandler {\n const logger = this.#logger.child({\n type: 'incomingRequest',\n });\n const customMorganFormat =\n '[:date[clf]] \":method :url HTTP/:http-version\" :status :res[content-length] \":referrer\" \":user-agent\"';\n return morgan(customMorganFormat, {\n stream: {\n write(message: string) {\n logger.info(message.trimEnd());\n },\n },\n });\n }\n\n /**\n * Returns a middleware that implements the helmet library.\n *\n * @remarks\n *\n * This middleware applies security policies to incoming requests and outgoing\n * responses. It is configured using config keys such as `backend.csp`.\n *\n * @see {@link https://helmetjs.github.io/}\n *\n * @returns An Express request handler\n */\n helmet(): RequestHandler {\n return helmet(readHelmetOptions(this.#config.getOptionalConfig('backend')));\n }\n\n /**\n * Returns a middleware that implements the cors library.\n *\n * @remarks\n *\n * This middleware handles CORS. It is configured using the config key\n * `backend.cors`.\n *\n * @see {@link https://github.com/expressjs/cors}\n *\n * @returns An Express request handler\n */\n cors(): RequestHandler {\n return cors(readCorsOptions(this.#config.getOptionalConfig('backend')));\n }\n\n /**\n * Express middleware to handle errors during request processing.\n *\n * @remarks\n *\n * This is commonly the very last middleware in the chain.\n *\n * Its primary purpose is not to do translation of business logic exceptions,\n * but rather to be a global catch-all for uncaught \"fatal\" errors that are\n * expected to result in a 500 error. However, it also does handle some common\n * error types (such as http-error exceptions, and the well-known error types\n * in the `@backstage/errors` package) and returns the enclosed status code\n * accordingly.\n *\n * It will also produce a response body with a serialized form of the error,\n * unless a previous handler already did send a body. See\n * {@link @backstage/errors#ErrorResponseBody} for the response shape used.\n *\n * @returns An Express error request handler\n */\n error(options: MiddlewareFactoryErrorOptions = {}): ErrorRequestHandler {\n const showStackTraces =\n options.showStackTraces ?? process.env.NODE_ENV === 'development';\n\n const logger = this.#logger.child({\n type: 'errorHandler',\n });\n\n return (\n rawError: Error,\n req: Request,\n res: Response,\n next: NextFunction,\n ) => {\n const error = applyInternalErrorFilter(rawError, logger);\n\n const statusCode = getStatusCode(error);\n if (options.logAllErrors || statusCode >= 500) {\n logger.error(`Request failed with status ${statusCode}`, error);\n }\n\n if (res.headersSent) {\n // If the headers have already been sent, do not send the response again\n // as this will throw an error in the backend.\n next(error);\n return;\n }\n\n const body: ErrorResponseBody = {\n error: serializeError(error, { includeStack: showStackTraces }),\n request: { method: req.method, url: req.url },\n response: { statusCode },\n };\n\n res.status(statusCode).json(body);\n };\n }\n}\n\nfunction getStatusCode(error: Error): number {\n // Look for common http library status codes\n const knownStatusCodeFields = ['statusCode', 'status'];\n for (const field of knownStatusCodeFields) {\n const statusCode = (error as any)[field];\n if (\n typeof statusCode === 'number' &&\n (statusCode | 0) === statusCode && // is whole integer\n statusCode >= 100 &&\n statusCode <= 599\n ) {\n return statusCode;\n }\n }\n\n // Handle well-known error types\n switch (error.name) {\n case NotModifiedError.name:\n return 304;\n case InputError.name:\n return 400;\n case AuthenticationError.name:\n return 401;\n case NotAllowedError.name:\n return 403;\n case NotFoundError.name:\n return 404;\n case ConflictError.name:\n return 409;\n case NotImplementedError.name:\n return 501;\n case ServiceUnavailableError.name:\n return 503;\n default:\n break;\n }\n\n // Fall back to internal server error\n return 500;\n}\n"],"names":["compression","morgan","helmet","readHelmetOptions","cors","readCorsOptions","applyInternalErrorFilter","serializeError","NotModifiedError","InputError","AuthenticationError","NotAllowedError","NotFoundError","ConflictError","NotImplementedError","ServiceUnavailableError"],"mappings":"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"}
1
+ {"version":3,"file":"MiddlewareFactory.cjs.js","sources":["../../../../src/entrypoints/rootHttpRouter/http/MiddlewareFactory.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 {\n RootConfigService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport {\n Request,\n Response,\n ErrorRequestHandler,\n NextFunction,\n RequestHandler,\n} from 'express';\nimport cors from 'cors';\nimport helmet from 'helmet';\nimport compression from 'compression';\nimport { readHelmetOptions } from './readHelmetOptions';\nimport { readCorsOptions } from './readCorsOptions';\nimport {\n AuthenticationError,\n ConflictError,\n ErrorResponseBody,\n InputError,\n NotAllowedError,\n NotFoundError,\n NotModifiedError,\n ServiceUnavailableError,\n serializeError,\n} from '@backstage/errors';\nimport { NotImplementedError } from '@backstage/errors';\nimport { applyInternalErrorFilter } from './applyInternalErrorFilter';\n\ntype LogMeta = {\n date: string;\n method: string;\n url: string;\n status: number;\n httpVersion: string;\n userAgent?: string;\n contentLength?: number;\n referrer?: string;\n};\n\nfunction getLogMeta(req: Request, res: Response): LogMeta {\n const referrer = req.headers.referer ?? req.headers.referrer;\n const userAgent = req.headers['user-agent'];\n const contentLength = Number(res.getHeader('content-length'));\n\n const meta: LogMeta = {\n date: new Date().toISOString(),\n method: req.method,\n url: req.originalUrl ?? req.url,\n status: res.statusCode,\n httpVersion: `${req.httpVersionMajor}.${req.httpVersionMinor}`,\n };\n\n if (userAgent) {\n meta.userAgent = userAgent;\n }\n\n if (isFinite(contentLength)) {\n meta.contentLength = contentLength;\n }\n\n if (referrer) {\n meta.referrer = Array.isArray(referrer) ? referrer.join(', ') : referrer;\n }\n\n return meta;\n}\n\n/**\n * Options used to create a {@link MiddlewareFactory}.\n *\n * @public\n */\nexport interface MiddlewareFactoryOptions {\n config: RootConfigService;\n logger: LoggerService;\n}\n\n/**\n * Options passed to the {@link MiddlewareFactory.error} middleware.\n *\n * @public\n */\nexport interface MiddlewareFactoryErrorOptions {\n /**\n * Whether error response bodies should show error stack traces or not.\n *\n * If not specified, by default shows stack traces only in development mode.\n */\n showStackTraces?: boolean;\n\n /**\n * Whether any 4xx errors should be logged or not.\n *\n * If not specified, default to only logging 5xx errors.\n */\n logAllErrors?: boolean;\n}\n\n/**\n * A utility to configure common middleware.\n *\n * @public\n */\nexport class MiddlewareFactory {\n #config: RootConfigService;\n #logger: LoggerService;\n\n /**\n * Creates a new {@link MiddlewareFactory}.\n */\n static create(options: MiddlewareFactoryOptions) {\n return new MiddlewareFactory(options);\n }\n\n private constructor(options: MiddlewareFactoryOptions) {\n this.#config = options.config;\n this.#logger = options.logger;\n }\n\n /**\n * Returns a middleware that unconditionally produces a 404 error response.\n *\n * @remarks\n *\n * Typically you want to place this middleware at the end of the chain, such\n * that it's the last one attempted after no other routes matched.\n *\n * @returns An Express request handler\n */\n notFound(): RequestHandler {\n return (_req: Request, res: Response) => {\n res.status(404).end();\n };\n }\n\n /**\n * Returns the compression middleware.\n *\n * @remarks\n *\n * The middleware will attempt to compress response bodies for all requests\n * that traverse through the middleware.\n */\n compression(): RequestHandler {\n return compression();\n }\n\n /**\n * Returns a request logging middleware.\n *\n * @remarks\n *\n * Typically you want to place this middleware at the start of the chain, such\n * that it always logs requests whether they are \"caught\" by handlers farther\n * down or not.\n *\n * @returns An Express request handler\n */\n logging(): RequestHandler {\n const logger = this.#logger;\n return (req: Request, res: Response, next: NextFunction) => {\n res.on('finish', () => {\n const meta = getLogMeta(req, res);\n logger.info(\n `[${meta.date}] \"${meta.method} ${meta.url} HTTP/${\n meta.httpVersion\n }\" ${meta.status} ${meta.contentLength ?? 0} \"${\n meta.referrer ?? '-'\n }\" \"${meta.userAgent ?? '-'}\"`,\n {\n type: 'incomingRequest',\n ...meta,\n },\n );\n });\n next();\n };\n }\n\n /**\n * Returns a middleware that implements the helmet library.\n *\n * @remarks\n *\n * This middleware applies security policies to incoming requests and outgoing\n * responses. It is configured using config keys such as `backend.csp`.\n *\n * @see {@link https://helmetjs.github.io/}\n *\n * @returns An Express request handler\n */\n helmet(): RequestHandler {\n return helmet(readHelmetOptions(this.#config.getOptionalConfig('backend')));\n }\n\n /**\n * Returns a middleware that implements the cors library.\n *\n * @remarks\n *\n * This middleware handles CORS. It is configured using the config key\n * `backend.cors`.\n *\n * @see {@link https://github.com/expressjs/cors}\n *\n * @returns An Express request handler\n */\n cors(): RequestHandler {\n return cors(readCorsOptions(this.#config.getOptionalConfig('backend')));\n }\n\n /**\n * Express middleware to handle errors during request processing.\n *\n * @remarks\n *\n * This is commonly the very last middleware in the chain.\n *\n * Its primary purpose is not to do translation of business logic exceptions,\n * but rather to be a global catch-all for uncaught \"fatal\" errors that are\n * expected to result in a 500 error. However, it also does handle some common\n * error types (such as http-error exceptions, and the well-known error types\n * in the `@backstage/errors` package) and returns the enclosed status code\n * accordingly.\n *\n * It will also produce a response body with a serialized form of the error,\n * unless a previous handler already did send a body. See\n * {@link @backstage/errors#ErrorResponseBody} for the response shape used.\n *\n * @returns An Express error request handler\n */\n error(options: MiddlewareFactoryErrorOptions = {}): ErrorRequestHandler {\n const showStackTraces =\n options.showStackTraces ?? process.env.NODE_ENV === 'development';\n\n const logger = this.#logger.child({\n type: 'errorHandler',\n });\n\n return (\n rawError: Error,\n req: Request,\n res: Response,\n next: NextFunction,\n ) => {\n const error = applyInternalErrorFilter(rawError, logger);\n\n const statusCode = getStatusCode(error);\n if (options.logAllErrors || statusCode >= 500) {\n logger.error(`Request failed with status ${statusCode}`, error);\n }\n\n if (res.headersSent) {\n // If the headers have already been sent, do not send the response again\n // as this will throw an error in the backend.\n next(error);\n return;\n }\n\n const body: ErrorResponseBody = {\n error: serializeError(error, { includeStack: showStackTraces }),\n request: { method: req.method, url: req.url },\n response: { statusCode },\n };\n\n res.status(statusCode).json(body);\n };\n }\n}\n\nfunction getStatusCode(error: Error): number {\n // Look for common http library status codes\n const knownStatusCodeFields = ['statusCode', 'status'];\n for (const field of knownStatusCodeFields) {\n const statusCode = (error as any)[field];\n if (\n typeof statusCode === 'number' &&\n (statusCode | 0) === statusCode && // is whole integer\n statusCode >= 100 &&\n statusCode <= 599\n ) {\n return statusCode;\n }\n }\n\n // Handle well-known error types\n switch (error.name) {\n case NotModifiedError.name:\n return 304;\n case InputError.name:\n return 400;\n case AuthenticationError.name:\n return 401;\n case NotAllowedError.name:\n return 403;\n case NotFoundError.name:\n return 404;\n case ConflictError.name:\n return 409;\n case NotImplementedError.name:\n return 501;\n case ServiceUnavailableError.name:\n return 503;\n default:\n break;\n }\n\n // Fall back to internal server error\n return 500;\n}\n"],"names":["compression","helmet","readHelmetOptions","cors","readCorsOptions","applyInternalErrorFilter","serializeError","NotModifiedError","InputError","AuthenticationError","NotAllowedError","NotFoundError","ConflictError","NotImplementedError","ServiceUnavailableError"],"mappings":"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"}
@@ -0,0 +1,156 @@
1
+ 'use strict';
2
+
3
+ var storageBlob = require('@azure/storage-blob');
4
+ var errors = require('@backstage/errors');
5
+ var stream = require('stream');
6
+ var posix = require('path/posix');
7
+ var ReadUrlResponseFactory = require('./ReadUrlResponseFactory.cjs.js');
8
+ var integration = require('@backstage/integration');
9
+
10
+ function parseUrl(url) {
11
+ const parsedUrl = new URL(url);
12
+ const pathSegments = parsedUrl.pathname.split("/").filter(Boolean);
13
+ if (pathSegments.length < 2) {
14
+ throw new Error(`Invalid Azure Blob Storage URL format: ${url}`);
15
+ }
16
+ const container = pathSegments[0];
17
+ const path = pathSegments.slice(1).join("/");
18
+ return { path, container };
19
+ }
20
+ class AzureBlobStorageUrlReader {
21
+ // private readonly blobServiceClient: BlobServiceClient;
22
+ constructor(credsManager, integration, deps) {
23
+ this.credsManager = credsManager;
24
+ this.integration = integration;
25
+ this.deps = deps;
26
+ }
27
+ static factory = ({ config, treeResponseFactory }) => {
28
+ const integrations = integration.ScmIntegrations.fromConfig(config);
29
+ const credsManager = integration.DefaultAzureCredentialsManager.fromIntegrations(integrations);
30
+ return integrations.azureBlobStorage.list().map((integrationConfig) => {
31
+ const reader = new AzureBlobStorageUrlReader(
32
+ credsManager,
33
+ integrationConfig,
34
+ {
35
+ treeResponseFactory
36
+ }
37
+ );
38
+ const predicate = (url) => url.host.endsWith(
39
+ `${integrationConfig.config.accountName}.${integrationConfig.config.host}`
40
+ );
41
+ return { reader, predicate };
42
+ });
43
+ };
44
+ async createContainerClient(containerName) {
45
+ const accountName = this.integration.config.accountName;
46
+ const accountKey = this.integration.config.accountKey;
47
+ if (accountKey && accountName) {
48
+ const creds = new storageBlob.StorageSharedKeyCredential(accountName, accountKey);
49
+ const blobServiceClient2 = new storageBlob.BlobServiceClient(
50
+ `https://${accountName}.${this.integration.config.host}`,
51
+ creds
52
+ );
53
+ return blobServiceClient2.getContainerClient(containerName);
54
+ }
55
+ const credential = await this.credsManager.getCredentials(
56
+ accountName
57
+ );
58
+ let blobServiceClientUrl;
59
+ if (this.integration.config.endpoint) {
60
+ if (this.integration.config.sasToken) {
61
+ blobServiceClientUrl = `${this.integration.config.endpoint}?${this.integration.config.sasToken}`;
62
+ } else {
63
+ blobServiceClientUrl = `${this.integration.config.endpoint}`;
64
+ }
65
+ } else {
66
+ blobServiceClientUrl = `https://${this.integration.config.accountName}.${this.integration.config.host}`;
67
+ }
68
+ const blobServiceClient = new storageBlob.BlobServiceClient(
69
+ blobServiceClientUrl,
70
+ credential
71
+ );
72
+ return blobServiceClient.getContainerClient(containerName);
73
+ }
74
+ async read(url) {
75
+ const response = await this.readUrl(url);
76
+ return response.buffer();
77
+ }
78
+ async readUrl(url, options) {
79
+ const { etag, lastModifiedAfter } = options ?? {};
80
+ try {
81
+ const { path, container } = parseUrl(url);
82
+ const containerClient = await this.createContainerClient(container);
83
+ const blobClient = containerClient.getBlobClient(path);
84
+ const getBlobOptions = {
85
+ abortSignal: options?.signal,
86
+ conditions: {
87
+ ...etag && { ifNoneMatch: etag },
88
+ ...lastModifiedAfter && { ifModifiedSince: lastModifiedAfter }
89
+ }
90
+ };
91
+ const downloadBlockBlobResponse = await blobClient.download(
92
+ 0,
93
+ void 0,
94
+ getBlobOptions
95
+ );
96
+ return ReadUrlResponseFactory.ReadUrlResponseFactory.fromReadable(
97
+ downloadBlockBlobResponse.readableStreamBody,
98
+ {
99
+ etag: downloadBlockBlobResponse.etag,
100
+ lastModifiedAt: downloadBlockBlobResponse.lastModified
101
+ }
102
+ );
103
+ } catch (e) {
104
+ if (e.$metadata && e.$metadata.httpStatusCode === 304) {
105
+ throw new errors.NotModifiedError();
106
+ }
107
+ throw new errors.ForwardedError(
108
+ "Could not retrieve file from Azure Blob Storage",
109
+ e
110
+ );
111
+ }
112
+ }
113
+ async readTree(url, options) {
114
+ try {
115
+ const { path, container } = parseUrl(url);
116
+ const containerClient = await this.createContainerClient(container);
117
+ const blobs = containerClient.listBlobsFlat({ prefix: path });
118
+ const responses = [];
119
+ for await (const blob of blobs) {
120
+ const blobClient = containerClient.getBlobClient(blob.name);
121
+ const downloadBlockBlobResponse = await blobClient.download(
122
+ void 0,
123
+ void 0,
124
+ { abortSignal: options?.signal }
125
+ );
126
+ responses.push({
127
+ data: stream.Readable.from(
128
+ downloadBlockBlobResponse.readableStreamBody
129
+ ),
130
+ path: posix.relative(path, blob.name),
131
+ lastModifiedAt: blob.properties.lastModified
132
+ });
133
+ }
134
+ return this.deps.treeResponseFactory.fromReadableArray(responses);
135
+ } catch (e) {
136
+ throw new errors.ForwardedError(
137
+ "Could not retrieve file tree from Azure Blob Storage",
138
+ e
139
+ );
140
+ }
141
+ }
142
+ async search() {
143
+ throw new Error("AzureBlobStorageUrlReader does not implement search");
144
+ }
145
+ toString() {
146
+ const accountName = this.integration.config.accountName;
147
+ const accountKey = this.integration.config.accountKey;
148
+ return `azureBlobStorage{accountName=${accountName},authed=${Boolean(
149
+ accountKey
150
+ )}}`;
151
+ }
152
+ }
153
+
154
+ exports.AzureBlobStorageUrlReader = AzureBlobStorageUrlReader;
155
+ exports.parseUrl = parseUrl;
156
+ //# sourceMappingURL=AzureBlobStorageUrlReader.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AzureBlobStorageUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.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 {\n BlobDownloadOptions,\n BlobServiceClient,\n ContainerClient,\n StorageSharedKeyCredential,\n} from '@azure/storage-blob';\nimport { ReaderFactory, ReadTreeResponseFactory } from './types';\nimport { ForwardedError, NotModifiedError } from '@backstage/errors';\nimport { Readable } from 'stream';\nimport { relative } from 'path/posix';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport {\n AzureBlobStorageIntergation,\n AzureCredentialsManager,\n DefaultAzureCredentialsManager,\n ScmIntegrations,\n} from '@backstage/integration';\nimport {\n UrlReaderService,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\n\nexport function parseUrl(url: string): { path: string; container: string } {\n const parsedUrl = new URL(url);\n const pathSegments = parsedUrl.pathname.split('/').filter(Boolean);\n\n if (pathSegments.length < 2) {\n throw new Error(`Invalid Azure Blob Storage URL format: ${url}`);\n }\n\n // First segment is the container name, rest is the blob path\n const container = pathSegments[0];\n const path = pathSegments.slice(1).join('/');\n\n return { path, container };\n}\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for Azure storage accounts urls.\n *\n * @public\n */\nexport class AzureBlobStorageUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const integrations = ScmIntegrations.fromConfig(config);\n\n const credsManager =\n DefaultAzureCredentialsManager.fromIntegrations(integrations);\n\n return integrations.azureBlobStorage.list().map(integrationConfig => {\n const reader = new AzureBlobStorageUrlReader(\n credsManager,\n integrationConfig,\n {\n treeResponseFactory,\n },\n );\n\n const predicate = (url: URL) =>\n url.host.endsWith(\n `${integrationConfig.config.accountName}.${integrationConfig.config.host}`,\n );\n return { reader, predicate };\n });\n };\n\n // private readonly blobServiceClient: BlobServiceClient;\n\n constructor(\n private readonly credsManager: AzureCredentialsManager,\n private readonly integration: AzureBlobStorageIntergation,\n private readonly deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n },\n ) {}\n\n private async createContainerClient(\n containerName: string,\n ): Promise<ContainerClient> {\n const accountName = this.integration.config.accountName; // Use the account name from the integration config\n const accountKey = this.integration.config.accountKey; // Get the account key if it exists\n\n if (accountKey && accountName) {\n const creds = new StorageSharedKeyCredential(accountName, accountKey);\n const blobServiceClient = new BlobServiceClient(\n `https://${accountName}.${this.integration.config.host}`,\n creds,\n );\n return blobServiceClient.getContainerClient(containerName);\n }\n // Use the credentials manager to get the correct credentials\n const credential = await this.credsManager.getCredentials(\n accountName as string,\n );\n\n let blobServiceClientUrl: string;\n\n if (this.integration.config.endpoint) {\n if (this.integration.config.sasToken) {\n blobServiceClientUrl = `${this.integration.config.endpoint}?${this.integration.config.sasToken}`;\n } else {\n blobServiceClientUrl = `${this.integration.config.endpoint}`;\n }\n } else {\n blobServiceClientUrl = `https://${this.integration.config.accountName}.${this.integration.config.host}`;\n }\n\n const blobServiceClient = new BlobServiceClient(\n blobServiceClientUrl,\n credential,\n );\n return blobServiceClient.getContainerClient(containerName);\n }\n\n async read(url: string): Promise<Buffer> {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n const { etag, lastModifiedAfter } = options ?? {};\n\n try {\n const { path, container } = parseUrl(url);\n\n const containerClient = await this.createContainerClient(container);\n const blobClient = containerClient.getBlobClient(path);\n\n const getBlobOptions: BlobDownloadOptions = {\n abortSignal: options?.signal,\n conditions: {\n ...(etag && { ifNoneMatch: etag }),\n ...(lastModifiedAfter && { ifModifiedSince: lastModifiedAfter }),\n },\n };\n\n const downloadBlockBlobResponse = await blobClient.download(\n 0,\n undefined,\n getBlobOptions,\n );\n\n return ReadUrlResponseFactory.fromReadable(\n downloadBlockBlobResponse.readableStreamBody as Readable,\n {\n etag: downloadBlockBlobResponse.etag,\n lastModifiedAt: downloadBlockBlobResponse.lastModified,\n },\n );\n } catch (e) {\n if (e.$metadata && e.$metadata.httpStatusCode === 304) {\n throw new NotModifiedError();\n }\n\n throw new ForwardedError(\n 'Could not retrieve file from Azure Blob Storage',\n e,\n );\n }\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n try {\n const { path, container } = parseUrl(url);\n\n const containerClient = await this.createContainerClient(container);\n\n const blobs = containerClient.listBlobsFlat({ prefix: path });\n\n const responses = [];\n\n for await (const blob of blobs) {\n const blobClient = containerClient.getBlobClient(blob.name);\n const downloadBlockBlobResponse = await blobClient.download(\n undefined,\n undefined,\n { abortSignal: options?.signal },\n );\n\n responses.push({\n data: Readable.from(\n downloadBlockBlobResponse.readableStreamBody as Readable,\n ),\n path: relative(path, blob.name),\n lastModifiedAt: blob.properties.lastModified,\n });\n }\n\n return this.deps.treeResponseFactory.fromReadableArray(responses);\n } catch (e) {\n throw new ForwardedError(\n 'Could not retrieve file tree from Azure Blob Storage',\n e,\n );\n }\n }\n\n async search(): Promise<UrlReaderServiceSearchResponse> {\n throw new Error('AzureBlobStorageUrlReader does not implement search');\n }\n\n toString() {\n const accountName = this.integration.config.accountName;\n const accountKey = this.integration.config.accountKey;\n return `azureBlobStorage{accountName=${accountName},authed=${Boolean(\n accountKey,\n )}}`;\n }\n}\n"],"names":["ScmIntegrations","DefaultAzureCredentialsManager","StorageSharedKeyCredential","blobServiceClient","BlobServiceClient","ReadUrlResponseFactory","NotModifiedError","ForwardedError","Readable","relative"],"mappings":";;;;;;;;;AA0CO,SAAS,SAAS,GAAkD,EAAA;AACzE,EAAM,MAAA,SAAA,GAAY,IAAI,GAAA,CAAI,GAAG,CAAA;AAC7B,EAAA,MAAM,eAAe,SAAU,CAAA,QAAA,CAAS,MAAM,GAAG,CAAA,CAAE,OAAO,OAAO,CAAA;AAEjE,EAAI,IAAA,YAAA,CAAa,SAAS,CAAG,EAAA;AAC3B,IAAA,MAAM,IAAI,KAAA,CAAM,CAA0C,uCAAA,EAAA,GAAG,CAAE,CAAA,CAAA;AAAA;AAIjE,EAAM,MAAA,SAAA,GAAY,aAAa,CAAC,CAAA;AAChC,EAAA,MAAM,OAAO,YAAa,CAAA,KAAA,CAAM,CAAC,CAAA,CAAE,KAAK,GAAG,CAAA;AAE3C,EAAO,OAAA,EAAE,MAAM,SAAU,EAAA;AAC3B;AAOO,MAAM,yBAAsD,CAAA;AAAA;AAAA,EA0BjE,WAAA,CACmB,YACA,EAAA,WAAA,EACA,IAGjB,EAAA;AALiB,IAAA,IAAA,CAAA,YAAA,GAAA,YAAA;AACA,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAAA;AAGhB,EA/BH,OAAO,OAAyB,GAAA,CAAC,EAAE,MAAA,EAAQ,qBAA0B,KAAA;AACnE,IAAM,MAAA,YAAA,GAAeA,2BAAgB,CAAA,UAAA,CAAW,MAAM,CAAA;AAEtD,IAAM,MAAA,YAAA,GACJC,0CAA+B,CAAA,gBAAA,CAAiB,YAAY,CAAA;AAE9D,IAAA,OAAO,YAAa,CAAA,gBAAA,CAAiB,IAAK,EAAA,CAAE,IAAI,CAAqB,iBAAA,KAAA;AACnE,MAAA,MAAM,SAAS,IAAI,yBAAA;AAAA,QACjB,YAAA;AAAA,QACA,iBAAA;AAAA,QACA;AAAA,UACE;AAAA;AACF,OACF;AAEA,MAAA,MAAM,SAAY,GAAA,CAAC,GACjB,KAAA,GAAA,CAAI,IAAK,CAAA,QAAA;AAAA,QACP,GAAG,iBAAkB,CAAA,MAAA,CAAO,WAAW,CAAI,CAAA,EAAA,iBAAA,CAAkB,OAAO,IAAI,CAAA;AAAA,OAC1E;AACF,MAAO,OAAA,EAAE,QAAQ,SAAU,EAAA;AAAA,KAC5B,CAAA;AAAA,GACH;AAAA,EAYA,MAAc,sBACZ,aAC0B,EAAA;AAC1B,IAAM,MAAA,WAAA,GAAc,IAAK,CAAA,WAAA,CAAY,MAAO,CAAA,WAAA;AAC5C,IAAM,MAAA,UAAA,GAAa,IAAK,CAAA,WAAA,CAAY,MAAO,CAAA,UAAA;AAE3C,IAAA,IAAI,cAAc,WAAa,EAAA;AAC7B,MAAA,MAAM,KAAQ,GAAA,IAAIC,sCAA2B,CAAA,WAAA,EAAa,UAAU,CAAA;AACpE,MAAA,MAAMC,qBAAoB,IAAIC,6BAAA;AAAA,QAC5B,WAAW,WAAW,CAAA,CAAA,EAAI,IAAK,CAAA,WAAA,CAAY,OAAO,IAAI,CAAA,CAAA;AAAA,QACtD;AAAA,OACF;AACA,MAAOD,OAAAA,kBAAAA,CAAkB,mBAAmB,aAAa,CAAA;AAAA;AAG3D,IAAM,MAAA,UAAA,GAAa,MAAM,IAAA,CAAK,YAAa,CAAA,cAAA;AAAA,MACzC;AAAA,KACF;AAEA,IAAI,IAAA,oBAAA;AAEJ,IAAI,IAAA,IAAA,CAAK,WAAY,CAAA,MAAA,CAAO,QAAU,EAAA;AACpC,MAAI,IAAA,IAAA,CAAK,WAAY,CAAA,MAAA,CAAO,QAAU,EAAA;AACpC,QAAuB,oBAAA,GAAA,CAAA,EAAG,KAAK,WAAY,CAAA,MAAA,CAAO,QAAQ,CAAI,CAAA,EAAA,IAAA,CAAK,WAAY,CAAA,MAAA,CAAO,QAAQ,CAAA,CAAA;AAAA,OACzF,MAAA;AACL,QAAA,oBAAA,GAAuB,CAAG,EAAA,IAAA,CAAK,WAAY,CAAA,MAAA,CAAO,QAAQ,CAAA,CAAA;AAAA;AAC5D,KACK,MAAA;AACL,MAAuB,oBAAA,GAAA,CAAA,QAAA,EAAW,KAAK,WAAY,CAAA,MAAA,CAAO,WAAW,CAAI,CAAA,EAAA,IAAA,CAAK,WAAY,CAAA,MAAA,CAAO,IAAI,CAAA,CAAA;AAAA;AAGvG,IAAA,MAAM,oBAAoB,IAAIC,6BAAA;AAAA,MAC5B,oBAAA;AAAA,MACA;AAAA,KACF;AACA,IAAO,OAAA,iBAAA,CAAkB,mBAAmB,aAAa,CAAA;AAAA;AAC3D,EAEA,MAAM,KAAK,GAA8B,EAAA;AACvC,IAAA,MAAM,QAAW,GAAA,MAAM,IAAK,CAAA,OAAA,CAAQ,GAAG,CAAA;AACvC,IAAA,OAAO,SAAS,MAAO,EAAA;AAAA;AACzB,EAEA,MAAM,OACJ,CAAA,GAAA,EACA,OAC0C,EAAA;AAC1C,IAAA,MAAM,EAAE,IAAA,EAAM,iBAAkB,EAAA,GAAI,WAAW,EAAC;AAEhD,IAAI,IAAA;AACF,MAAA,MAAM,EAAE,IAAA,EAAM,SAAU,EAAA,GAAI,SAAS,GAAG,CAAA;AAExC,MAAA,MAAM,eAAkB,GAAA,MAAM,IAAK,CAAA,qBAAA,CAAsB,SAAS,CAAA;AAClE,MAAM,MAAA,UAAA,GAAa,eAAgB,CAAA,aAAA,CAAc,IAAI,CAAA;AAErD,MAAA,MAAM,cAAsC,GAAA;AAAA,QAC1C,aAAa,OAAS,EAAA,MAAA;AAAA,QACtB,UAAY,EAAA;AAAA,UACV,GAAI,IAAA,IAAQ,EAAE,WAAA,EAAa,IAAK,EAAA;AAAA,UAChC,GAAI,iBAAA,IAAqB,EAAE,eAAA,EAAiB,iBAAkB;AAAA;AAChE,OACF;AAEA,MAAM,MAAA,yBAAA,GAA4B,MAAM,UAAW,CAAA,QAAA;AAAA,QACjD,CAAA;AAAA,QACA,KAAA,CAAA;AAAA,QACA;AAAA,OACF;AAEA,MAAA,OAAOC,6CAAuB,CAAA,YAAA;AAAA,QAC5B,yBAA0B,CAAA,kBAAA;AAAA,QAC1B;AAAA,UACE,MAAM,yBAA0B,CAAA,IAAA;AAAA,UAChC,gBAAgB,yBAA0B,CAAA;AAAA;AAC5C,OACF;AAAA,aACO,CAAG,EAAA;AACV,MAAA,IAAI,CAAE,CAAA,SAAA,IAAa,CAAE,CAAA,SAAA,CAAU,mBAAmB,GAAK,EAAA;AACrD,QAAA,MAAM,IAAIC,uBAAiB,EAAA;AAAA;AAG7B,MAAA,MAAM,IAAIC,qBAAA;AAAA,QACR,iDAAA;AAAA,QACA;AAAA,OACF;AAAA;AACF;AACF,EAEA,MAAM,QACJ,CAAA,GAAA,EACA,OAC2C,EAAA;AAC3C,IAAI,IAAA;AACF,MAAA,MAAM,EAAE,IAAA,EAAM,SAAU,EAAA,GAAI,SAAS,GAAG,CAAA;AAExC,MAAA,MAAM,eAAkB,GAAA,MAAM,IAAK,CAAA,qBAAA,CAAsB,SAAS,CAAA;AAElE,MAAA,MAAM,QAAQ,eAAgB,CAAA,aAAA,CAAc,EAAE,MAAA,EAAQ,MAAM,CAAA;AAE5D,MAAA,MAAM,YAAY,EAAC;AAEnB,MAAA,WAAA,MAAiB,QAAQ,KAAO,EAAA;AAC9B,QAAA,MAAM,UAAa,GAAA,eAAA,CAAgB,aAAc,CAAA,IAAA,CAAK,IAAI,CAAA;AAC1D,QAAM,MAAA,yBAAA,GAA4B,MAAM,UAAW,CAAA,QAAA;AAAA,UACjD,KAAA,CAAA;AAAA,UACA,KAAA,CAAA;AAAA,UACA,EAAE,WAAa,EAAA,OAAA,EAAS,MAAO;AAAA,SACjC;AAEA,QAAA,SAAA,CAAU,IAAK,CAAA;AAAA,UACb,MAAMC,eAAS,CAAA,IAAA;AAAA,YACb,yBAA0B,CAAA;AAAA,WAC5B;AAAA,UACA,IAAM,EAAAC,cAAA,CAAS,IAAM,EAAA,IAAA,CAAK,IAAI,CAAA;AAAA,UAC9B,cAAA,EAAgB,KAAK,UAAW,CAAA;AAAA,SACjC,CAAA;AAAA;AAGH,MAAA,OAAO,IAAK,CAAA,IAAA,CAAK,mBAAoB,CAAA,iBAAA,CAAkB,SAAS,CAAA;AAAA,aACzD,CAAG,EAAA;AACV,MAAA,MAAM,IAAIF,qBAAA;AAAA,QACR,sDAAA;AAAA,QACA;AAAA,OACF;AAAA;AACF;AACF,EAEA,MAAM,MAAkD,GAAA;AACtD,IAAM,MAAA,IAAI,MAAM,qDAAqD,CAAA;AAAA;AACvE,EAEA,QAAW,GAAA;AACT,IAAM,MAAA,WAAA,GAAc,IAAK,CAAA,WAAA,CAAY,MAAO,CAAA,WAAA;AAC5C,IAAM,MAAA,UAAA,GAAa,IAAK,CAAA,WAAA,CAAY,MAAO,CAAA,UAAA;AAC3C,IAAO,OAAA,CAAA,6BAAA,EAAgC,WAAW,CAAW,QAAA,EAAA,OAAA;AAAA,MAC3D;AAAA,KACD,CAAA,CAAA,CAAA;AAAA;AAEL;;;;;"}
@@ -15,6 +15,7 @@ var AwsS3UrlReader = require('./AwsS3UrlReader.cjs.js');
15
15
  var GiteaUrlReader = require('./GiteaUrlReader.cjs.js');
16
16
  var AwsCodeCommitUrlReader = require('./AwsCodeCommitUrlReader.cjs.js');
17
17
  var HarnessUrlReader = require('./HarnessUrlReader.cjs.js');
18
+ var AzureBlobStorageUrlReader = require('./AzureBlobStorageUrlReader.cjs.js');
18
19
 
19
20
  class UrlReaders {
20
21
  /**
@@ -57,6 +58,7 @@ class UrlReaders {
57
58
  GoogleGcsUrlReader.GoogleGcsUrlReader.factory,
58
59
  HarnessUrlReader.HarnessUrlReader.factory,
59
60
  AwsS3UrlReader.AwsS3UrlReader.factory,
61
+ AzureBlobStorageUrlReader.AzureBlobStorageUrlReader.factory,
60
62
  AwsCodeCommitUrlReader.AwsCodeCommitUrlReader.factory,
61
63
  FetchUrlReader.FetchUrlReader.factory
62
64
  ])
@@ -1 +1 @@
1
- {"version":3,"file":"UrlReaders.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/UrlReaders.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 {\n LoggerService,\n RootConfigService,\n UrlReaderService,\n} from '@backstage/backend-plugin-api';\nimport { ReaderFactory } from './types';\nimport { UrlReaderPredicateMux } from './UrlReaderPredicateMux';\nimport { AzureUrlReader } from './AzureUrlReader';\nimport { BitbucketCloudUrlReader } from './BitbucketCloudUrlReader';\nimport { BitbucketServerUrlReader } from './BitbucketServerUrlReader';\nimport { BitbucketUrlReader } from './BitbucketUrlReader';\nimport { GerritUrlReader } from './GerritUrlReader';\nimport { GithubUrlReader } from './GithubUrlReader';\nimport { GitlabUrlReader } from './GitlabUrlReader';\nimport { DefaultReadTreeResponseFactory } from './tree';\nimport { FetchUrlReader } from './FetchUrlReader';\nimport { GoogleGcsUrlReader } from './GoogleGcsUrlReader';\nimport { AwsS3UrlReader } from './AwsS3UrlReader';\nimport { GiteaUrlReader } from './GiteaUrlReader';\nimport { AwsCodeCommitUrlReader } from './AwsCodeCommitUrlReader';\nimport { HarnessUrlReader } from './HarnessUrlReader';\n\n/**\n * Creation options for {@link @backstage/backend-plugin-api#UrlReaderService}.\n *\n * @public\n */\nexport type UrlReadersOptions = {\n /** Root config object */\n config: RootConfigService;\n /** Logger used by all the readers */\n logger: LoggerService;\n /** A list of factories used to construct individual readers that match on URLs */\n factories?: ReaderFactory[];\n};\n\n/**\n * Helps construct {@link @backstage/backend-plugin-api#UrlReaderService}s.\n *\n * @public\n */\nexport class UrlReaders {\n /**\n * Creates a custom {@link @backstage/backend-plugin-api#UrlReaderService} wrapper for your own set of factories.\n */\n static create(options: UrlReadersOptions): UrlReaderService {\n const { logger, config, factories } = options;\n const mux = new UrlReaderPredicateMux();\n const treeResponseFactory = DefaultReadTreeResponseFactory.create({\n config,\n });\n for (const factory of factories ?? []) {\n const tuples = factory({ config, logger: logger, treeResponseFactory });\n\n for (const tuple of tuples) {\n mux.register(tuple);\n }\n }\n\n return mux;\n }\n\n /**\n * Creates a {@link @backstage/backend-plugin-api#UrlReaderService} wrapper that includes all the default factories\n * from this package.\n *\n * Any additional factories passed will be loaded before the default ones.\n */\n static default(options: UrlReadersOptions) {\n const { logger, config, factories = [] } = options;\n return UrlReaders.create({\n logger,\n config,\n factories: factories.concat([\n AzureUrlReader.factory,\n BitbucketCloudUrlReader.factory,\n BitbucketServerUrlReader.factory,\n BitbucketUrlReader.factory,\n GerritUrlReader.factory,\n GithubUrlReader.factory,\n GiteaUrlReader.factory,\n GitlabUrlReader.factory,\n GoogleGcsUrlReader.factory,\n HarnessUrlReader.factory,\n AwsS3UrlReader.factory,\n AwsCodeCommitUrlReader.factory,\n FetchUrlReader.factory,\n ]),\n });\n }\n}\n"],"names":["UrlReaderPredicateMux","DefaultReadTreeResponseFactory","AzureUrlReader","BitbucketCloudUrlReader","BitbucketServerUrlReader","BitbucketUrlReader","GerritUrlReader","GithubUrlReader","GiteaUrlReader","GitlabUrlReader","GoogleGcsUrlReader","HarnessUrlReader","AwsS3UrlReader","AwsCodeCommitUrlReader","FetchUrlReader"],"mappings":";;;;;;;;;;;;;;;;;;AAyDO,MAAM,UAAW,CAAA;AAAA;AAAA;AAAA;AAAA,EAItB,OAAO,OAAO,OAA8C,EAAA;AAC1D,IAAA,MAAM,EAAE,MAAA,EAAQ,MAAQ,EAAA,SAAA,EAAc,GAAA,OAAA;AACtC,IAAM,MAAA,GAAA,GAAM,IAAIA,2CAAsB,EAAA;AACtC,IAAM,MAAA,mBAAA,GAAsBC,uDAA+B,MAAO,CAAA;AAAA,MAChE;AAAA,KACD,CAAA;AACD,IAAW,KAAA,MAAA,OAAA,IAAW,SAAa,IAAA,EAAI,EAAA;AACrC,MAAA,MAAM,SAAS,OAAQ,CAAA,EAAE,MAAQ,EAAA,MAAA,EAAgB,qBAAqB,CAAA;AAEtE,MAAA,KAAA,MAAW,SAAS,MAAQ,EAAA;AAC1B,QAAA,GAAA,CAAI,SAAS,KAAK,CAAA;AAAA;AACpB;AAGF,IAAO,OAAA,GAAA;AAAA;AACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,QAAQ,OAA4B,EAAA;AACzC,IAAA,MAAM,EAAE,MAAQ,EAAA,MAAA,EAAQ,SAAY,GAAA,IAAO,GAAA,OAAA;AAC3C,IAAA,OAAO,WAAW,MAAO,CAAA;AAAA,MACvB,MAAA;AAAA,MACA,MAAA;AAAA,MACA,SAAA,EAAW,UAAU,MAAO,CAAA;AAAA,QAC1BC,6BAAe,CAAA,OAAA;AAAA,QACfC,+CAAwB,CAAA,OAAA;AAAA,QACxBC,iDAAyB,CAAA,OAAA;AAAA,QACzBC,qCAAmB,CAAA,OAAA;AAAA,QACnBC,+BAAgB,CAAA,OAAA;AAAA,QAChBC,+BAAgB,CAAA,OAAA;AAAA,QAChBC,6BAAe,CAAA,OAAA;AAAA,QACfC,+BAAgB,CAAA,OAAA;AAAA,QAChBC,qCAAmB,CAAA,OAAA;AAAA,QACnBC,iCAAiB,CAAA,OAAA;AAAA,QACjBC,6BAAe,CAAA,OAAA;AAAA,QACfC,6CAAuB,CAAA,OAAA;AAAA,QACvBC,6BAAe,CAAA;AAAA,OAChB;AAAA,KACF,CAAA;AAAA;AAEL;;;;"}
1
+ {"version":3,"file":"UrlReaders.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/UrlReaders.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 {\n LoggerService,\n RootConfigService,\n UrlReaderService,\n} from '@backstage/backend-plugin-api';\nimport { ReaderFactory } from './types';\nimport { UrlReaderPredicateMux } from './UrlReaderPredicateMux';\nimport { AzureUrlReader } from './AzureUrlReader';\nimport { BitbucketCloudUrlReader } from './BitbucketCloudUrlReader';\nimport { BitbucketServerUrlReader } from './BitbucketServerUrlReader';\nimport { BitbucketUrlReader } from './BitbucketUrlReader';\nimport { GerritUrlReader } from './GerritUrlReader';\nimport { GithubUrlReader } from './GithubUrlReader';\nimport { GitlabUrlReader } from './GitlabUrlReader';\nimport { DefaultReadTreeResponseFactory } from './tree';\nimport { FetchUrlReader } from './FetchUrlReader';\nimport { GoogleGcsUrlReader } from './GoogleGcsUrlReader';\nimport { AwsS3UrlReader } from './AwsS3UrlReader';\nimport { GiteaUrlReader } from './GiteaUrlReader';\nimport { AwsCodeCommitUrlReader } from './AwsCodeCommitUrlReader';\nimport { HarnessUrlReader } from './HarnessUrlReader';\nimport { AzureBlobStorageUrlReader } from './AzureBlobStorageUrlReader';\n\n/**\n * Creation options for {@link @backstage/backend-plugin-api#UrlReaderService}.\n *\n * @public\n */\nexport type UrlReadersOptions = {\n /** Root config object */\n config: RootConfigService;\n /** Logger used by all the readers */\n logger: LoggerService;\n /** A list of factories used to construct individual readers that match on URLs */\n factories?: ReaderFactory[];\n};\n\n/**\n * Helps construct {@link @backstage/backend-plugin-api#UrlReaderService}s.\n *\n * @public\n */\nexport class UrlReaders {\n /**\n * Creates a custom {@link @backstage/backend-plugin-api#UrlReaderService} wrapper for your own set of factories.\n */\n static create(options: UrlReadersOptions): UrlReaderService {\n const { logger, config, factories } = options;\n const mux = new UrlReaderPredicateMux();\n const treeResponseFactory = DefaultReadTreeResponseFactory.create({\n config,\n });\n for (const factory of factories ?? []) {\n const tuples = factory({ config, logger: logger, treeResponseFactory });\n\n for (const tuple of tuples) {\n mux.register(tuple);\n }\n }\n\n return mux;\n }\n\n /**\n * Creates a {@link @backstage/backend-plugin-api#UrlReaderService} wrapper that includes all the default factories\n * from this package.\n *\n * Any additional factories passed will be loaded before the default ones.\n */\n static default(options: UrlReadersOptions) {\n const { logger, config, factories = [] } = options;\n return UrlReaders.create({\n logger,\n config,\n factories: factories.concat([\n AzureUrlReader.factory,\n BitbucketCloudUrlReader.factory,\n BitbucketServerUrlReader.factory,\n BitbucketUrlReader.factory,\n GerritUrlReader.factory,\n GithubUrlReader.factory,\n GiteaUrlReader.factory,\n GitlabUrlReader.factory,\n GoogleGcsUrlReader.factory,\n HarnessUrlReader.factory,\n AwsS3UrlReader.factory,\n AzureBlobStorageUrlReader.factory,\n AwsCodeCommitUrlReader.factory,\n FetchUrlReader.factory,\n ]),\n });\n }\n}\n"],"names":["UrlReaderPredicateMux","DefaultReadTreeResponseFactory","AzureUrlReader","BitbucketCloudUrlReader","BitbucketServerUrlReader","BitbucketUrlReader","GerritUrlReader","GithubUrlReader","GiteaUrlReader","GitlabUrlReader","GoogleGcsUrlReader","HarnessUrlReader","AwsS3UrlReader","AzureBlobStorageUrlReader","AwsCodeCommitUrlReader","FetchUrlReader"],"mappings":";;;;;;;;;;;;;;;;;;;AA0DO,MAAM,UAAW,CAAA;AAAA;AAAA;AAAA;AAAA,EAItB,OAAO,OAAO,OAA8C,EAAA;AAC1D,IAAA,MAAM,EAAE,MAAA,EAAQ,MAAQ,EAAA,SAAA,EAAc,GAAA,OAAA;AACtC,IAAM,MAAA,GAAA,GAAM,IAAIA,2CAAsB,EAAA;AACtC,IAAM,MAAA,mBAAA,GAAsBC,uDAA+B,MAAO,CAAA;AAAA,MAChE;AAAA,KACD,CAAA;AACD,IAAW,KAAA,MAAA,OAAA,IAAW,SAAa,IAAA,EAAI,EAAA;AACrC,MAAA,MAAM,SAAS,OAAQ,CAAA,EAAE,MAAQ,EAAA,MAAA,EAAgB,qBAAqB,CAAA;AAEtE,MAAA,KAAA,MAAW,SAAS,MAAQ,EAAA;AAC1B,QAAA,GAAA,CAAI,SAAS,KAAK,CAAA;AAAA;AACpB;AAGF,IAAO,OAAA,GAAA;AAAA;AACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,QAAQ,OAA4B,EAAA;AACzC,IAAA,MAAM,EAAE,MAAQ,EAAA,MAAA,EAAQ,SAAY,GAAA,IAAO,GAAA,OAAA;AAC3C,IAAA,OAAO,WAAW,MAAO,CAAA;AAAA,MACvB,MAAA;AAAA,MACA,MAAA;AAAA,MACA,SAAA,EAAW,UAAU,MAAO,CAAA;AAAA,QAC1BC,6BAAe,CAAA,OAAA;AAAA,QACfC,+CAAwB,CAAA,OAAA;AAAA,QACxBC,iDAAyB,CAAA,OAAA;AAAA,QACzBC,qCAAmB,CAAA,OAAA;AAAA,QACnBC,+BAAgB,CAAA,OAAA;AAAA,QAChBC,+BAAgB,CAAA,OAAA;AAAA,QAChBC,6BAAe,CAAA,OAAA;AAAA,QACfC,+BAAgB,CAAA,OAAA;AAAA,QAChBC,qCAAmB,CAAA,OAAA;AAAA,QACnBC,iCAAiB,CAAA,OAAA;AAAA,QACjBC,6BAAe,CAAA,OAAA;AAAA,QACfC,mDAA0B,CAAA,OAAA;AAAA,QAC1BC,6CAAuB,CAAA,OAAA;AAAA,QACvBC,6BAAe,CAAA;AAAA,OAChB;AAAA,KACF,CAAA;AAAA;AAEL;;;;"}
@@ -4,5 +4,6 @@ var httpAuthServiceFactory = require('./entrypoints/httpAuth/httpAuthServiceFact
4
4
 
5
5
 
6
6
 
7
+ exports.DefaultHttpAuthService = httpAuthServiceFactory.DefaultHttpAuthService;
7
8
  exports.httpAuthServiceFactory = httpAuthServiceFactory.httpAuthServiceFactory;
8
9
  //# sourceMappingURL=httpAuth.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"httpAuth.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
1
+ {"version":3,"file":"httpAuth.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;"}