@backstage/plugin-proxy-backend 0.4.16 → 0.5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,61 @@
1
1
  # @backstage/plugin-proxy-backend
2
2
 
3
+ ## 0.5.0-next.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 88480e4: **BREAKING**: The proxy backend plugin is now protected by Backstage auth, by
8
+ default. Unless specifically configured (see below), all proxy endpoints will
9
+ reject requests immediately unless a valid Backstage user or service token is
10
+ passed along with the request. This aligns the proxy with how other Backstage
11
+ backends behave out of the box, and serves to protect your upstreams from
12
+ unauthorized access.
13
+
14
+ A proxy configuration section can now look as follows:
15
+
16
+ ```yaml
17
+ proxy:
18
+ endpoints:
19
+ '/pagerduty':
20
+ target: https://api.pagerduty.com
21
+ credentials: require # NEW!
22
+ headers:
23
+ Authorization: Token token=${PAGERDUTY_TOKEN}
24
+ ```
25
+
26
+ There are three possible `credentials` settings at this point:
27
+
28
+ - `require`: Callers must provide Backstage user or service credentials with
29
+ each request. The credentials are not forwarded to the proxy target.
30
+ - `forward`: Callers must provide Backstage user or service credentials with
31
+ each request, and those credentials are forwarded to the proxy target.
32
+ - `dangerously-allow-unauthenticated`: No Backstage credentials are required to
33
+ access this proxy target. The target can still apply its own credentials
34
+ checks, but the proxy will not help block non-Backstage-blessed callers. If
35
+ you also add `allowedHeaders: ['Authorization']` to an endpoint configuration,
36
+ then the Backstage token (if provided) WILL be forwarded.
37
+
38
+ The value `dangerously-allow-unauthenticated` was the old default.
39
+
40
+ The value `require` is the new default, so requests that were previously
41
+ permitted may now start resulting in `401 Unauthorized` responses. If you have
42
+ `backend.auth.dangerouslyDisableDefaultAuthPolicy` set to `true`, this does not
43
+ apply; the proxy will behave as if all endpoints were set to
44
+ `dangerously-allow-unauthenticated`.
45
+
46
+ If you have proxy endpoints that require unauthenticated access still, please
47
+ add `credentials: dangerously-allow-unauthenticated` to their declarations in
48
+ your app-config.
49
+
50
+ ### Patch Changes
51
+
52
+ - 8869b8e: Updated local development setup.
53
+ - Updated dependencies
54
+ - @backstage/backend-common@0.22.1-next.0
55
+ - @backstage/backend-plugin-api@0.6.19-next.0
56
+ - @backstage/config@1.2.0
57
+ - @backstage/types@1.1.1
58
+
3
59
  ## 0.4.16
4
60
 
5
61
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-proxy-backend",
3
- "version": "0.4.16",
3
+ "version": "0.5.0-next.0",
4
4
  "main": "../dist/alpha.cjs.js",
5
5
  "types": "../dist/alpha.d.ts"
6
6
  }
package/config.d.ts CHANGED
@@ -71,6 +71,33 @@ export interface Config {
71
71
  * and headers that are set by the proxy will be forwarded.
72
72
  */
73
73
  allowedHeaders?: string[];
74
+ /**
75
+ * The credentials policy to apply.
76
+ *
77
+ * @remarks
78
+ *
79
+ * The values are as follows:
80
+ *
81
+ * - 'require': Callers must provide Backstage user or service
82
+ * credentials with each request. The credentials are not
83
+ * forwarded to the proxy target.
84
+ * - 'forward': Callers must provide Backstage user or service
85
+ * credentials with each request, and those credentials are
86
+ * forwarded to the proxy target.
87
+ * - 'dangerously-allow-unauthenticated': No Backstage credentials
88
+ * are required to access this proxy target. The target can still
89
+ * apply its own credentials checks, but the proxy will not help
90
+ * block non-Backstage-blessed callers.
91
+ *
92
+ * Note that if you have
93
+ * `backend.auth.dangerouslyDisableDefaultAuthPolicy` set to `true`,
94
+ * the `credentials` value does not apply; the proxy will behave as
95
+ * if all endpoints were set to `dangerously-allow-unauthenticated`.
96
+ */
97
+ credentials?:
98
+ | 'require'
99
+ | 'forward'
100
+ | 'dangerously-allow-unauthenticated';
74
101
  };
75
102
  };
76
103
  } & {
@@ -121,6 +148,33 @@ export interface Config {
121
148
  * and headers that are set by the proxy will be forwarded.
122
149
  */
123
150
  allowedHeaders?: string[];
151
+ /**
152
+ * The credentials policy to apply.
153
+ *
154
+ * @remarks
155
+ *
156
+ * The values are as follows:
157
+ *
158
+ * - 'require': Callers must provide Backstage user or service
159
+ * credentials with each request. The credentials are not forwarded
160
+ * to the proxy target.
161
+ * - 'forward': Callers must provide Backstage user or service
162
+ * credentials with each request, and those credentials are
163
+ * forwarded to the proxy target.
164
+ * - 'dangerously-allow-unauthenticated': No Backstage credentials are
165
+ * required to access this proxy target. The target can still apply
166
+ * its own credentials checks, but the proxy will not help block
167
+ * non-Backstage-blessed callers.
168
+ *
169
+ * Note that if you have
170
+ * `backend.auth.dangerouslyDisableDefaultAuthPolicy` set to `true`,
171
+ * the `credentials` value does not apply; the proxy will behave as if
172
+ * all endpoints were set to `dangerously-allow-unauthenticated`.
173
+ */
174
+ credentials?:
175
+ | 'require'
176
+ | 'forward'
177
+ | 'dangerously-allow-unauthenticated';
124
178
  };
125
179
  };
126
180
  }
package/dist/alpha.cjs.js CHANGED
@@ -4,7 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var backendCommon = require('@backstage/backend-common');
6
6
  var backendPluginApi = require('@backstage/backend-plugin-api');
7
- var router = require('./cjs/router-D6Z1qlfG.cjs.js');
7
+ var router = require('./cjs/router-BB9HHp8x.cjs.js');
8
8
  require('express-promise-router');
9
9
  require('http-proxy-middleware');
10
10
 
@@ -19,16 +19,11 @@ var alpha = backendPluginApi.createBackendPlugin({
19
19
  httpRouter: backendPluginApi.coreServices.httpRouter
20
20
  },
21
21
  async init({ config, discovery, logger, httpRouter }) {
22
- httpRouter.use(
23
- await router.createRouter({
24
- config,
25
- discovery,
26
- logger: backendCommon.loggerToWinstonLogger(logger)
27
- })
28
- );
29
- httpRouter.addAuthPolicy({
30
- allow: "unauthenticated",
31
- path: "/"
22
+ await router.createRouterInternal({
23
+ config,
24
+ discovery,
25
+ logger: backendCommon.loggerToWinstonLogger(logger),
26
+ httpRouterService: httpRouter
32
27
  });
33
28
  }
34
29
  });
@@ -1 +1 @@
1
- {"version":3,"file":"alpha.cjs.js","sources":["../src/alpha.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 { loggerToWinstonLogger } from '@backstage/backend-common';\nimport {\n createBackendPlugin,\n coreServices,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\n\n/**\n * The proxy backend plugin.\n *\n * @alpha\n */\nexport default createBackendPlugin({\n pluginId: 'proxy',\n register(env) {\n env.registerInit({\n deps: {\n config: coreServices.rootConfig,\n discovery: coreServices.discovery,\n logger: coreServices.logger,\n httpRouter: coreServices.httpRouter,\n },\n async init({ config, discovery, logger, httpRouter }) {\n httpRouter.use(\n await createRouter({\n config,\n discovery,\n logger: loggerToWinstonLogger(logger),\n }),\n );\n httpRouter.addAuthPolicy({\n allow: 'unauthenticated',\n path: '/',\n });\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","createRouter","loggerToWinstonLogger"],"mappings":";;;;;;;;;;AA4BA,YAAeA,oCAAoB,CAAA;AAAA,EACjC,QAAU,EAAA,OAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,QAAQC,6BAAa,CAAA,UAAA;AAAA,QACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,YAAYA,6BAAa,CAAA,UAAA;AAAA,OAC3B;AAAA,MACA,MAAM,IAAK,CAAA,EAAE,QAAQ,SAAW,EAAA,MAAA,EAAQ,YAAc,EAAA;AACpD,QAAW,UAAA,CAAA,GAAA;AAAA,UACT,MAAMC,mBAAa,CAAA;AAAA,YACjB,MAAA;AAAA,YACA,SAAA;AAAA,YACA,MAAA,EAAQC,oCAAsB,MAAM,CAAA;AAAA,WACrC,CAAA;AAAA,SACH,CAAA;AACA,QAAA,UAAA,CAAW,aAAc,CAAA;AAAA,UACvB,KAAO,EAAA,iBAAA;AAAA,UACP,IAAM,EAAA,GAAA;AAAA,SACP,CAAA,CAAA;AAAA,OACH;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF,CAAC,CAAA;;;;"}
1
+ {"version":3,"file":"alpha.cjs.js","sources":["../src/alpha.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 { loggerToWinstonLogger } from '@backstage/backend-common';\nimport {\n createBackendPlugin,\n coreServices,\n} from '@backstage/backend-plugin-api';\nimport { createRouterInternal } from './service/router';\n\n/**\n * The proxy backend plugin.\n *\n * @alpha\n */\nexport default createBackendPlugin({\n pluginId: 'proxy',\n register(env) {\n env.registerInit({\n deps: {\n config: coreServices.rootConfig,\n discovery: coreServices.discovery,\n logger: coreServices.logger,\n httpRouter: coreServices.httpRouter,\n },\n async init({ config, discovery, logger, httpRouter }) {\n await createRouterInternal({\n config,\n discovery,\n logger: loggerToWinstonLogger(logger),\n httpRouterService: httpRouter,\n });\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","createRouterInternal","loggerToWinstonLogger"],"mappings":";;;;;;;;;;AA4BA,YAAeA,oCAAoB,CAAA;AAAA,EACjC,QAAU,EAAA,OAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,QAAQC,6BAAa,CAAA,UAAA;AAAA,QACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,YAAYA,6BAAa,CAAA,UAAA;AAAA,OAC3B;AAAA,MACA,MAAM,IAAK,CAAA,EAAE,QAAQ,SAAW,EAAA,MAAA,EAAQ,YAAc,EAAA;AACpD,QAAA,MAAMC,2BAAqB,CAAA;AAAA,UACzB,MAAA;AAAA,UACA,SAAA;AAAA,UACA,MAAA,EAAQC,oCAAsB,MAAM,CAAA;AAAA,UACpC,iBAAmB,EAAA,UAAA;AAAA,SACpB,CAAA,CAAA;AAAA,OACH;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF,CAAC,CAAA;;;;"}
@@ -24,9 +24,34 @@ const safeForwardHeaders = [
24
24
  "accept-language",
25
25
  "user-agent"
26
26
  ];
27
- function buildMiddleware(pathPrefix, logger, route, config, reviveConsumedRequestBodies) {
28
- var _a;
29
- const fullConfig = typeof config === "string" ? { target: config } : { ...config };
27
+ function buildMiddleware(pathPrefix, logger, route, config, reviveConsumedRequestBodies, httpRouterService) {
28
+ let fullConfig;
29
+ let credentialsPolicy;
30
+ if (typeof config === "string") {
31
+ fullConfig = { target: config };
32
+ credentialsPolicy = "require";
33
+ } else {
34
+ const { credentials, ...rest } = config;
35
+ fullConfig = rest;
36
+ credentialsPolicy = credentials ?? "require";
37
+ }
38
+ const credentialsPolicyCandidates = [
39
+ "require",
40
+ "forward",
41
+ "dangerously-allow-unauthenticated"
42
+ ];
43
+ if (!credentialsPolicyCandidates.includes(credentialsPolicy)) {
44
+ const valid = credentialsPolicyCandidates.map((c) => `'${c}'`).join(", ");
45
+ throw new Error(
46
+ `Unknown credentials policy '${credentialsPolicy}' for proxy route '${route}'; expected one of ${valid}`
47
+ );
48
+ }
49
+ if (credentialsPolicy === "dangerously-allow-unauthenticated") {
50
+ httpRouterService?.addAuthPolicy({
51
+ path: route,
52
+ allow: "unauthenticated"
53
+ });
54
+ }
30
55
  const targetType = typeof fullConfig.target;
31
56
  if (targetType !== "string") {
32
57
  throw new Error(
@@ -37,7 +62,7 @@ function buildMiddleware(pathPrefix, logger, route, config, reviveConsumedReques
37
62
  new URL(fullConfig.target);
38
63
  } catch {
39
64
  throw new Error(
40
- `Proxy target is not a valid URL: ${(_a = fullConfig.target) != null ? _a : ""}`
65
+ `Proxy target is not a valid URL: ${fullConfig.target ?? ""}`
41
66
  );
42
67
  }
43
68
  if (fullConfig.pathRewrite === void 0) {
@@ -66,15 +91,17 @@ function buildMiddleware(pathPrefix, logger, route, config, reviveConsumedReques
66
91
  ...fullConfig.allowedHeaders || []
67
92
  ].map((h) => h.toLocaleLowerCase())
68
93
  );
94
+ if (credentialsPolicy === "forward") {
95
+ requestHeaderAllowList.add("authorization");
96
+ }
69
97
  const filter = (_pathname, req) => {
70
- var _a2, _b;
71
98
  const headerNames = Object.keys(req.headers);
72
99
  headerNames.forEach((h) => {
73
100
  if (!requestHeaderAllowList.has(h.toLocaleLowerCase())) {
74
101
  delete req.headers[h];
75
102
  }
76
103
  });
77
- return (_b = (_a2 = fullConfig == null ? void 0 : fullConfig.allowedMethods) == null ? void 0 : _a2.includes(req.method)) != null ? _b : true;
104
+ return fullConfig?.allowedMethods?.includes(req.method) ?? true;
78
105
  };
79
106
  filter.toString = () => route;
80
107
  const responseHeaderAllowList = new Set(
@@ -99,12 +126,11 @@ function buildMiddleware(pathPrefix, logger, route, config, reviveConsumedReques
99
126
  return httpProxyMiddleware.createProxyMiddleware(filter, fullConfig);
100
127
  }
101
128
  function readProxyConfig(config, logger) {
102
- var _a, _b;
103
- const endpoints = (_a = config.getOptionalConfig("proxy.endpoints")) == null ? void 0 : _a.get();
129
+ const endpoints = config.getOptionalConfig("proxy.endpoints")?.get();
104
130
  if (endpoints) {
105
131
  return endpoints;
106
132
  }
107
- const root = (_b = config.getOptionalConfig("proxy")) == null ? void 0 : _b.get();
133
+ const root = config.getOptionalConfig("proxy")?.get();
108
134
  if (!root) {
109
135
  return {};
110
136
  }
@@ -120,11 +146,13 @@ function readProxyConfig(config, logger) {
120
146
  return rootEndpoints;
121
147
  }
122
148
  async function createRouter(options) {
123
- var _a, _b, _c, _d;
149
+ return createRouterInternal(options);
150
+ }
151
+ async function createRouterInternal(options) {
124
152
  const router = Router__default.default();
125
153
  let currentRouter = Router__default.default();
126
- const skipInvalidProxies = (_b = (_a = options.skipInvalidProxies) != null ? _a : options.config.getOptionalBoolean("proxy.skipInvalidProxies")) != null ? _b : false;
127
- const reviveConsumedRequestBodies = (_d = (_c = options.reviveConsumedRequestBodies) != null ? _c : options.config.getOptionalBoolean("proxy.reviveConsumedRequestBodies")) != null ? _d : false;
154
+ const skipInvalidProxies = options.skipInvalidProxies ?? options.config.getOptionalBoolean("proxy.skipInvalidProxies") ?? false;
155
+ const reviveConsumedRequestBodies = options.reviveConsumedRequestBodies ?? options.config.getOptionalBoolean("proxy.reviveConsumedRequestBodies") ?? false;
128
156
  const proxyOptions = {
129
157
  skipInvalidProxies,
130
158
  reviveConsumedRequestBodies,
@@ -133,7 +161,13 @@ async function createRouter(options) {
133
161
  const externalUrl = await options.discovery.getExternalBaseUrl("proxy");
134
162
  const { pathname: pathPrefix } = new URL(externalUrl);
135
163
  const proxyConfig = readProxyConfig(options.config, options.logger);
136
- configureMiddlewares(proxyOptions, currentRouter, pathPrefix, proxyConfig);
164
+ configureMiddlewares(
165
+ proxyOptions,
166
+ currentRouter,
167
+ pathPrefix,
168
+ proxyConfig,
169
+ options.httpRouterService
170
+ );
137
171
  router.use((...args) => currentRouter(...args));
138
172
  if (options.config.subscribe) {
139
173
  let currentKey = JSON.stringify(proxyConfig);
@@ -147,14 +181,16 @@ async function createRouter(options) {
147
181
  proxyOptions,
148
182
  currentRouter,
149
183
  pathPrefix,
150
- newProxyConfig
184
+ newProxyConfig,
185
+ options.httpRouterService
151
186
  );
152
187
  }
153
188
  });
154
189
  }
190
+ options.httpRouterService?.use(router);
155
191
  return router;
156
192
  }
157
- function configureMiddlewares(options, router, pathPrefix, proxyConfig) {
193
+ function configureMiddlewares(options, router, pathPrefix, proxyConfig, httpRouterService) {
158
194
  Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => {
159
195
  try {
160
196
  router.use(
@@ -164,7 +200,8 @@ function configureMiddlewares(options, router, pathPrefix, proxyConfig) {
164
200
  options.logger,
165
201
  route,
166
202
  proxyRouteConfig,
167
- options.reviveConsumedRequestBodies
203
+ options.reviveConsumedRequestBodies,
204
+ httpRouterService
168
205
  )
169
206
  );
170
207
  } catch (e) {
@@ -178,4 +215,5 @@ function configureMiddlewares(options, router, pathPrefix, proxyConfig) {
178
215
  }
179
216
 
180
217
  exports.createRouter = createRouter;
181
- //# sourceMappingURL=router-D6Z1qlfG.cjs.js.map
218
+ exports.createRouterInternal = createRouterInternal;
219
+ //# sourceMappingURL=router-BB9HHp8x.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router-BB9HHp8x.cjs.js","sources":["../../src/service/router.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 { Config } from '@backstage/config';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport {\n createProxyMiddleware,\n fixRequestBody,\n Options,\n RequestHandler,\n} from 'http-proxy-middleware';\nimport { Logger } from 'winston';\nimport http from 'http';\nimport { PluginEndpointDiscovery } from '@backstage/backend-common';\nimport { JsonObject } from '@backstage/types';\nimport { HttpRouterService } from '@backstage/backend-plugin-api';\n\n// A list of headers that are always forwarded to the proxy targets.\nconst safeForwardHeaders = [\n // https://fetch.spec.whatwg.org/#cors-safelisted-request-header\n 'cache-control',\n 'content-language',\n 'content-length',\n 'content-type',\n 'expires',\n 'last-modified',\n 'pragma',\n\n // host is overridden by default. if changeOrigin is configured to false,\n // we assume this is a intentional and should also be forwarded.\n 'host',\n\n // other headers that we assume to be ok\n 'accept',\n 'accept-language',\n 'user-agent',\n];\n\n/** @public */\nexport interface RouterOptions {\n logger: Logger;\n config: Config;\n discovery: PluginEndpointDiscovery;\n skipInvalidProxies?: boolean;\n reviveConsumedRequestBodies?: boolean;\n}\n\nexport interface ProxyConfig extends Options {\n allowedMethods?: string[];\n allowedHeaders?: string[];\n reviveRequestBody?: boolean;\n}\n\n// Creates a proxy middleware, possibly with defaults added on top of the\n// given config.\nexport function buildMiddleware(\n pathPrefix: string,\n logger: Logger,\n route: string,\n config: string | ProxyConfig,\n reviveConsumedRequestBodies?: boolean,\n httpRouterService?: HttpRouterService,\n): RequestHandler {\n let fullConfig: ProxyConfig;\n let credentialsPolicy: string;\n if (typeof config === 'string') {\n fullConfig = { target: config };\n credentialsPolicy = 'require';\n } else {\n const { credentials, ...rest } = config as any;\n fullConfig = rest;\n credentialsPolicy = credentials ?? 'require';\n }\n\n const credentialsPolicyCandidates = [\n 'require',\n 'forward',\n 'dangerously-allow-unauthenticated',\n ];\n if (!credentialsPolicyCandidates.includes(credentialsPolicy)) {\n const valid = credentialsPolicyCandidates.map(c => `'${c}'`).join(', ');\n throw new Error(\n `Unknown credentials policy '${credentialsPolicy}' for proxy route '${route}'; expected one of ${valid}`,\n );\n }\n\n if (credentialsPolicy === 'dangerously-allow-unauthenticated') {\n httpRouterService?.addAuthPolicy({\n path: route,\n allow: 'unauthenticated',\n });\n }\n\n // Validate that target is a valid URL.\n const targetType = typeof fullConfig.target;\n if (targetType !== 'string') {\n throw new Error(\n `Proxy target for route \"${route}\" must be a string, but is of type ${targetType}`,\n );\n }\n try {\n // eslint-disable-next-line no-new\n new URL(fullConfig.target! as string);\n } catch {\n throw new Error(\n `Proxy target is not a valid URL: ${fullConfig.target ?? ''}`,\n );\n }\n\n // Default is to do a path rewrite that strips out the proxy's path prefix\n // and the rest of the route.\n if (fullConfig.pathRewrite === undefined) {\n let routeWithSlash = route.endsWith('/') ? route : `${route}/`;\n\n if (!pathPrefix.endsWith('/') && !routeWithSlash.startsWith('/')) {\n // Need to insert a / between pathPrefix and routeWithSlash\n routeWithSlash = `/${routeWithSlash}`;\n } else if (pathPrefix.endsWith('/') && routeWithSlash.startsWith('/')) {\n // Never expect this to happen at this point in time as\n // pathPrefix is set using `getExternalBaseUrl` which \"Returns the\n // external HTTP base backend URL for a given plugin,\n // **without a trailing slash.**\". But in case this changes in future, we\n // need to drop a / on either pathPrefix or routeWithSlash\n routeWithSlash = routeWithSlash.substring(1);\n }\n\n // The ? makes the slash optional for the rewrite, so that a base path without an ending slash\n // will also be matched (e.g. '/sample' and then requesting just '/api/proxy/sample' without an\n // ending slash). Otherwise the target gets called with the full '/api/proxy/sample' path\n // appended.\n fullConfig.pathRewrite = {\n [`^${pathPrefix}${routeWithSlash}?`]: '/',\n };\n }\n\n // Default is to update the Host header to the target\n if (fullConfig.changeOrigin === undefined) {\n fullConfig.changeOrigin = true;\n }\n\n // Attach the logger to the proxy config\n fullConfig.logProvider = () => logger;\n // http-proxy-middleware uses this log level to check if it should log the\n // requests that it proxies. Setting this to the most verbose log level\n // ensures that it always logs these requests. Our logger ends up deciding\n // if the logs are displayed or not.\n fullConfig.logLevel = 'debug';\n\n // Only return the allowed HTTP headers to not forward unwanted secret headers\n const requestHeaderAllowList = new Set<string>(\n [\n // allow all safe headers\n ...safeForwardHeaders,\n\n // allow all headers that are set by the proxy\n ...((fullConfig.headers && Object.keys(fullConfig.headers)) || []),\n\n // allow all configured headers\n ...(fullConfig.allowedHeaders || []),\n ].map(h => h.toLocaleLowerCase()),\n );\n\n if (credentialsPolicy === 'forward') {\n requestHeaderAllowList.add('authorization');\n }\n\n // Use the custom middleware filter to do two things:\n // 1. Remove any headers not in the allow list to stop them being forwarded\n // 2. Only permit the allowed HTTP methods if configured\n //\n // We are filtering the proxy request headers here rather than in\n // `onProxyReq` because when global-agent is enabled then `onProxyReq`\n // fires _after_ the agent has already sent the headers to the proxy\n // target, causing a ERR_HTTP_HEADERS_SENT crash\n const filter = (_pathname: string, req: http.IncomingMessage): boolean => {\n const headerNames = Object.keys(req.headers);\n headerNames.forEach(h => {\n if (!requestHeaderAllowList.has(h.toLocaleLowerCase())) {\n delete req.headers[h];\n }\n });\n\n return fullConfig?.allowedMethods?.includes(req.method!) ?? true;\n };\n // Makes http-proxy-middleware logs look nicer and include the mount path\n filter.toString = () => route;\n\n // Only forward the allowed HTTP headers to not forward unwanted secret headers\n const responseHeaderAllowList = new Set<string>(\n [\n // allow all safe headers\n ...safeForwardHeaders,\n\n // allow all configured headers\n ...(fullConfig.allowedHeaders || []),\n ].map(h => h.toLocaleLowerCase()),\n );\n\n // only forward the allowed headers in backend->client\n fullConfig.onProxyRes = (proxyRes: http.IncomingMessage) => {\n const headerNames = Object.keys(proxyRes.headers);\n\n headerNames.forEach(h => {\n if (!responseHeaderAllowList.has(h.toLocaleLowerCase())) {\n delete proxyRes.headers[h];\n }\n });\n };\n\n if (reviveConsumedRequestBodies) {\n fullConfig.onProxyReq = fixRequestBody;\n }\n\n return createProxyMiddleware(filter, fullConfig);\n}\n\nfunction readProxyConfig(config: Config, logger: Logger): JsonObject {\n const endpoints = config\n .getOptionalConfig('proxy.endpoints')\n ?.get<JsonObject>();\n if (endpoints) {\n return endpoints;\n }\n\n const root = config.getOptionalConfig('proxy')?.get<JsonObject>();\n if (!root) {\n return {};\n }\n\n const rootEndpoints = Object.fromEntries(\n Object.entries(root).filter(([key]) => key.startsWith('/')),\n );\n if (Object.keys(rootEndpoints).length === 0) {\n return {};\n }\n\n logger.warn(\n \"Configuring proxy endpoints in the root 'proxy' configuration is deprecated. Move this configuration to 'proxy.endpoints' instead.\",\n );\n\n return rootEndpoints;\n}\n\n/**\n * Creates a new\n * {@link https://expressjs.com/en/api.html#router | \"express router\"} that\n * proxies each target configured under the `proxy.endpoints` key of the config.\n *\n * @remarks\n *\n * Example configuration:\n *\n * ```yaml\n * proxy:\n * endpoints:\n * # Option 1: Simple URL String\n * simple-example: http://simple.example.com:8080\n * # Option 2: `http-proxy-middleware` compatible object\n * '/larger-example/v1':\n * target: http://larger.example.com:8080/svc.v1\n * headers:\n * Authorization: Bearer ${EXAMPLE_AUTH_TOKEN}\n * ```\n *\n * @see https://backstage.io/docs/plugins/proxying\n * @public\n */\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n return createRouterInternal(options);\n}\n\nexport async function createRouterInternal(\n options: RouterOptions & { httpRouterService?: HttpRouterService },\n): Promise<express.Router> {\n const router = Router();\n let currentRouter = Router();\n\n const skipInvalidProxies =\n options.skipInvalidProxies ??\n options.config.getOptionalBoolean('proxy.skipInvalidProxies') ??\n false;\n const reviveConsumedRequestBodies =\n options.reviveConsumedRequestBodies ??\n options.config.getOptionalBoolean('proxy.reviveConsumedRequestBodies') ??\n false;\n const proxyOptions = {\n skipInvalidProxies,\n reviveConsumedRequestBodies,\n logger: options.logger,\n };\n\n const externalUrl = await options.discovery.getExternalBaseUrl('proxy');\n const { pathname: pathPrefix } = new URL(externalUrl);\n\n const proxyConfig = readProxyConfig(options.config, options.logger);\n configureMiddlewares(\n proxyOptions,\n currentRouter,\n pathPrefix,\n proxyConfig,\n options.httpRouterService,\n );\n router.use((...args) => currentRouter(...args));\n\n if (options.config.subscribe) {\n let currentKey = JSON.stringify(proxyConfig);\n\n options.config.subscribe(() => {\n const newProxyConfig = readProxyConfig(options.config, options.logger);\n const newKey = JSON.stringify(newProxyConfig);\n\n if (currentKey !== newKey) {\n currentKey = newKey;\n currentRouter = Router();\n configureMiddlewares(\n proxyOptions,\n currentRouter,\n pathPrefix,\n newProxyConfig,\n options.httpRouterService,\n );\n }\n });\n }\n\n options.httpRouterService?.use(router);\n return router;\n}\n\nfunction configureMiddlewares(\n options: {\n reviveConsumedRequestBodies: boolean;\n skipInvalidProxies: boolean;\n logger: Logger;\n },\n router: express.Router,\n pathPrefix: string,\n proxyConfig: any,\n httpRouterService?: HttpRouterService,\n) {\n Object.entries<any>(proxyConfig).forEach(([route, proxyRouteConfig]) => {\n try {\n router.use(\n route,\n buildMiddleware(\n pathPrefix,\n options.logger,\n route,\n proxyRouteConfig,\n options.reviveConsumedRequestBodies,\n httpRouterService,\n ),\n );\n } catch (e) {\n if (options.skipInvalidProxies) {\n options.logger.warn(`skipped configuring ${route} due to ${e.message}`);\n } else {\n throw e;\n }\n }\n });\n}\n"],"names":["fixRequestBody","createProxyMiddleware","Router"],"mappings":";;;;;;;;;AAgCA,MAAM,kBAAqB,GAAA;AAAA;AAAA,EAEzB,eAAA;AAAA,EACA,kBAAA;AAAA,EACA,gBAAA;AAAA,EACA,cAAA;AAAA,EACA,SAAA;AAAA,EACA,eAAA;AAAA,EACA,QAAA;AAAA;AAAA;AAAA,EAIA,MAAA;AAAA;AAAA,EAGA,QAAA;AAAA,EACA,iBAAA;AAAA,EACA,YAAA;AACF,CAAA,CAAA;AAmBO,SAAS,gBACd,UACA,EAAA,MAAA,EACA,KACA,EAAA,MAAA,EACA,6BACA,iBACgB,EAAA;AAChB,EAAI,IAAA,UAAA,CAAA;AACJ,EAAI,IAAA,iBAAA,CAAA;AACJ,EAAI,IAAA,OAAO,WAAW,QAAU,EAAA;AAC9B,IAAa,UAAA,GAAA,EAAE,QAAQ,MAAO,EAAA,CAAA;AAC9B,IAAoB,iBAAA,GAAA,SAAA,CAAA;AAAA,GACf,MAAA;AACL,IAAA,MAAM,EAAE,WAAA,EAAa,GAAG,IAAA,EAAS,GAAA,MAAA,CAAA;AACjC,IAAa,UAAA,GAAA,IAAA,CAAA;AACb,IAAA,iBAAA,GAAoB,WAAe,IAAA,SAAA,CAAA;AAAA,GACrC;AAEA,EAAA,MAAM,2BAA8B,GAAA;AAAA,IAClC,SAAA;AAAA,IACA,SAAA;AAAA,IACA,mCAAA;AAAA,GACF,CAAA;AACA,EAAA,IAAI,CAAC,2BAAA,CAA4B,QAAS,CAAA,iBAAiB,CAAG,EAAA;AAC5D,IAAM,MAAA,KAAA,GAAQ,4BAA4B,GAAI,CAAA,CAAA,CAAA,KAAK,IAAI,CAAC,CAAA,CAAA,CAAG,CAAE,CAAA,IAAA,CAAK,IAAI,CAAA,CAAA;AACtE,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAA+B,4BAAA,EAAA,iBAAiB,CAAsB,mBAAA,EAAA,KAAK,sBAAsB,KAAK,CAAA,CAAA;AAAA,KACxG,CAAA;AAAA,GACF;AAEA,EAAA,IAAI,sBAAsB,mCAAqC,EAAA;AAC7D,IAAA,iBAAA,EAAmB,aAAc,CAAA;AAAA,MAC/B,IAAM,EAAA,KAAA;AAAA,MACN,KAAO,EAAA,iBAAA;AAAA,KACR,CAAA,CAAA;AAAA,GACH;AAGA,EAAM,MAAA,UAAA,GAAa,OAAO,UAAW,CAAA,MAAA,CAAA;AACrC,EAAA,IAAI,eAAe,QAAU,EAAA;AAC3B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,wBAAA,EAA2B,KAAK,CAAA,mCAAA,EAAsC,UAAU,CAAA,CAAA;AAAA,KAClF,CAAA;AAAA,GACF;AACA,EAAI,IAAA;AAEF,IAAI,IAAA,GAAA,CAAI,WAAW,MAAiB,CAAA,CAAA;AAAA,GAC9B,CAAA,MAAA;AACN,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,iCAAA,EAAoC,UAAW,CAAA,MAAA,IAAU,EAAE,CAAA,CAAA;AAAA,KAC7D,CAAA;AAAA,GACF;AAIA,EAAI,IAAA,UAAA,CAAW,gBAAgB,KAAW,CAAA,EAAA;AACxC,IAAA,IAAI,iBAAiB,KAAM,CAAA,QAAA,CAAS,GAAG,CAAI,GAAA,KAAA,GAAQ,GAAG,KAAK,CAAA,CAAA,CAAA,CAAA;AAE3D,IAAI,IAAA,CAAC,WAAW,QAAS,CAAA,GAAG,KAAK,CAAC,cAAA,CAAe,UAAW,CAAA,GAAG,CAAG,EAAA;AAEhE,MAAA,cAAA,GAAiB,IAAI,cAAc,CAAA,CAAA,CAAA;AAAA,KACrC,MAAA,IAAW,WAAW,QAAS,CAAA,GAAG,KAAK,cAAe,CAAA,UAAA,CAAW,GAAG,CAAG,EAAA;AAMrE,MAAiB,cAAA,GAAA,cAAA,CAAe,UAAU,CAAC,CAAA,CAAA;AAAA,KAC7C;AAMA,IAAA,UAAA,CAAW,WAAc,GAAA;AAAA,MACvB,CAAC,CAAI,CAAA,EAAA,UAAU,CAAG,EAAA,cAAc,GAAG,GAAG,GAAA;AAAA,KACxC,CAAA;AAAA,GACF;AAGA,EAAI,IAAA,UAAA,CAAW,iBAAiB,KAAW,CAAA,EAAA;AACzC,IAAA,UAAA,CAAW,YAAe,GAAA,IAAA,CAAA;AAAA,GAC5B;AAGA,EAAA,UAAA,CAAW,cAAc,MAAM,MAAA,CAAA;AAK/B,EAAA,UAAA,CAAW,QAAW,GAAA,OAAA,CAAA;AAGtB,EAAA,MAAM,yBAAyB,IAAI,GAAA;AAAA,IACjC;AAAA;AAAA,MAEE,GAAG,kBAAA;AAAA;AAAA,MAGH,GAAK,WAAW,OAAW,IAAA,MAAA,CAAO,KAAK,UAAW,CAAA,OAAO,KAAM,EAAC;AAAA;AAAA,MAGhE,GAAI,UAAW,CAAA,cAAA,IAAkB,EAAC;AAAA,KAClC,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA,CAAA,CAAE,mBAAmB,CAAA;AAAA,GAClC,CAAA;AAEA,EAAA,IAAI,sBAAsB,SAAW,EAAA;AACnC,IAAA,sBAAA,CAAuB,IAAI,eAAe,CAAA,CAAA;AAAA,GAC5C;AAUA,EAAM,MAAA,MAAA,GAAS,CAAC,SAAA,EAAmB,GAAuC,KAAA;AACxE,IAAA,MAAM,WAAc,GAAA,MAAA,CAAO,IAAK,CAAA,GAAA,CAAI,OAAO,CAAA,CAAA;AAC3C,IAAA,WAAA,CAAY,QAAQ,CAAK,CAAA,KAAA;AACvB,MAAA,IAAI,CAAC,sBAAuB,CAAA,GAAA,CAAI,CAAE,CAAA,iBAAA,EAAmB,CAAG,EAAA;AACtD,QAAO,OAAA,GAAA,CAAI,QAAQ,CAAC,CAAA,CAAA;AAAA,OACtB;AAAA,KACD,CAAA,CAAA;AAED,IAAA,OAAO,UAAY,EAAA,cAAA,EAAgB,QAAS,CAAA,GAAA,CAAI,MAAO,CAAK,IAAA,IAAA,CAAA;AAAA,GAC9D,CAAA;AAEA,EAAA,MAAA,CAAO,WAAW,MAAM,KAAA,CAAA;AAGxB,EAAA,MAAM,0BAA0B,IAAI,GAAA;AAAA,IAClC;AAAA;AAAA,MAEE,GAAG,kBAAA;AAAA;AAAA,MAGH,GAAI,UAAW,CAAA,cAAA,IAAkB,EAAC;AAAA,KAClC,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA,CAAA,CAAE,mBAAmB,CAAA;AAAA,GAClC,CAAA;AAGA,EAAW,UAAA,CAAA,UAAA,GAAa,CAAC,QAAmC,KAAA;AAC1D,IAAA,MAAM,WAAc,GAAA,MAAA,CAAO,IAAK,CAAA,QAAA,CAAS,OAAO,CAAA,CAAA;AAEhD,IAAA,WAAA,CAAY,QAAQ,CAAK,CAAA,KAAA;AACvB,MAAA,IAAI,CAAC,uBAAwB,CAAA,GAAA,CAAI,CAAE,CAAA,iBAAA,EAAmB,CAAG,EAAA;AACvD,QAAO,OAAA,QAAA,CAAS,QAAQ,CAAC,CAAA,CAAA;AAAA,OAC3B;AAAA,KACD,CAAA,CAAA;AAAA,GACH,CAAA;AAEA,EAAA,IAAI,2BAA6B,EAAA;AAC/B,IAAA,UAAA,CAAW,UAAa,GAAAA,kCAAA,CAAA;AAAA,GAC1B;AAEA,EAAO,OAAAC,yCAAA,CAAsB,QAAQ,UAAU,CAAA,CAAA;AACjD,CAAA;AAEA,SAAS,eAAA,CAAgB,QAAgB,MAA4B,EAAA;AACnE,EAAA,MAAM,SAAY,GAAA,MAAA,CACf,iBAAkB,CAAA,iBAAiB,GAClC,GAAgB,EAAA,CAAA;AACpB,EAAA,IAAI,SAAW,EAAA;AACb,IAAO,OAAA,SAAA,CAAA;AAAA,GACT;AAEA,EAAA,MAAM,IAAO,GAAA,MAAA,CAAO,iBAAkB,CAAA,OAAO,GAAG,GAAgB,EAAA,CAAA;AAChE,EAAA,IAAI,CAAC,IAAM,EAAA;AACT,IAAA,OAAO,EAAC,CAAA;AAAA,GACV;AAEA,EAAA,MAAM,gBAAgB,MAAO,CAAA,WAAA;AAAA,IAC3B,MAAO,CAAA,OAAA,CAAQ,IAAI,CAAA,CAAE,MAAO,CAAA,CAAC,CAAC,GAAG,CAAM,KAAA,GAAA,CAAI,UAAW,CAAA,GAAG,CAAC,CAAA;AAAA,GAC5D,CAAA;AACA,EAAA,IAAI,MAAO,CAAA,IAAA,CAAK,aAAa,CAAA,CAAE,WAAW,CAAG,EAAA;AAC3C,IAAA,OAAO,EAAC,CAAA;AAAA,GACV;AAEA,EAAO,MAAA,CAAA,IAAA;AAAA,IACL,oIAAA;AAAA,GACF,CAAA;AAEA,EAAO,OAAA,aAAA,CAAA;AACT,CAAA;AA0BA,eAAsB,aACpB,OACyB,EAAA;AACzB,EAAA,OAAO,qBAAqB,OAAO,CAAA,CAAA;AACrC,CAAA;AAEA,eAAsB,qBACpB,OACyB,EAAA;AACzB,EAAA,MAAM,SAASC,uBAAO,EAAA,CAAA;AACtB,EAAA,IAAI,gBAAgBA,uBAAO,EAAA,CAAA;AAE3B,EAAA,MAAM,qBACJ,OAAQ,CAAA,kBAAA,IACR,QAAQ,MAAO,CAAA,kBAAA,CAAmB,0BAA0B,CAC5D,IAAA,KAAA,CAAA;AACF,EAAA,MAAM,8BACJ,OAAQ,CAAA,2BAAA,IACR,QAAQ,MAAO,CAAA,kBAAA,CAAmB,mCAAmC,CACrE,IAAA,KAAA,CAAA;AACF,EAAA,MAAM,YAAe,GAAA;AAAA,IACnB,kBAAA;AAAA,IACA,2BAAA;AAAA,IACA,QAAQ,OAAQ,CAAA,MAAA;AAAA,GAClB,CAAA;AAEA,EAAA,MAAM,WAAc,GAAA,MAAM,OAAQ,CAAA,SAAA,CAAU,mBAAmB,OAAO,CAAA,CAAA;AACtE,EAAA,MAAM,EAAE,QAAU,EAAA,UAAA,EAAe,GAAA,IAAI,IAAI,WAAW,CAAA,CAAA;AAEpD,EAAA,MAAM,WAAc,GAAA,eAAA,CAAgB,OAAQ,CAAA,MAAA,EAAQ,QAAQ,MAAM,CAAA,CAAA;AAClE,EAAA,oBAAA;AAAA,IACE,YAAA;AAAA,IACA,aAAA;AAAA,IACA,UAAA;AAAA,IACA,WAAA;AAAA,IACA,OAAQ,CAAA,iBAAA;AAAA,GACV,CAAA;AACA,EAAA,MAAA,CAAO,IAAI,CAAI,GAAA,IAAA,KAAS,aAAc,CAAA,GAAG,IAAI,CAAC,CAAA,CAAA;AAE9C,EAAI,IAAA,OAAA,CAAQ,OAAO,SAAW,EAAA;AAC5B,IAAI,IAAA,UAAA,GAAa,IAAK,CAAA,SAAA,CAAU,WAAW,CAAA,CAAA;AAE3C,IAAQ,OAAA,CAAA,MAAA,CAAO,UAAU,MAAM;AAC7B,MAAA,MAAM,cAAiB,GAAA,eAAA,CAAgB,OAAQ,CAAA,MAAA,EAAQ,QAAQ,MAAM,CAAA,CAAA;AACrE,MAAM,MAAA,MAAA,GAAS,IAAK,CAAA,SAAA,CAAU,cAAc,CAAA,CAAA;AAE5C,MAAA,IAAI,eAAe,MAAQ,EAAA;AACzB,QAAa,UAAA,GAAA,MAAA,CAAA;AACb,QAAA,aAAA,GAAgBA,uBAAO,EAAA,CAAA;AACvB,QAAA,oBAAA;AAAA,UACE,YAAA;AAAA,UACA,aAAA;AAAA,UACA,UAAA;AAAA,UACA,cAAA;AAAA,UACA,OAAQ,CAAA,iBAAA;AAAA,SACV,CAAA;AAAA,OACF;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AAEA,EAAQ,OAAA,CAAA,iBAAA,EAAmB,IAAI,MAAM,CAAA,CAAA;AACrC,EAAO,OAAA,MAAA,CAAA;AACT,CAAA;AAEA,SAAS,oBACP,CAAA,OAAA,EAKA,MACA,EAAA,UAAA,EACA,aACA,iBACA,EAAA;AACA,EAAO,MAAA,CAAA,OAAA,CAAa,WAAW,CAAE,CAAA,OAAA,CAAQ,CAAC,CAAC,KAAA,EAAO,gBAAgB,CAAM,KAAA;AACtE,IAAI,IAAA;AACF,MAAO,MAAA,CAAA,GAAA;AAAA,QACL,KAAA;AAAA,QACA,eAAA;AAAA,UACE,UAAA;AAAA,UACA,OAAQ,CAAA,MAAA;AAAA,UACR,KAAA;AAAA,UACA,gBAAA;AAAA,UACA,OAAQ,CAAA,2BAAA;AAAA,UACR,iBAAA;AAAA,SACF;AAAA,OACF,CAAA;AAAA,aACO,CAAG,EAAA;AACV,MAAA,IAAI,QAAQ,kBAAoB,EAAA;AAC9B,QAAA,OAAA,CAAQ,OAAO,IAAK,CAAA,CAAA,oBAAA,EAAuB,KAAK,CAAW,QAAA,EAAA,CAAA,CAAE,OAAO,CAAE,CAAA,CAAA,CAAA;AAAA,OACjE,MAAA;AACL,QAAM,MAAA,CAAA,CAAA;AAAA,OACR;AAAA,KACF;AAAA,GACD,CAAA,CAAA;AACH;;;;;"}
package/dist/index.cjs.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var router = require('./cjs/router-D6Z1qlfG.cjs.js');
3
+ var router = require('./cjs/router-BB9HHp8x.cjs.js');
4
4
  require('express-promise-router');
5
5
  require('http-proxy-middleware');
6
6
 
package/dist/index.d.ts CHANGED
@@ -12,20 +12,26 @@ interface RouterOptions {
12
12
  reviveConsumedRequestBodies?: boolean;
13
13
  }
14
14
  /**
15
- * Creates a new {@link https://expressjs.com/en/api.html#router | "express router"} that proxy each target configured under the `proxy` key of the config
16
- * @example
17
- * ```ts
18
- * let router = await createRouter({logger, config, discovery});
19
- * ```
20
- * @config
15
+ * Creates a new
16
+ * {@link https://expressjs.com/en/api.html#router | "express router"} that
17
+ * proxies each target configured under the `proxy.endpoints` key of the config.
18
+ *
19
+ * @remarks
20
+ *
21
+ * Example configuration:
22
+ *
21
23
  * ```yaml
22
24
  * proxy:
23
- * simple-example: http://simple.example.com:8080 # Opt 1 Simple URL String
24
- * '/larger-example/v1': # Opt 2 `http-proxy-middleware` compatible object
25
- * target: http://larger.example.com:8080/svc.v1
26
- * headers:
27
- * Authorization: Bearer ${EXAMPLE_AUTH_TOKEN}
28
- *```
25
+ * endpoints:
26
+ * # Option 1: Simple URL String
27
+ * simple-example: http://simple.example.com:8080
28
+ * # Option 2: `http-proxy-middleware` compatible object
29
+ * '/larger-example/v1':
30
+ * target: http://larger.example.com:8080/svc.v1
31
+ * headers:
32
+ * Authorization: Bearer ${EXAMPLE_AUTH_TOKEN}
33
+ * ```
34
+ *
29
35
  * @see https://backstage.io/docs/plugins/proxying
30
36
  * @public
31
37
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-proxy-backend",
3
- "version": "0.4.16",
3
+ "version": "0.5.0-next.0",
4
4
  "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend",
5
5
  "backstage": {
6
6
  "role": "backend-plugin"
@@ -48,9 +48,10 @@
48
48
  "test": "backstage-cli package test"
49
49
  },
50
50
  "dependencies": {
51
- "@backstage/backend-common": "^0.22.0",
52
- "@backstage/backend-plugin-api": "^0.6.18",
51
+ "@backstage/backend-common": "^0.22.1-next.0",
52
+ "@backstage/backend-plugin-api": "^0.6.19-next.0",
53
53
  "@backstage/config": "^1.2.0",
54
+ "@backstage/types": "^1.1.1",
54
55
  "@types/express": "^4.17.6",
55
56
  "express": "^4.17.1",
56
57
  "express-promise-router": "^4.1.0",
@@ -63,14 +64,18 @@
63
64
  "yup": "^1.0.0"
64
65
  },
65
66
  "devDependencies": {
66
- "@backstage/backend-test-utils": "^0.3.8",
67
- "@backstage/cli": "^0.26.5",
67
+ "@backstage/backend-defaults": "^0.2.19-next.0",
68
+ "@backstage/backend-test-utils": "^0.3.9-next.0",
69
+ "@backstage/cli": "^0.26.6-next.0",
68
70
  "@backstage/config-loader": "^1.8.0",
71
+ "@backstage/errors": "^1.2.4",
69
72
  "@types/http-proxy-middleware": "^1.0.0",
70
73
  "@types/supertest": "^2.0.8",
71
74
  "@types/uuid": "^9.0.0",
72
75
  "@types/yup": "^0.32.0",
73
76
  "msw": "^1.0.0",
77
+ "node-fetch": "^2.6.7",
78
+ "portfinder": "^1.0.32",
74
79
  "supertest": "^6.1.3"
75
80
  },
76
81
  "configSchema": "config.d.ts"
@@ -1 +0,0 @@
1
- {"version":3,"file":"router-D6Z1qlfG.cjs.js","sources":["../../src/service/router.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 { Config } from '@backstage/config';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport {\n createProxyMiddleware,\n fixRequestBody,\n Options,\n RequestHandler,\n} from 'http-proxy-middleware';\nimport { Logger } from 'winston';\nimport http from 'http';\nimport { PluginEndpointDiscovery } from '@backstage/backend-common';\n\n// A list of headers that are always forwarded to the proxy targets.\nconst safeForwardHeaders = [\n // https://fetch.spec.whatwg.org/#cors-safelisted-request-header\n 'cache-control',\n 'content-language',\n 'content-length',\n 'content-type',\n 'expires',\n 'last-modified',\n 'pragma',\n\n // host is overridden by default. if changeOrigin is configured to false,\n // we assume this is a intentional and should also be forwarded.\n 'host',\n\n // other headers that we assume to be ok\n 'accept',\n 'accept-language',\n 'user-agent',\n];\n\n/** @public */\nexport interface RouterOptions {\n logger: Logger;\n config: Config;\n discovery: PluginEndpointDiscovery;\n skipInvalidProxies?: boolean;\n reviveConsumedRequestBodies?: boolean;\n}\n\nexport interface ProxyConfig extends Options {\n allowedMethods?: string[];\n allowedHeaders?: string[];\n reviveRequestBody?: boolean;\n}\n\n// Creates a proxy middleware, possibly with defaults added on top of the\n// given config.\nexport function buildMiddleware(\n pathPrefix: string,\n logger: Logger,\n route: string,\n config: string | ProxyConfig,\n reviveConsumedRequestBodies?: boolean,\n): RequestHandler {\n const fullConfig =\n typeof config === 'string' ? { target: config } : { ...config };\n\n // Validate that target is a valid URL.\n const targetType = typeof fullConfig.target;\n if (targetType !== 'string') {\n throw new Error(\n `Proxy target for route \"${route}\" must be a string, but is of type ${targetType}`,\n );\n }\n try {\n // eslint-disable-next-line no-new\n new URL(fullConfig.target! as string);\n } catch {\n throw new Error(\n `Proxy target is not a valid URL: ${fullConfig.target ?? ''}`,\n );\n }\n\n // Default is to do a path rewrite that strips out the proxy's path prefix\n // and the rest of the route.\n if (fullConfig.pathRewrite === undefined) {\n let routeWithSlash = route.endsWith('/') ? route : `${route}/`;\n\n if (!pathPrefix.endsWith('/') && !routeWithSlash.startsWith('/')) {\n // Need to insert a / between pathPrefix and routeWithSlash\n routeWithSlash = `/${routeWithSlash}`;\n } else if (pathPrefix.endsWith('/') && routeWithSlash.startsWith('/')) {\n // Never expect this to happen at this point in time as\n // pathPrefix is set using `getExternalBaseUrl` which \"Returns the\n // external HTTP base backend URL for a given plugin,\n // **without a trailing slash.**\". But in case this changes in future, we\n // need to drop a / on either pathPrefix or routeWithSlash\n routeWithSlash = routeWithSlash.substring(1);\n }\n\n // The ? makes the slash optional for the rewrite, so that a base path without an ending slash\n // will also be matched (e.g. '/sample' and then requesting just '/api/proxy/sample' without an\n // ending slash). Otherwise the target gets called with the full '/api/proxy/sample' path\n // appended.\n fullConfig.pathRewrite = {\n [`^${pathPrefix}${routeWithSlash}?`]: '/',\n };\n }\n\n // Default is to update the Host header to the target\n if (fullConfig.changeOrigin === undefined) {\n fullConfig.changeOrigin = true;\n }\n\n // Attach the logger to the proxy config\n fullConfig.logProvider = () => logger;\n // http-proxy-middleware uses this log level to check if it should log the\n // requests that it proxies. Setting this to the most verbose log level\n // ensures that it always logs these requests. Our logger ends up deciding\n // if the logs are displayed or not.\n fullConfig.logLevel = 'debug';\n\n // Only return the allowed HTTP headers to not forward unwanted secret headers\n const requestHeaderAllowList = new Set<string>(\n [\n // allow all safe headers\n ...safeForwardHeaders,\n\n // allow all headers that are set by the proxy\n ...((fullConfig.headers && Object.keys(fullConfig.headers)) || []),\n\n // allow all configured headers\n ...(fullConfig.allowedHeaders || []),\n ].map(h => h.toLocaleLowerCase()),\n );\n\n // Use the custom middleware filter to do two things:\n // 1. Remove any headers not in the allow list to stop them being forwarded\n // 2. Only permit the allowed HTTP methods if configured\n //\n // We are filtering the proxy request headers here rather than in\n // `onProxyReq` because when global-agent is enabled then `onProxyReq`\n // fires _after_ the agent has already sent the headers to the proxy\n // target, causing a ERR_HTTP_HEADERS_SENT crash\n const filter = (_pathname: string, req: http.IncomingMessage): boolean => {\n const headerNames = Object.keys(req.headers);\n headerNames.forEach(h => {\n if (!requestHeaderAllowList.has(h.toLocaleLowerCase())) {\n delete req.headers[h];\n }\n });\n\n return fullConfig?.allowedMethods?.includes(req.method!) ?? true;\n };\n // Makes http-proxy-middleware logs look nicer and include the mount path\n filter.toString = () => route;\n\n // Only forward the allowed HTTP headers to not forward unwanted secret headers\n const responseHeaderAllowList = new Set<string>(\n [\n // allow all safe headers\n ...safeForwardHeaders,\n\n // allow all configured headers\n ...(fullConfig.allowedHeaders || []),\n ].map(h => h.toLocaleLowerCase()),\n );\n\n // only forward the allowed headers in backend->client\n fullConfig.onProxyRes = (proxyRes: http.IncomingMessage) => {\n const headerNames = Object.keys(proxyRes.headers);\n\n headerNames.forEach(h => {\n if (!responseHeaderAllowList.has(h.toLocaleLowerCase())) {\n delete proxyRes.headers[h];\n }\n });\n };\n\n if (reviveConsumedRequestBodies) {\n fullConfig.onProxyReq = fixRequestBody;\n }\n\n return createProxyMiddleware(filter, fullConfig);\n}\n\nfunction readProxyConfig(config: Config, logger: Logger): unknown {\n const endpoints = config.getOptionalConfig('proxy.endpoints')?.get();\n if (endpoints) {\n return endpoints;\n }\n\n const root = config.getOptionalConfig('proxy')?.get();\n if (!root) {\n return {};\n }\n\n const rootEndpoints = Object.fromEntries(\n Object.entries(root).filter(([key]) => key.startsWith('/')),\n );\n if (Object.keys(rootEndpoints).length === 0) {\n return {};\n }\n\n logger.warn(\n \"Configuring proxy endpoints in the root 'proxy' configuration is deprecated. Move this configuration to 'proxy.endpoints' instead.\",\n );\n\n return rootEndpoints;\n}\n\n/**\n * Creates a new {@link https://expressjs.com/en/api.html#router | \"express router\"} that proxy each target configured under the `proxy` key of the config\n * @example\n * ```ts\n * let router = await createRouter({logger, config, discovery});\n * ```\n * @config\n * ```yaml\n * proxy:\n * simple-example: http://simple.example.com:8080 # Opt 1 Simple URL String\n * '/larger-example/v1': # Opt 2 `http-proxy-middleware` compatible object\n * target: http://larger.example.com:8080/svc.v1\n * headers:\n * Authorization: Bearer ${EXAMPLE_AUTH_TOKEN}\n *```\n * @see https://backstage.io/docs/plugins/proxying\n * @public\n */\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const router = Router();\n let currentRouter = Router();\n\n const skipInvalidProxies =\n options.skipInvalidProxies ??\n options.config.getOptionalBoolean('proxy.skipInvalidProxies') ??\n false;\n const reviveConsumedRequestBodies =\n options.reviveConsumedRequestBodies ??\n options.config.getOptionalBoolean('proxy.reviveConsumedRequestBodies') ??\n false;\n const proxyOptions = {\n skipInvalidProxies,\n reviveConsumedRequestBodies,\n logger: options.logger,\n };\n\n const externalUrl = await options.discovery.getExternalBaseUrl('proxy');\n const { pathname: pathPrefix } = new URL(externalUrl);\n\n const proxyConfig = readProxyConfig(options.config, options.logger);\n configureMiddlewares(proxyOptions, currentRouter, pathPrefix, proxyConfig);\n router.use((...args) => currentRouter(...args));\n\n if (options.config.subscribe) {\n let currentKey = JSON.stringify(proxyConfig);\n\n options.config.subscribe(() => {\n const newProxyConfig = readProxyConfig(options.config, options.logger);\n const newKey = JSON.stringify(newProxyConfig);\n\n if (currentKey !== newKey) {\n currentKey = newKey;\n currentRouter = Router();\n configureMiddlewares(\n proxyOptions,\n currentRouter,\n pathPrefix,\n newProxyConfig,\n );\n }\n });\n }\n\n return router;\n}\n\nfunction configureMiddlewares(\n options: {\n reviveConsumedRequestBodies: boolean;\n skipInvalidProxies: boolean;\n logger: Logger;\n },\n router: express.Router,\n pathPrefix: string,\n proxyConfig: any,\n) {\n Object.entries<any>(proxyConfig).forEach(([route, proxyRouteConfig]) => {\n try {\n router.use(\n route,\n buildMiddleware(\n pathPrefix,\n options.logger,\n route,\n proxyRouteConfig,\n options.reviveConsumedRequestBodies,\n ),\n );\n } catch (e) {\n if (options.skipInvalidProxies) {\n options.logger.warn(`skipped configuring ${route} due to ${e.message}`);\n } else {\n throw e;\n }\n }\n });\n}\n"],"names":["_a","fixRequestBody","createProxyMiddleware","Router"],"mappings":"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"}