@verdaccio/middleware 8.0.0-next-8.10 → 8.0.0-next-8.11

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,17 @@
1
1
  # @verdaccio/middleware
2
2
 
3
+ ## 8.0.0-next-8.11
4
+
5
+ ### Patch Changes
6
+
7
+ - 4110873: chore(middleware): improve loop detection
8
+ - 66bc284: chore: middleware package update
9
+ - Updated dependencies [85e0e13]
10
+ - @verdaccio/utils@8.1.0-next-8.11
11
+ - @verdaccio/config@8.0.0-next-8.11
12
+ - @verdaccio/core@8.0.0-next-8.11
13
+ - @verdaccio/url@13.0.0-next-8.11
14
+
3
15
  ## 8.0.0-next-8.10
4
16
 
5
17
  ### Patch Changes
@@ -16,13 +16,24 @@ function antiLoop(config) {
16
16
  const arr = req.get('via')?.split(',');
17
17
  if (Array.isArray(arr)) {
18
18
  for (let i = 0; i < arr.length; i++) {
19
- // the "via" header must contains an specific headers, this has to be on sync
19
+ // the "via" header must contain a specific value, this has to be in sync
20
20
  // with the proxy request
21
21
  // match eg: Server 1 or Server 2
22
- // TODO: improve this RegEX
23
- const m = arr[i].trim().match(/\s*(\S+)\s+(\S+)/);
24
- if (m && m[2] === config.server_id) {
25
- return next(_core.errorUtils.getCode(_core.HTTP_STATUS.LOOP_DETECTED, 'loop detected'));
22
+
23
+ // RFC 7230: Via = 1*( "," OWS Via-value )
24
+ // Via-value = received-protocol RWS received-by [ RWS comment ]
25
+ // received-protocol = [ protocol-name "/" ] protocol-version
26
+ // received-by = ( uri-host [ ":" port ] ) / pseudonym
27
+
28
+ // Split the trimmed header value into parts
29
+ const parts = arr[i].trim().split(/\s+/);
30
+ // Check if we have at least protocol/version and received-by parts
31
+ if (parts.length >= 2) {
32
+ // Get the received-by value (server id), removing any comment
33
+ const serverId = parts[1].split('(')[0].trim();
34
+ if (serverId === config.server_id) {
35
+ return next(_core.errorUtils.getCode(_core.HTTP_STATUS.LOOP_DETECTED, 'loop detected'));
36
+ }
26
37
  }
27
38
  }
28
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"antiLoop.js","names":["_core","require","antiLoop","config","req","res","next","headers","via","arr","get","split","Array","isArray","i","length","m","trim","match","server_id","errorUtils","getCode","HTTP_STATUS","LOOP_DETECTED"],"sources":["../../src/middlewares/antiLoop.ts"],"sourcesContent":["import { HTTP_STATUS, errorUtils } from '@verdaccio/core';\nimport { Config } from '@verdaccio/types';\n\nimport { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';\n\n/**\n * A middleware that avoid a registry points itself as proxy and avoid create infinite loops.\n * @param config\n * @returns\n */\nexport function antiLoop(config: Config) {\n return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {\n if (req?.headers?.via != null) {\n const arr = req.get('via')?.split(',');\n if (Array.isArray(arr)) {\n for (let i = 0; i < arr.length; i++) {\n // the \"via\" header must contains an specific headers, this has to be on sync\n // with the proxy request\n // match eg: Server 1 or Server 2\n // TODO: improve this RegEX\n const m = arr[i].trim().match(/\\s*(\\S+)\\s+(\\S+)/);\n if (m && m[2] === config.server_id) {\n return next(errorUtils.getCode(HTTP_STATUS.LOOP_DETECTED, 'loop detected'));\n }\n }\n }\n }\n next();\n };\n}\n"],"mappings":";;;;;;AAAA,IAAAA,KAAA,GAAAC,OAAA;AAKA;AACA;AACA;AACA;AACA;AACO,SAASC,QAAQA,CAACC,MAAc,EAAE;EACvC,OAAO,UAAUC,GAAmB,EAAEC,GAAoB,EAAEC,IAAsB,EAAQ;IACxF,IAAIF,GAAG,EAAEG,OAAO,EAAEC,GAAG,IAAI,IAAI,EAAE;MAC7B,MAAMC,GAAG,GAAGL,GAAG,CAACM,GAAG,CAAC,KAAK,CAAC,EAAEC,KAAK,CAAC,GAAG,CAAC;MACtC,IAAIC,KAAK,CAACC,OAAO,CAACJ,GAAG,CAAC,EAAE;QACtB,KAAK,IAAIK,CAAC,GAAG,CAAC,EAAEA,CAAC,GAAGL,GAAG,CAACM,MAAM,EAAED,CAAC,EAAE,EAAE;UACnC;UACA;UACA;UACA;UACA,MAAME,CAAC,GAAGP,GAAG,CAACK,CAAC,CAAC,CAACG,IAAI,CAAC,CAAC,CAACC,KAAK,CAAC,kBAAkB,CAAC;UACjD,IAAIF,CAAC,IAAIA,CAAC,CAAC,CAAC,CAAC,KAAKb,MAAM,CAACgB,SAAS,EAAE;YAClC,OAAOb,IAAI,CAACc,gBAAU,CAACC,OAAO,CAACC,iBAAW,CAACC,aAAa,EAAE,eAAe,CAAC,CAAC;UAC7E;QACF;MACF;IACF;IACAjB,IAAI,CAAC,CAAC;EACR,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"file":"antiLoop.js","names":["_core","require","antiLoop","config","req","res","next","headers","via","arr","get","split","Array","isArray","i","length","parts","trim","serverId","server_id","errorUtils","getCode","HTTP_STATUS","LOOP_DETECTED"],"sources":["../../src/middlewares/antiLoop.ts"],"sourcesContent":["import { HTTP_STATUS, errorUtils } from '@verdaccio/core';\nimport { Config } from '@verdaccio/types';\n\nimport { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';\n\n/**\n * A middleware that avoid a registry points itself as proxy and avoid create infinite loops.\n * @param config\n * @returns\n */\nexport function antiLoop(config: Config) {\n return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {\n if (req?.headers?.via != null) {\n const arr = req.get('via')?.split(',');\n if (Array.isArray(arr)) {\n for (let i = 0; i < arr.length; i++) {\n // the \"via\" header must contain a specific value, this has to be in sync\n // with the proxy request\n // match eg: Server 1 or Server 2\n\n // RFC 7230: Via = 1*( \",\" OWS Via-value )\n // Via-value = received-protocol RWS received-by [ RWS comment ]\n // received-protocol = [ protocol-name \"/\" ] protocol-version\n // received-by = ( uri-host [ \":\" port ] ) / pseudonym\n\n // Split the trimmed header value into parts\n const parts = arr[i].trim().split(/\\s+/);\n // Check if we have at least protocol/version and received-by parts\n if (parts.length >= 2) {\n // Get the received-by value (server id), removing any comment\n const serverId = parts[1].split('(')[0].trim();\n if (serverId === config.server_id) {\n return next(errorUtils.getCode(HTTP_STATUS.LOOP_DETECTED, 'loop detected'));\n }\n }\n }\n }\n }\n next();\n };\n}\n"],"mappings":";;;;;;AAAA,IAAAA,KAAA,GAAAC,OAAA;AAKA;AACA;AACA;AACA;AACA;AACO,SAASC,QAAQA,CAACC,MAAc,EAAE;EACvC,OAAO,UAAUC,GAAmB,EAAEC,GAAoB,EAAEC,IAAsB,EAAQ;IACxF,IAAIF,GAAG,EAAEG,OAAO,EAAEC,GAAG,IAAI,IAAI,EAAE;MAC7B,MAAMC,GAAG,GAAGL,GAAG,CAACM,GAAG,CAAC,KAAK,CAAC,EAAEC,KAAK,CAAC,GAAG,CAAC;MACtC,IAAIC,KAAK,CAACC,OAAO,CAACJ,GAAG,CAAC,EAAE;QACtB,KAAK,IAAIK,CAAC,GAAG,CAAC,EAAEA,CAAC,GAAGL,GAAG,CAACM,MAAM,EAAED,CAAC,EAAE,EAAE;UACnC;UACA;UACA;;UAEA;UACA;UACA;UACA;;UAEA;UACA,MAAME,KAAK,GAAGP,GAAG,CAACK,CAAC,CAAC,CAACG,IAAI,CAAC,CAAC,CAACN,KAAK,CAAC,KAAK,CAAC;UACxC;UACA,IAAIK,KAAK,CAACD,MAAM,IAAI,CAAC,EAAE;YACrB;YACA,MAAMG,QAAQ,GAAGF,KAAK,CAAC,CAAC,CAAC,CAACL,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAACM,IAAI,CAAC,CAAC;YAC9C,IAAIC,QAAQ,KAAKf,MAAM,CAACgB,SAAS,EAAE;cACjC,OAAOb,IAAI,CAACc,gBAAU,CAACC,OAAO,CAACC,iBAAW,CAACC,aAAa,EAAE,eAAe,CAAC,CAAC;YAC7E;UACF;QACF;MACF;IACF;IACAjB,IAAI,CAAC,CAAC;EACR,CAAC;AACH","ignoreList":[]}
@@ -12,6 +12,7 @@ var _core = require("@verdaccio/core");
12
12
  var _url = require("@verdaccio/url");
13
13
  var _security = require("./security");
14
14
  var _renderHTML = _interopRequireDefault(require("./utils/renderHTML"));
15
+ var _webUrls = require("./web-urls");
15
16
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
17
  const debug = (0, _debug.default)('verdaccio:web:render');
17
18
  const sendFileCallback = next => err => {
@@ -40,7 +41,7 @@ function renderWebMiddleware(config, tokenMiddleware, pluginOptions) {
40
41
  router.use(_security.setSecurityWebHeaders);
41
42
 
42
43
  // any match within the static is routed to the file system
43
- router.get('/-/static/*', function (req, res, next) {
44
+ router.get(_webUrls.WebUrlsNamespace.static + '*', function (req, res, next) {
44
45
  const filename = req.params[0];
45
46
  let file = `${staticPath}/${filename}`;
46
47
  if (filename === 'favicon.ico' && config?.web?.favicon) {
@@ -65,7 +66,7 @@ function renderWebMiddleware(config, tokenMiddleware, pluginOptions) {
65
66
  if (_fs.default.existsSync(absoluteLocalFile) && typeof _fs.default.accessSync(absoluteLocalFile, _fs.default.constants.R_OK) === 'undefined') {
66
67
  // Note: `path.join` will break on Windows, because it transforms `/` to `\`
67
68
  // Use POSIX version `path.posix.join` instead.
68
- logo = _path.default.posix.join('/-/static/', _path.default.basename(logo));
69
+ logo = _path.default.posix.join(_webUrls.WebUrlsNamespace.static, _path.default.basename(logo));
69
70
  router.get(logo, function (_req, res, next) {
70
71
  // @ts-ignore
71
72
  debug('serve custom logo web:%s - local:%s', logo, absoluteLocalFile);
@@ -91,11 +92,11 @@ function renderWebMiddleware(config, tokenMiddleware, pluginOptions) {
91
92
  if (config?.web?.logoDark) {
92
93
  config.web.logoDark = logoDark;
93
94
  }
94
- router.get('/-/web/:section/*', function (req, res) {
95
+ router.get(_webUrls.WebUrlsNamespace.web + ':section/*', function (req, res) {
95
96
  (0, _renderHTML.default)(config, manifest, manifestFiles, req, res);
96
97
  debug('render html section');
97
98
  });
98
- router.get('/', function (req, res) {
99
+ router.get(_webUrls.WebUrlsNamespace.root, function (req, res) {
99
100
  (0, _renderHTML.default)(config, manifest, manifestFiles, req, res);
100
101
  debug('render root');
101
102
  });
@@ -1 +1 @@
1
- {"version":3,"file":"render-web.js","names":["_debug","_interopRequireDefault","require","_express","_fs","_path","_core","_url","_security","_renderHTML","e","__esModule","default","debug","buildDebug","sendFileCallback","next","err","status","HTTP_STATUS","NOT_FOUND","renderWebMiddleware","config","tokenMiddleware","pluginOptions","staticPath","manifest","manifestFiles","router","express","Router","use","setSecurityWebHeaders","get","req","res","filename","params","file","web","favicon","isURLhasValidProtocol","url","sendFile","renderLogo","logo","absoluteLocalFile","path","posix","resolve","fs","existsSync","accessSync","constants","R_OK","join","basename","_req","undefined","logoDark","renderHTML"],"sources":["../../../src/middlewares/web/render-web.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport express from 'express';\nimport fs from 'fs';\nimport path from 'path';\n\nimport { HTTP_STATUS } from '@verdaccio/core';\nimport { isURLhasValidProtocol } from '@verdaccio/url';\n\nimport { setSecurityWebHeaders } from './security';\nimport renderHTML from './utils/renderHTML';\n\nconst debug = buildDebug('verdaccio:web:render');\n\nconst sendFileCallback = (next) => (err) => {\n if (!err) {\n return;\n }\n if (err.status === HTTP_STATUS.NOT_FOUND) {\n next();\n } else {\n next(err);\n }\n};\n\nexport function renderWebMiddleware(config, tokenMiddleware, pluginOptions) {\n const { staticPath, manifest, manifestFiles } = pluginOptions;\n debug('static path %o', staticPath);\n\n /* eslint new-cap:off */\n const router = express.Router();\n if (typeof tokenMiddleware === 'function') {\n router.use(tokenMiddleware);\n }\n\n router.use(setSecurityWebHeaders);\n\n // any match within the static is routed to the file system\n router.get('/-/static/*', function (req, res, next) {\n const filename = req.params[0];\n let file = `${staticPath}/${filename}`;\n if (filename === 'favicon.ico' && config?.web?.favicon) {\n file = config?.web?.favicon;\n if (isURLhasValidProtocol(file)) {\n debug('redirect to favicon %s', file);\n req.url = file;\n return next();\n }\n }\n debug('render static file %o', file);\n res.sendFile(file, sendFileCallback(next));\n });\n\n function renderLogo(logo: string | undefined): string | undefined {\n // check the origin of the logo\n if (logo && !isURLhasValidProtocol(logo)) {\n // URI related to a local file\n const absoluteLocalFile = path.posix.resolve(logo);\n debug('serve local logo %s', absoluteLocalFile);\n try {\n // TODO: replace existsSync by async alternative\n if (\n fs.existsSync(absoluteLocalFile) &&\n typeof fs.accessSync(absoluteLocalFile, fs.constants.R_OK) === 'undefined'\n ) {\n // Note: `path.join` will break on Windows, because it transforms `/` to `\\`\n // Use POSIX version `path.posix.join` instead.\n logo = path.posix.join('/-/static/', path.basename(logo));\n router.get(logo, function (_req, res, next) {\n // @ts-ignore\n debug('serve custom logo web:%s - local:%s', logo, absoluteLocalFile);\n res.sendFile(absoluteLocalFile, sendFileCallback(next));\n });\n debug('enabled custom logo %s', logo);\n } else {\n logo = undefined;\n debug(`web logo is wrong, path ${absoluteLocalFile} does not exist or is not readable`);\n }\n } catch {\n logo = undefined;\n debug(`web logo is wrong, path ${absoluteLocalFile} does not exist or is not readable`);\n }\n }\n return logo;\n }\n\n const logo = renderLogo(config?.web?.logo);\n if (config?.web?.logo) {\n config.web.logo = logo;\n }\n const logoDark = renderLogo(config?.web?.logoDark);\n if (config?.web?.logoDark) {\n config.web.logoDark = logoDark;\n }\n\n router.get('/-/web/:section/*', function (req, res) {\n renderHTML(config, manifest, manifestFiles, req, res);\n debug('render html section');\n });\n\n router.get('/', function (req, res) {\n renderHTML(config, manifest, manifestFiles, req, res);\n debug('render root');\n });\n\n return router;\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"render-web.js","names":["_debug","_interopRequireDefault","require","_express","_fs","_path","_core","_url","_security","_renderHTML","_webUrls","e","__esModule","default","debug","buildDebug","sendFileCallback","next","err","status","HTTP_STATUS","NOT_FOUND","renderWebMiddleware","config","tokenMiddleware","pluginOptions","staticPath","manifest","manifestFiles","router","express","Router","use","setSecurityWebHeaders","get","WebUrlsNamespace","static","req","res","filename","params","file","web","favicon","isURLhasValidProtocol","url","sendFile","renderLogo","logo","absoluteLocalFile","path","posix","resolve","fs","existsSync","accessSync","constants","R_OK","join","basename","_req","undefined","logoDark","renderHTML","root"],"sources":["../../../src/middlewares/web/render-web.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport express from 'express';\nimport fs from 'fs';\nimport path from 'path';\n\nimport { HTTP_STATUS } from '@verdaccio/core';\nimport { isURLhasValidProtocol } from '@verdaccio/url';\n\nimport { setSecurityWebHeaders } from './security';\nimport renderHTML from './utils/renderHTML';\nimport { WebUrlsNamespace } from './web-urls';\n\nconst debug = buildDebug('verdaccio:web:render');\n\nconst sendFileCallback = (next) => (err) => {\n if (!err) {\n return;\n }\n if (err.status === HTTP_STATUS.NOT_FOUND) {\n next();\n } else {\n next(err);\n }\n};\n\nexport function renderWebMiddleware(config, tokenMiddleware, pluginOptions) {\n const { staticPath, manifest, manifestFiles } = pluginOptions;\n debug('static path %o', staticPath);\n\n /* eslint new-cap:off */\n const router = express.Router();\n if (typeof tokenMiddleware === 'function') {\n router.use(tokenMiddleware);\n }\n\n router.use(setSecurityWebHeaders);\n\n // any match within the static is routed to the file system\n router.get(WebUrlsNamespace.static + '*', function (req, res, next) {\n const filename = req.params[0];\n let file = `${staticPath}/${filename}`;\n if (filename === 'favicon.ico' && config?.web?.favicon) {\n file = config?.web?.favicon;\n if (isURLhasValidProtocol(file)) {\n debug('redirect to favicon %s', file);\n req.url = file;\n return next();\n }\n }\n debug('render static file %o', file);\n res.sendFile(file, sendFileCallback(next));\n });\n\n function renderLogo(logo: string | undefined): string | undefined {\n // check the origin of the logo\n if (logo && !isURLhasValidProtocol(logo)) {\n // URI related to a local file\n const absoluteLocalFile = path.posix.resolve(logo);\n debug('serve local logo %s', absoluteLocalFile);\n try {\n // TODO: replace existsSync by async alternative\n if (\n fs.existsSync(absoluteLocalFile) &&\n typeof fs.accessSync(absoluteLocalFile, fs.constants.R_OK) === 'undefined'\n ) {\n // Note: `path.join` will break on Windows, because it transforms `/` to `\\`\n // Use POSIX version `path.posix.join` instead.\n logo = path.posix.join(WebUrlsNamespace.static, path.basename(logo));\n router.get(logo, function (_req, res, next) {\n // @ts-ignore\n debug('serve custom logo web:%s - local:%s', logo, absoluteLocalFile);\n res.sendFile(absoluteLocalFile, sendFileCallback(next));\n });\n debug('enabled custom logo %s', logo);\n } else {\n logo = undefined;\n debug(`web logo is wrong, path ${absoluteLocalFile} does not exist or is not readable`);\n }\n } catch {\n logo = undefined;\n debug(`web logo is wrong, path ${absoluteLocalFile} does not exist or is not readable`);\n }\n }\n return logo;\n }\n\n const logo = renderLogo(config?.web?.logo);\n if (config?.web?.logo) {\n config.web.logo = logo;\n }\n const logoDark = renderLogo(config?.web?.logoDark);\n if (config?.web?.logoDark) {\n config.web.logoDark = logoDark;\n }\n\n router.get(WebUrlsNamespace.web + ':section/*', function (req, res) {\n renderHTML(config, manifest, manifestFiles, req, res);\n debug('render html section');\n });\n\n router.get(WebUrlsNamespace.root, function (req, res) {\n renderHTML(config, manifest, manifestFiles, req, res);\n debug('render root');\n });\n\n return router;\n}\n"],"mappings":"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","ignoreList":[]}
@@ -7,6 +7,7 @@ exports.default = void 0;
7
7
  var _express = _interopRequireDefault(require("express"));
8
8
  var _renderWeb = require("./render-web");
9
9
  var _webApi = require("./web-api");
10
+ var _webUrls = require("./web-urls");
10
11
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
12
  var _default = (config, middlewares, pluginOptions) => {
12
13
  // eslint-disable-next-line new-cap
@@ -16,9 +17,9 @@ var _default = (config, middlewares, pluginOptions) => {
16
17
  webEndpointsApi
17
18
  } = middlewares;
18
19
  // render web
19
- router.use('/', (0, _renderWeb.renderWebMiddleware)(config, tokenMiddleware, pluginOptions));
20
- // web endpoints, search, packages, etc
21
- router.use('/-/verdaccio/', (0, _webApi.webAPIMiddleware)(tokenMiddleware, webEndpointsApi));
20
+ router.use(_webUrls.WebUrlsNamespace.root, (0, _renderWeb.renderWebMiddleware)(config, tokenMiddleware, pluginOptions));
21
+ // web endpoints: search, packages, readme, sidebar, etc
22
+ router.use(_webUrls.WebUrlsNamespace.endpoints, (0, _webApi.webAPIMiddleware)(tokenMiddleware, webEndpointsApi));
22
23
  return router;
23
24
  };
24
25
  exports.default = _default;
@@ -1 +1 @@
1
- {"version":3,"file":"web-middleware.js","names":["_express","_interopRequireDefault","require","_renderWeb","_webApi","e","__esModule","default","_default","config","middlewares","pluginOptions","router","express","Router","tokenMiddleware","webEndpointsApi","use","renderWebMiddleware","webAPIMiddleware","exports"],"sources":["../../../src/middlewares/web/web-middleware.ts"],"sourcesContent":["import express from 'express';\n\nimport { renderWebMiddleware } from './render-web';\nimport { webAPIMiddleware } from './web-api';\n\nexport default (config, middlewares, pluginOptions): any => {\n // eslint-disable-next-line new-cap\n const router = express.Router();\n const { tokenMiddleware, webEndpointsApi } = middlewares;\n // render web\n router.use('/', renderWebMiddleware(config, tokenMiddleware, pluginOptions));\n // web endpoints, search, packages, etc\n router.use('/-/verdaccio/', webAPIMiddleware(tokenMiddleware, webEndpointsApi));\n return router;\n};\n"],"mappings":";;;;;;AAAA,IAAAA,QAAA,GAAAC,sBAAA,CAAAC,OAAA;AAEA,IAAAC,UAAA,GAAAD,OAAA;AACA,IAAAE,OAAA,GAAAF,OAAA;AAA6C,SAAAD,uBAAAI,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAAA,IAAAG,QAAA,GAE9BA,CAACC,MAAM,EAAEC,WAAW,EAAEC,aAAa,KAAU;EAC1D;EACA,MAAMC,MAAM,GAAGC,gBAAO,CAACC,MAAM,CAAC,CAAC;EAC/B,MAAM;IAAEC,eAAe;IAAEC;EAAgB,CAAC,GAAGN,WAAW;EACxD;EACAE,MAAM,CAACK,GAAG,CAAC,GAAG,EAAE,IAAAC,8BAAmB,EAACT,MAAM,EAAEM,eAAe,EAAEJ,aAAa,CAAC,CAAC;EAC5E;EACAC,MAAM,CAACK,GAAG,CAAC,eAAe,EAAE,IAAAE,wBAAgB,EAACJ,eAAe,EAAEC,eAAe,CAAC,CAAC;EAC/E,OAAOJ,MAAM;AACf,CAAC;AAAAQ,OAAA,CAAAb,OAAA,GAAAC,QAAA","ignoreList":[]}
1
+ {"version":3,"file":"web-middleware.js","names":["_express","_interopRequireDefault","require","_renderWeb","_webApi","_webUrls","e","__esModule","default","_default","config","middlewares","pluginOptions","router","express","Router","tokenMiddleware","webEndpointsApi","use","WebUrlsNamespace","root","renderWebMiddleware","endpoints","webAPIMiddleware","exports"],"sources":["../../../src/middlewares/web/web-middleware.ts"],"sourcesContent":["import express from 'express';\n\nimport { renderWebMiddleware } from './render-web';\nimport { webAPIMiddleware } from './web-api';\nimport { WebUrlsNamespace } from './web-urls';\n\nexport default (config, middlewares, pluginOptions): any => {\n // eslint-disable-next-line new-cap\n const router = express.Router();\n const { tokenMiddleware, webEndpointsApi } = middlewares;\n // render web\n router.use(WebUrlsNamespace.root, renderWebMiddleware(config, tokenMiddleware, pluginOptions));\n // web endpoints: search, packages, readme, sidebar, etc\n router.use(WebUrlsNamespace.endpoints, webAPIMiddleware(tokenMiddleware, webEndpointsApi));\n return router;\n};\n"],"mappings":";;;;;;AAAA,IAAAA,QAAA,GAAAC,sBAAA,CAAAC,OAAA;AAEA,IAAAC,UAAA,GAAAD,OAAA;AACA,IAAAE,OAAA,GAAAF,OAAA;AACA,IAAAG,QAAA,GAAAH,OAAA;AAA8C,SAAAD,uBAAAK,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAAA,IAAAG,QAAA,GAE/BA,CAACC,MAAM,EAAEC,WAAW,EAAEC,aAAa,KAAU;EAC1D;EACA,MAAMC,MAAM,GAAGC,gBAAO,CAACC,MAAM,CAAC,CAAC;EAC/B,MAAM;IAAEC,eAAe;IAAEC;EAAgB,CAAC,GAAGN,WAAW;EACxD;EACAE,MAAM,CAACK,GAAG,CAACC,yBAAgB,CAACC,IAAI,EAAE,IAAAC,8BAAmB,EAACX,MAAM,EAAEM,eAAe,EAAEJ,aAAa,CAAC,CAAC;EAC9F;EACAC,MAAM,CAACK,GAAG,CAACC,yBAAgB,CAACG,SAAS,EAAE,IAAAC,wBAAgB,EAACP,eAAe,EAAEC,eAAe,CAAC,CAAC;EAC1F,OAAOJ,MAAM;AACf,CAAC;AAAAW,OAAA,CAAAhB,OAAA,GAAAC,QAAA","ignoreList":[]}
@@ -15,7 +15,10 @@ export declare enum WebUrls {
15
15
  * Enum for web urls namespace, used on the web middleware
16
16
  */
17
17
  export declare enum WebUrlsNamespace {
18
- root = "/-/verdaccio/",
18
+ root = "/",
19
+ static = "/-/static/",
20
+ endpoints = "/-/verdaccio/",
21
+ web = "/-/web/",
19
22
  data = "/data/",
20
23
  sec = "/sec/"
21
24
  }
@@ -22,7 +22,10 @@ let WebUrls = exports.WebUrls = /*#__PURE__*/function (WebUrls) {
22
22
  * Enum for web urls namespace, used on the web middleware
23
23
  */
24
24
  let WebUrlsNamespace = exports.WebUrlsNamespace = /*#__PURE__*/function (WebUrlsNamespace) {
25
- WebUrlsNamespace["root"] = "/-/verdaccio/";
25
+ WebUrlsNamespace["root"] = "/";
26
+ WebUrlsNamespace["static"] = "/-/static/";
27
+ WebUrlsNamespace["endpoints"] = "/-/verdaccio/";
28
+ WebUrlsNamespace["web"] = "/-/web/";
26
29
  WebUrlsNamespace["data"] = "/data/";
27
30
  WebUrlsNamespace["sec"] = "/sec/";
28
31
  return WebUrlsNamespace;
@@ -1 +1 @@
1
- {"version":3,"file":"web-urls.js","names":["WebUrls","exports","WebUrlsNamespace"],"sources":["../../../src/middlewares/web/web-urls.ts"],"sourcesContent":["/**\n * Enum for web urls, used on the web middleware\n */\nexport enum WebUrls {\n sidebar_scopped_package = '/sidebar/:scope/:package',\n sidebar_package = '/sidebar/:package',\n readme_package_scoped_version = '/package/readme/:scope/:package/:version?',\n readme_package_version = '/package/readme/:package/:version?',\n packages_all = '/packages',\n user_login = '/login',\n search = '/search/:anything',\n reset_password = '/reset_password',\n}\n\n/**\n * Enum for web urls namespace, used on the web middleware\n */\nexport enum WebUrlsNamespace {\n root = '/-/verdaccio/',\n data = '/data/',\n sec = '/sec/',\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AAFA,IAGYA,OAAO,GAAAC,OAAA,CAAAD,OAAA,0BAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAA,OAAPA,OAAO;AAAA;AAWnB;AACA;AACA;AAFA,IAGYE,gBAAgB,GAAAD,OAAA,CAAAC,gBAAA,0BAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAA,OAAhBA,gBAAgB;AAAA","ignoreList":[]}
1
+ {"version":3,"file":"web-urls.js","names":["WebUrls","exports","WebUrlsNamespace"],"sources":["../../../src/middlewares/web/web-urls.ts"],"sourcesContent":["/**\n * Enum for web urls, used on the web middleware\n */\nexport enum WebUrls {\n sidebar_scopped_package = '/sidebar/:scope/:package',\n sidebar_package = '/sidebar/:package',\n readme_package_scoped_version = '/package/readme/:scope/:package/:version?',\n readme_package_version = '/package/readme/:package/:version?',\n packages_all = '/packages',\n user_login = '/login',\n search = '/search/:anything',\n reset_password = '/reset_password',\n}\n\n/**\n * Enum for web urls namespace, used on the web middleware\n */\nexport enum WebUrlsNamespace {\n root = '/',\n static = '/-/static/',\n endpoints = '/-/verdaccio/',\n web = '/-/web/',\n data = '/data/',\n sec = '/sec/',\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AAFA,IAGYA,OAAO,GAAAC,OAAA,CAAAD,OAAA,0BAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAPA,OAAO;EAAA,OAAPA,OAAO;AAAA;AAWnB;AACA;AACA;AAFA,IAGYE,gBAAgB,GAAAD,OAAA,CAAAC,gBAAA,0BAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAA,OAAhBA,gBAAgB;AAAA","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verdaccio/middleware",
3
- "version": "8.0.0-next-8.10",
3
+ "version": "8.0.0-next-8.11",
4
4
  "description": "express middleware utils",
5
5
  "main": "./build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -29,10 +29,10 @@
29
29
  "node": ">=18"
30
30
  },
31
31
  "dependencies": {
32
- "@verdaccio/config": "8.0.0-next-8.10",
33
- "@verdaccio/core": "8.0.0-next-8.10",
34
- "@verdaccio/url": "13.0.0-next-8.10",
35
- "@verdaccio/utils": "8.1.0-next-8.10",
32
+ "@verdaccio/config": "8.0.0-next-8.11",
33
+ "@verdaccio/core": "8.0.0-next-8.11",
34
+ "@verdaccio/url": "13.0.0-next-8.11",
35
+ "@verdaccio/utils": "8.1.0-next-8.11",
36
36
  "debug": "4.4.0",
37
37
  "express": "4.21.2",
38
38
  "express-rate-limit": "5.5.1",
@@ -45,7 +45,7 @@
45
45
  "url": "https://opencollective.com/verdaccio"
46
46
  },
47
47
  "devDependencies": {
48
- "@verdaccio/logger": "8.0.0-next-8.10",
48
+ "@verdaccio/logger": "8.0.0-next-8.11",
49
49
  "body-parser": "1.20.3",
50
50
  "supertest": "7.0.0",
51
51
  "jsdom": "25.0.1"
@@ -14,13 +14,24 @@ export function antiLoop(config: Config) {
14
14
  const arr = req.get('via')?.split(',');
15
15
  if (Array.isArray(arr)) {
16
16
  for (let i = 0; i < arr.length; i++) {
17
- // the "via" header must contains an specific headers, this has to be on sync
17
+ // the "via" header must contain a specific value, this has to be in sync
18
18
  // with the proxy request
19
19
  // match eg: Server 1 or Server 2
20
- // TODO: improve this RegEX
21
- const m = arr[i].trim().match(/\s*(\S+)\s+(\S+)/);
22
- if (m && m[2] === config.server_id) {
23
- return next(errorUtils.getCode(HTTP_STATUS.LOOP_DETECTED, 'loop detected'));
20
+
21
+ // RFC 7230: Via = 1*( "," OWS Via-value )
22
+ // Via-value = received-protocol RWS received-by [ RWS comment ]
23
+ // received-protocol = [ protocol-name "/" ] protocol-version
24
+ // received-by = ( uri-host [ ":" port ] ) / pseudonym
25
+
26
+ // Split the trimmed header value into parts
27
+ const parts = arr[i].trim().split(/\s+/);
28
+ // Check if we have at least protocol/version and received-by parts
29
+ if (parts.length >= 2) {
30
+ // Get the received-by value (server id), removing any comment
31
+ const serverId = parts[1].split('(')[0].trim();
32
+ if (serverId === config.server_id) {
33
+ return next(errorUtils.getCode(HTTP_STATUS.LOOP_DETECTED, 'loop detected'));
34
+ }
24
35
  }
25
36
  }
26
37
  }
@@ -8,6 +8,7 @@ import { isURLhasValidProtocol } from '@verdaccio/url';
8
8
 
9
9
  import { setSecurityWebHeaders } from './security';
10
10
  import renderHTML from './utils/renderHTML';
11
+ import { WebUrlsNamespace } from './web-urls';
11
12
 
12
13
  const debug = buildDebug('verdaccio:web:render');
13
14
 
@@ -35,7 +36,7 @@ export function renderWebMiddleware(config, tokenMiddleware, pluginOptions) {
35
36
  router.use(setSecurityWebHeaders);
36
37
 
37
38
  // any match within the static is routed to the file system
38
- router.get('/-/static/*', function (req, res, next) {
39
+ router.get(WebUrlsNamespace.static + '*', function (req, res, next) {
39
40
  const filename = req.params[0];
40
41
  let file = `${staticPath}/${filename}`;
41
42
  if (filename === 'favicon.ico' && config?.web?.favicon) {
@@ -64,7 +65,7 @@ export function renderWebMiddleware(config, tokenMiddleware, pluginOptions) {
64
65
  ) {
65
66
  // Note: `path.join` will break on Windows, because it transforms `/` to `\`
66
67
  // Use POSIX version `path.posix.join` instead.
67
- logo = path.posix.join('/-/static/', path.basename(logo));
68
+ logo = path.posix.join(WebUrlsNamespace.static, path.basename(logo));
68
69
  router.get(logo, function (_req, res, next) {
69
70
  // @ts-ignore
70
71
  debug('serve custom logo web:%s - local:%s', logo, absoluteLocalFile);
@@ -92,12 +93,12 @@ export function renderWebMiddleware(config, tokenMiddleware, pluginOptions) {
92
93
  config.web.logoDark = logoDark;
93
94
  }
94
95
 
95
- router.get('/-/web/:section/*', function (req, res) {
96
+ router.get(WebUrlsNamespace.web + ':section/*', function (req, res) {
96
97
  renderHTML(config, manifest, manifestFiles, req, res);
97
98
  debug('render html section');
98
99
  });
99
100
 
100
- router.get('/', function (req, res) {
101
+ router.get(WebUrlsNamespace.root, function (req, res) {
101
102
  renderHTML(config, manifest, manifestFiles, req, res);
102
103
  debug('render root');
103
104
  });
@@ -2,14 +2,15 @@ import express from 'express';
2
2
 
3
3
  import { renderWebMiddleware } from './render-web';
4
4
  import { webAPIMiddleware } from './web-api';
5
+ import { WebUrlsNamespace } from './web-urls';
5
6
 
6
7
  export default (config, middlewares, pluginOptions): any => {
7
8
  // eslint-disable-next-line new-cap
8
9
  const router = express.Router();
9
10
  const { tokenMiddleware, webEndpointsApi } = middlewares;
10
11
  // render web
11
- router.use('/', renderWebMiddleware(config, tokenMiddleware, pluginOptions));
12
- // web endpoints, search, packages, etc
13
- router.use('/-/verdaccio/', webAPIMiddleware(tokenMiddleware, webEndpointsApi));
12
+ router.use(WebUrlsNamespace.root, renderWebMiddleware(config, tokenMiddleware, pluginOptions));
13
+ // web endpoints: search, packages, readme, sidebar, etc
14
+ router.use(WebUrlsNamespace.endpoints, webAPIMiddleware(tokenMiddleware, webEndpointsApi));
14
15
  return router;
15
16
  };
@@ -16,7 +16,10 @@ export enum WebUrls {
16
16
  * Enum for web urls namespace, used on the web middleware
17
17
  */
18
18
  export enum WebUrlsNamespace {
19
- root = '/-/verdaccio/',
19
+ root = '/',
20
+ static = '/-/static/',
21
+ endpoints = '/-/verdaccio/',
22
+ web = '/-/web/',
20
23
  data = '/data/',
21
24
  sec = '/sec/',
22
25
  }
@@ -0,0 +1,90 @@
1
+ import request from 'supertest';
2
+ import { test } from 'vitest';
3
+
4
+ import { HTTP_STATUS } from '@verdaccio/core';
5
+
6
+ import { antiLoop } from '../src';
7
+ import { getApp } from './helper';
8
+
9
+ test('should not be a loop', async () => {
10
+ const app = getApp([]);
11
+ // @ts-ignore
12
+ app.use(antiLoop({ server_id: '1' }));
13
+ app.get('/sec', (req, res) => {
14
+ res.status(HTTP_STATUS.OK).json({});
15
+ });
16
+
17
+ return request(app).get('/sec').set('via', 'Server 2').expect(HTTP_STATUS.OK);
18
+ });
19
+
20
+ test('should be a loop', async () => {
21
+ const app = getApp([]);
22
+ // @ts-ignore
23
+ app.use(antiLoop({ server_id: '1' }));
24
+ app.get('/sec', (req, res) => {
25
+ res.status(HTTP_STATUS.OK).json({});
26
+ });
27
+
28
+ return request(app)
29
+ .get('/sec')
30
+ .set('via', 'Server 1, Server 2')
31
+ .expect(HTTP_STATUS.LOOP_DETECTED);
32
+ });
33
+
34
+ test('should detect loop with protocol name in via header', async () => {
35
+ const app = getApp([]);
36
+ // @ts-ignore
37
+ app.use(antiLoop({ server_id: '1' }));
38
+ app.get('/sec', (req, res) => {
39
+ res.status(HTTP_STATUS.OK).json({});
40
+ });
41
+
42
+ return request(app).get('/sec').set('via', 'HTTP/1.1 1').expect(HTTP_STATUS.LOOP_DETECTED);
43
+ });
44
+
45
+ test('should detect loop with comment in via header', async () => {
46
+ const app = getApp([]);
47
+ // @ts-ignore
48
+ app.use(antiLoop({ server_id: '1' }));
49
+ app.get('/sec', (req, res) => {
50
+ res.status(HTTP_STATUS.OK).json({});
51
+ });
52
+
53
+ return request(app).get('/sec').set('via', '1.1 1 (Verdaccio)').expect(HTTP_STATUS.LOOP_DETECTED);
54
+ });
55
+
56
+ test('should detect loop in multiple via entries', async () => {
57
+ const app = getApp([]);
58
+ // @ts-ignore
59
+ app.use(antiLoop({ server_id: '1' }));
60
+ app.get('/sec', (req, res) => {
61
+ res.status(HTTP_STATUS.OK).json({});
62
+ });
63
+
64
+ return request(app)
65
+ .get('/sec')
66
+ .set('via', '1.1 server-a, 1.1 1, 1.1 server-b')
67
+ .expect(HTTP_STATUS.LOOP_DETECTED);
68
+ });
69
+
70
+ test('should handle malformed via header gracefully', async () => {
71
+ const app = getApp([]);
72
+ // @ts-ignore
73
+ app.use(antiLoop({ server_id: '1' }));
74
+ app.get('/sec', (req, res) => {
75
+ res.status(HTTP_STATUS.OK).json({});
76
+ });
77
+
78
+ return request(app).get('/sec').set('via', 'malformed-header').expect(HTTP_STATUS.OK);
79
+ });
80
+
81
+ test('should handle via header with unexpected format', async () => {
82
+ const app = getApp([]);
83
+ // @ts-ignore
84
+ app.use(antiLoop({ server_id: '1' }));
85
+ app.get('/sec', (req, res) => {
86
+ res.status(HTTP_STATUS.OK).json({});
87
+ });
88
+
89
+ return request(app).get('/sec').set('via', 'unexpected format').expect(HTTP_STATUS.OK);
90
+ });
package/test/loop.spec.ts DELETED
@@ -1,32 +0,0 @@
1
- import request from 'supertest';
2
- import { test } from 'vitest';
3
-
4
- import { HTTP_STATUS } from '@verdaccio/core';
5
-
6
- import { antiLoop } from '../src';
7
- import { getApp } from './helper';
8
-
9
- test('should not be a loop', async () => {
10
- const app = getApp([]);
11
- // @ts-ignore
12
- app.use(antiLoop({ server_id: '1' }));
13
- app.get('/sec', (req, res) => {
14
- res.status(HTTP_STATUS.OK).json({});
15
- });
16
-
17
- return request(app).get('/sec').set('via', 'Server 2').expect(HTTP_STATUS.OK);
18
- });
19
-
20
- test('should be a loop', async () => {
21
- const app = getApp([]);
22
- // @ts-ignore
23
- app.use(antiLoop({ server_id: '1' }));
24
- app.get('/sec', (req, res) => {
25
- res.status(HTTP_STATUS.OK).json({});
26
- });
27
-
28
- return request(app)
29
- .get('/sec')
30
- .set('via', 'Server 1, Server 2')
31
- .expect(HTTP_STATUS.LOOP_DETECTED);
32
- });