@verdaccio/middleware 9.0.0-next-9.20 → 9.0.0-next-9.22

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 (71) hide show
  1. package/build/middlewares/allow.js +6 -2
  2. package/build/middlewares/allow.js.map +1 -1
  3. package/build/middlewares/allow.mjs +7 -3
  4. package/build/middlewares/allow.mjs.map +1 -1
  5. package/build/middlewares/antiLoop.d.ts +2 -2
  6. package/build/middlewares/antiLoop.js.map +1 -1
  7. package/build/middlewares/antiLoop.mjs.map +1 -1
  8. package/build/middlewares/body-parser.d.ts +2 -2
  9. package/build/middlewares/body-parser.js.map +1 -1
  10. package/build/middlewares/body-parser.mjs.map +1 -1
  11. package/build/middlewares/dotfiles.d.ts +1 -1
  12. package/build/middlewares/dotfiles.js.map +1 -1
  13. package/build/middlewares/dotfiles.mjs.map +1 -1
  14. package/build/middlewares/encode-pkg.d.ts +1 -1
  15. package/build/middlewares/encode-pkg.js.map +1 -1
  16. package/build/middlewares/encode-pkg.mjs.map +1 -1
  17. package/build/middlewares/error.d.ts +3 -3
  18. package/build/middlewares/error.js.map +1 -1
  19. package/build/middlewares/error.mjs.map +1 -1
  20. package/build/middlewares/final.d.ts +2 -2
  21. package/build/middlewares/final.js.map +1 -1
  22. package/build/middlewares/json.d.ts +1 -1
  23. package/build/middlewares/log.d.ts +1 -1
  24. package/build/middlewares/log.js +53 -37
  25. package/build/middlewares/log.js.map +1 -1
  26. package/build/middlewares/log.mjs +54 -38
  27. package/build/middlewares/log.mjs.map +1 -1
  28. package/build/middlewares/make-url-relative.d.ts +1 -1
  29. package/build/middlewares/make-url-relative.js.map +1 -1
  30. package/build/middlewares/media.js.map +1 -1
  31. package/build/middlewares/media.mjs.map +1 -1
  32. package/build/middlewares/rate-limit.d.ts +2 -2
  33. package/build/middlewares/rate-limit.js.map +1 -1
  34. package/build/middlewares/request-options.d.ts +2 -2
  35. package/build/middlewares/request-options.js +1 -7
  36. package/build/middlewares/request-options.js.map +1 -1
  37. package/build/middlewares/request-options.mjs +1 -5
  38. package/build/middlewares/request-options.mjs.map +1 -1
  39. package/build/middlewares/token-auth.d.ts +2 -2
  40. package/build/middlewares/token-auth.js +1 -1
  41. package/build/middlewares/token-auth.js.map +1 -1
  42. package/build/middlewares/token-auth.mjs +1 -1
  43. package/build/middlewares/token-auth.mjs.map +1 -1
  44. package/build/middlewares/user-agent.d.ts +1 -1
  45. package/build/middlewares/user-agent.js.map +1 -1
  46. package/build/middlewares/validation.d.ts +1 -1
  47. package/build/middlewares/web/index.js +6 -3
  48. package/build/middlewares/web/index.mjs +4 -3
  49. package/build/middlewares/web/render-web.d.ts +1 -1
  50. package/build/middlewares/web/render-web.js +4 -2
  51. package/build/middlewares/web/render-web.js.map +1 -1
  52. package/build/middlewares/web/render-web.mjs +4 -2
  53. package/build/middlewares/web/render-web.mjs.map +1 -1
  54. package/build/middlewares/web/security.js.map +1 -1
  55. package/build/middlewares/web/utils/file-utils.js.map +1 -1
  56. package/build/middlewares/web/utils/manifest.js.map +1 -1
  57. package/build/middlewares/web/utils/renderHTML.d.ts +4 -4
  58. package/build/middlewares/web/utils/renderHTML.js.map +1 -1
  59. package/build/middlewares/web/utils/renderHTML.mjs.map +1 -1
  60. package/build/middlewares/web/utils/template.d.ts +2 -2
  61. package/build/middlewares/web/utils/template.js.map +1 -1
  62. package/build/middlewares/web/utils/template.mjs.map +1 -1
  63. package/build/middlewares/web/utils/ui-options.d.ts +3 -3
  64. package/build/middlewares/web/utils/ui-options.js.map +1 -1
  65. package/build/middlewares/web/utils/ui-options.mjs.map +1 -1
  66. package/build/middlewares/web/utils/web-utils.js.map +1 -1
  67. package/build/middlewares/web/web-api.d.ts +2 -1
  68. package/build/middlewares/web/web-api.js.map +1 -1
  69. package/build/middlewares/web/web-middleware.d.ts +1 -1
  70. package/build/types.d.ts +2 -2
  71. package/package.json +7 -7
@@ -1,14 +1,17 @@
1
- import { HEADERS } from "@verdaccio/core";
1
+ import { HEADERS, constants } from "@verdaccio/core";
2
2
  import buildDebug from "debug";
3
3
  import { isNil } from "lodash-es";
4
4
  //#region src/middlewares/log.ts
5
5
  var debug = buildDebug("verdaccio:middleware:log");
6
6
  function isStaticRequest(url) {
7
- return url.startsWith("/-/static/");
7
+ return url.startsWith("/-/static/") || url.startsWith("/favicon");
8
+ }
9
+ var LOG_STATUS_MESSAGE = constants.LOG_STATUS_MESSAGE;
10
+ var LOG_VERDACCIO_ERROR = constants.LOG_VERDACCIO_ERROR;
11
+ var LOG_VERDACCIO_BYTES = constants.LOG_VERDACCIO_BYTES;
12
+ function convertToDebugString(template) {
13
+ return template.replace(/@\{[^}]+\}/g, "%o");
8
14
  }
9
- var LOG_STATUS_MESSAGE = "@{status}, user: @{user}(@{remoteIP}), req: '@{request.method} @{request.url}'";
10
- var LOG_VERDACCIO_ERROR = `${LOG_STATUS_MESSAGE}, error: @{!error}`;
11
- var LOG_VERDACCIO_BYTES = `${LOG_STATUS_MESSAGE}, bytes: @{bytes.in}/@{bytes.out}`;
12
15
  var log = (logger, options = {}) => {
13
16
  const { hideStaticLogs = true } = options;
14
17
  return function log(req, res, next) {
@@ -19,14 +22,12 @@ var log = (logger, options = {}) => {
19
22
  if (isNil(_cookie) === false) req.headers.cookie = "<Classified>";
20
23
  req.url = req.originalUrl;
21
24
  const _skipLog = hideStaticLogs && isStaticRequest(req.url);
22
- if (_skipLog) debug("@{ip} requested '@{req.method} @{req.url}'", {
23
- ip: req.ip,
24
- req
25
- });
26
- else req.log.info({
25
+ if (_skipLog) {
26
+ if (debug.enabled) debug(convertToDebugString(constants.LOG_REQUEST_MESSAGE), req.ip, req.method, req.url);
27
+ } else req.log.info({
27
28
  req,
28
29
  ip: req.ip
29
- }, "@{ip} requested '@{req.method} @{req.url}'");
30
+ }, constants.LOG_REQUEST_MESSAGE);
30
31
  req.originalUrl = req.url;
31
32
  if (isNil(_auth) === false) req.headers.authorization = _auth;
32
33
  if (isNil(_cookie) === false) req.headers.cookie = _cookie;
@@ -40,18 +41,16 @@ var log = (logger, options = {}) => {
40
41
  bytesout += args[0]?.length || 0;
41
42
  return _write.apply(res, args);
42
43
  };
43
- const log = function() {
44
+ let requestCompleted = false;
45
+ let abortLogged = false;
46
+ const getRequestContext = () => {
44
47
  const forwardedFor = req.get(HEADERS.FORWARDED_FOR);
45
48
  const remoteAddress = req.socket.remoteAddress;
46
49
  const remoteIP = forwardedFor ? `${forwardedFor} via ${remoteAddress}` : remoteAddress;
47
- let message;
48
- if (res.locals._verdaccio_error) message = LOG_VERDACCIO_ERROR;
49
- else message = LOG_VERDACCIO_BYTES;
50
- req.url = req.originalUrl;
51
- if (_skipLog) debug(message, {
50
+ return {
52
51
  request: {
53
52
  method: req.method,
54
- url: req.url
53
+ url: req.originalUrl
55
54
  },
56
55
  user: req.remote_user?.name || null,
57
56
  remoteIP,
@@ -61,32 +60,49 @@ var log = (logger, options = {}) => {
61
60
  in: bytesin,
62
61
  out: bytesout
63
62
  }
64
- });
65
- else req.log.http({
66
- request: {
67
- method: req.method,
68
- url: req.url
69
- },
70
- user: req.remote_user?.name || null,
71
- remoteIP,
72
- status: res.statusCode,
73
- error: res.locals._verdaccio_error,
74
- bytes: {
75
- in: bytesin,
76
- out: bytesout
77
- }
78
- }, message);
79
- req.originalUrl = req.url;
63
+ };
64
+ };
65
+ const logAbortedRequest = () => {
66
+ if (abortLogged || requestCompleted) return;
67
+ abortLogged = true;
68
+ const context = {
69
+ ...getRequestContext(),
70
+ status: constants.HTTP_STATUS.CLIENT_CLOSED_REQUEST
71
+ };
72
+ if (_skipLog) {
73
+ if (debug.enabled) debug(convertToDebugString(constants.LOG_VERDACCIO_ABORT), context.status, context.user, context.remoteIP, context.request.method, context.request.url);
74
+ } else req.log.info(context, constants.LOG_VERDACCIO_ABORT);
75
+ };
76
+ const cleanupSocketListeners = () => {
77
+ req.socket.removeListener("close", onClose);
78
+ req.socket.removeListener("error", onError);
79
+ };
80
+ const onClose = () => {
81
+ if (!requestCompleted) logAbortedRequest();
82
+ cleanupSocketListeners();
83
+ };
84
+ const onError = () => {
85
+ if (!requestCompleted) logAbortedRequest();
86
+ cleanupSocketListeners();
87
+ };
88
+ const logCompletedRequest = () => {
89
+ requestCompleted = true;
90
+ const context = getRequestContext();
91
+ const message = context.error ? constants.LOG_VERDACCIO_ERROR : constants.LOG_VERDACCIO_BYTES;
92
+ if (_skipLog) {
93
+ if (debug.enabled) if (context.error) debug(convertToDebugString(message), context.status, context.user, context.remoteIP, context.request.method, context.request.url, context.error);
94
+ else debug(convertToDebugString(message), context.status, context.user, context.remoteIP, context.request.method, context.request.url, context.bytes.in, context.bytes.out);
95
+ } else req.log.http(context, message);
96
+ cleanupSocketListeners();
80
97
  };
81
- req.on("close", function() {
82
- log();
83
- });
84
98
  const _end = res.end;
85
99
  res.end = function(...args) {
86
100
  if (args[0]) bytesout += args[0].length;
87
101
  _end.apply(res, args);
88
- log();
102
+ logCompletedRequest();
89
103
  };
104
+ req.socket.on("close", onClose);
105
+ req.socket.on("error", onError);
90
106
  next();
91
107
  };
92
108
  };
@@ -1 +1 @@
1
- {"version":3,"file":"log.mjs","names":[],"sources":["../../src/middlewares/log.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { isNil } from 'lodash-es';\n\nimport { HEADERS } from '@verdaccio/core';\n\nimport type { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';\n\nconst debug = buildDebug('verdaccio:middleware:log');\n\nfunction isStaticRequest(url: string): boolean {\n return url.startsWith('/-/static/');\n}\n\n// FIXME: deprecated, moved to @verdaccio/dev-commons\nexport const LOG_STATUS_MESSAGE =\n \"@{status}, user: @{user}(@{remoteIP}), req: '@{request.method} @{request.url}'\";\nexport const LOG_VERDACCIO_ERROR = `${LOG_STATUS_MESSAGE}, error: @{!error}`;\nexport const LOG_VERDACCIO_BYTES = `${LOG_STATUS_MESSAGE}, bytes: @{bytes.in}/@{bytes.out}`;\n\nexport type LogOptions = {\n // When true, static file requests (/-/static/*) are hidden from pino logs\n // and only visible via DEBUG=verdaccio:middleware:log. Defaults to true.\n hideStaticLogs?: boolean;\n};\n\nexport const log = (logger, options: LogOptions = {}) => {\n const { hideStaticLogs = true } = options;\n\n return function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {\n // logger\n req.log = logger.child({ sub: 'in' });\n\n const _auth = req.headers.authorization;\n if (isNil(_auth) === false) {\n req.headers.authorization = '<Classified>';\n }\n\n const _cookie = req.get('cookie');\n if (isNil(_cookie) === false) {\n req.headers.cookie = '<Classified>';\n }\n\n req.url = req.originalUrl;\n const _skipLog = hideStaticLogs && isStaticRequest(req.url);\n if (_skipLog) {\n debug(\"@{ip} requested '@{req.method} @{req.url}'\", { ip: req.ip, req });\n } else {\n req.log.info({ req: req, ip: req.ip }, \"@{ip} requested '@{req.method} @{req.url}'\");\n }\n req.originalUrl = req.url;\n\n if (isNil(_auth) === false) {\n req.headers.authorization = _auth;\n }\n\n if (isNil(_cookie) === false) {\n req.headers.cookie = _cookie;\n }\n\n let bytesin = 0;\n req.on('data', function (chunk): void {\n bytesin += chunk.length;\n });\n\n let bytesout = 0;\n const _write = res.write;\n // @ts-ignore\n res.write = function (...args): boolean {\n bytesout += args[0]?.length || 0;\n // @ts-ignore\n return _write.apply(res, args);\n };\n\n const log = function (): void {\n const forwardedFor = req.get(HEADERS.FORWARDED_FOR);\n const remoteAddress = req.socket.remoteAddress;\n const remoteIP = forwardedFor ? `${forwardedFor} via ${remoteAddress}` : remoteAddress;\n let message;\n if (res.locals._verdaccio_error) {\n message = LOG_VERDACCIO_ERROR;\n } else {\n message = LOG_VERDACCIO_BYTES;\n }\n\n req.url = req.originalUrl;\n if (_skipLog) {\n debug(message, {\n request: { method: req.method, url: req.url },\n user: req.remote_user?.name || null,\n remoteIP,\n status: res.statusCode,\n error: res.locals._verdaccio_error,\n bytes: { in: bytesin, out: bytesout },\n });\n } else {\n req.log.http(\n {\n request: {\n method: req.method,\n url: req.url,\n },\n user: req.remote_user?.name || null,\n remoteIP,\n status: res.statusCode,\n error: res.locals._verdaccio_error,\n bytes: {\n in: bytesin,\n out: bytesout,\n },\n },\n message\n );\n }\n req.originalUrl = req.url;\n };\n\n req.on('close', function (): void {\n log();\n });\n\n const _end = res.end;\n // @ts-ignore\n res.end = function (...args): void {\n if (args[0]) {\n bytesout += args[0].length;\n }\n // @ts-ignore\n _end.apply(res, args);\n log();\n };\n next();\n };\n};\n"],"mappings":";;;;AAOA,IAAM,QAAQ,WAAW,0BAA0B;AAEnD,SAAS,gBAAgB,KAAsB;CAC7C,OAAO,IAAI,WAAW,YAAY;AACpC;AAGA,IAAa,qBACX;AACF,IAAa,sBAAsB,GAAG,mBAAmB;AACzD,IAAa,sBAAsB,GAAG,mBAAmB;AAQzD,IAAa,OAAO,QAAQ,UAAsB,CAAC,MAAM;CACvD,MAAM,EAAE,iBAAiB,SAAS;CAElC,OAAO,SAAS,IAAI,KAAqB,KAAsB,MAA8B;EAE3F,IAAI,MAAM,OAAO,MAAM,EAAE,KAAK,KAAK,CAAC;EAEpC,MAAM,QAAQ,IAAI,QAAQ;EAC1B,IAAI,MAAM,KAAK,MAAM,OACnB,IAAI,QAAQ,gBAAgB;EAG9B,MAAM,UAAU,IAAI,IAAI,QAAQ;EAChC,IAAI,MAAM,OAAO,MAAM,OACrB,IAAI,QAAQ,SAAS;EAGvB,IAAI,MAAM,IAAI;EACd,MAAM,WAAW,kBAAkB,gBAAgB,IAAI,GAAG;EAC1D,IAAI,UACF,MAAM,8CAA8C;GAAE,IAAI,IAAI;GAAI;EAAI,CAAC;OAEvE,IAAI,IAAI,KAAK;GAAO;GAAK,IAAI,IAAI;EAAG,GAAG,4CAA4C;EAErF,IAAI,cAAc,IAAI;EAEtB,IAAI,MAAM,KAAK,MAAM,OACnB,IAAI,QAAQ,gBAAgB;EAG9B,IAAI,MAAM,OAAO,MAAM,OACrB,IAAI,QAAQ,SAAS;EAGvB,IAAI,UAAU;EACd,IAAI,GAAG,QAAQ,SAAU,OAAa;GACpC,WAAW,MAAM;EACnB,CAAC;EAED,IAAI,WAAW;EACf,MAAM,SAAS,IAAI;EAEnB,IAAI,QAAQ,SAAU,GAAG,MAAe;GACtC,YAAY,KAAK,IAAI,UAAU;GAE/B,OAAO,OAAO,MAAM,KAAK,IAAI;EAC/B;EAEA,MAAM,MAAM,WAAkB;GAC5B,MAAM,eAAe,IAAI,IAAI,QAAQ,aAAa;GAClD,MAAM,gBAAgB,IAAI,OAAO;GACjC,MAAM,WAAW,eAAe,GAAG,aAAa,OAAO,kBAAkB;GACzE,IAAI;GACJ,IAAI,IAAI,OAAO,kBACb,UAAU;QAEV,UAAU;GAGZ,IAAI,MAAM,IAAI;GACd,IAAI,UACF,MAAM,SAAS;IACb,SAAS;KAAE,QAAQ,IAAI;KAAQ,KAAK,IAAI;IAAI;IAC5C,MAAM,IAAI,aAAa,QAAQ;IAC/B;IACA,QAAQ,IAAI;IACZ,OAAO,IAAI,OAAO;IAClB,OAAO;KAAE,IAAI;KAAS,KAAK;IAAS;GACtC,CAAC;QAED,IAAI,IAAI,KACN;IACE,SAAS;KACP,QAAQ,IAAI;KACZ,KAAK,IAAI;IACX;IACA,MAAM,IAAI,aAAa,QAAQ;IAC/B;IACA,QAAQ,IAAI;IACZ,OAAO,IAAI,OAAO;IAClB,OAAO;KACL,IAAI;KACJ,KAAK;IACP;GACF,GACA,OACF;GAEF,IAAI,cAAc,IAAI;EACxB;EAEA,IAAI,GAAG,SAAS,WAAkB;GAChC,IAAI;EACN,CAAC;EAED,MAAM,OAAO,IAAI;EAEjB,IAAI,MAAM,SAAU,GAAG,MAAY;GACjC,IAAI,KAAK,IACP,YAAY,KAAK,GAAG;GAGtB,KAAK,MAAM,KAAK,IAAI;GACpB,IAAI;EACN;EACA,KAAK;CACP;AACF"}
1
+ {"version":3,"file":"log.mjs","names":[],"sources":["../../src/middlewares/log.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { isNil } from 'lodash-es';\n\nimport { HEADERS, constants } from '@verdaccio/core';\n\nimport type { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';\n\nconst debug = buildDebug('verdaccio:middleware:log');\n\nfunction isStaticRequest(url: string): boolean {\n return url.startsWith('/-/static/') || url.startsWith('/favicon');\n}\n\n// Keep export of constants for backward compatibility\nexport const LOG_STATUS_MESSAGE = constants.LOG_STATUS_MESSAGE;\nexport const LOG_VERDACCIO_ERROR = constants.LOG_VERDACCIO_ERROR;\nexport const LOG_VERDACCIO_BYTES = constants.LOG_VERDACCIO_BYTES;\n\n// Converts all @{...} to %o for debug compatibility\nfunction convertToDebugString(template: string): string {\n return template.replace(/@\\{[^}]+\\}/g, '%o');\n}\n\nexport type LogOptions = {\n // When true, static file requests (/-/static/*) are hidden from pino logs\n // and only visible via DEBUG=verdaccio:middleware:log. Defaults to true.\n hideStaticLogs?: boolean;\n};\n\nexport const log = (logger, options: LogOptions = {}) => {\n const { hideStaticLogs = true } = options;\n\n return function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {\n // logger\n req.log = logger.child({ sub: 'in' });\n\n const _auth = req.headers.authorization;\n if (isNil(_auth) === false) {\n req.headers.authorization = '<Classified>';\n }\n\n const _cookie = req.get('cookie');\n if (isNil(_cookie) === false) {\n req.headers.cookie = '<Classified>';\n }\n\n req.url = req.originalUrl;\n const _skipLog = hideStaticLogs && isStaticRequest(req.url);\n if (_skipLog) {\n if (debug.enabled) {\n debug(convertToDebugString(constants.LOG_REQUEST_MESSAGE), req.ip, req.method, req.url);\n }\n } else {\n req.log.info({ req, ip: req.ip }, constants.LOG_REQUEST_MESSAGE);\n }\n req.originalUrl = req.url;\n\n if (isNil(_auth) === false) {\n req.headers.authorization = _auth;\n }\n\n if (isNil(_cookie) === false) {\n req.headers.cookie = _cookie;\n }\n\n let bytesin = 0;\n req.on('data', function (chunk): void {\n bytesin += chunk.length;\n });\n\n let bytesout = 0;\n const _write = res.write;\n res.write = function (...args): boolean {\n bytesout += args[0]?.length || 0;\n // @ts-ignore\n return _write.apply(res, args);\n };\n\n // Track if the request completed normally\n let requestCompleted = false;\n let abortLogged = false;\n\n const getRequestContext = () => {\n const forwardedFor = req.get(HEADERS.FORWARDED_FOR);\n const remoteAddress = req.socket.remoteAddress;\n const remoteIP = forwardedFor ? `${forwardedFor} via ${remoteAddress}` : remoteAddress;\n return {\n request: {\n method: req.method,\n url: req.originalUrl,\n },\n user: req.remote_user?.name || null,\n remoteIP,\n status: res.statusCode,\n error: res.locals._verdaccio_error,\n bytes: {\n in: bytesin,\n out: bytesout,\n },\n };\n };\n\n const logAbortedRequest = () => {\n if (abortLogged || requestCompleted) return;\n abortLogged = true;\n\n const context = {\n ...getRequestContext(),\n status: constants.HTTP_STATUS.CLIENT_CLOSED_REQUEST,\n };\n\n if (_skipLog) {\n if (debug.enabled) {\n debug(\n convertToDebugString(constants.LOG_VERDACCIO_ABORT),\n context.status,\n context.user,\n context.remoteIP,\n context.request.method,\n context.request.url\n );\n }\n } else {\n req.log.info(context, constants.LOG_VERDACCIO_ABORT);\n }\n };\n\n const cleanupSocketListeners = () => {\n req.socket.removeListener('close', onClose);\n req.socket.removeListener('error', onError);\n };\n\n const onClose = () => {\n if (!requestCompleted) {\n logAbortedRequest();\n }\n cleanupSocketListeners();\n };\n\n const onError = () => {\n if (!requestCompleted) {\n logAbortedRequest();\n }\n cleanupSocketListeners();\n };\n\n const logCompletedRequest = () => {\n requestCompleted = true;\n\n const context = getRequestContext();\n const message = context.error ? constants.LOG_VERDACCIO_ERROR : constants.LOG_VERDACCIO_BYTES;\n\n if (_skipLog) {\n if (debug.enabled) {\n if (context.error) {\n debug(\n convertToDebugString(message),\n context.status,\n context.user,\n context.remoteIP,\n context.request.method,\n context.request.url,\n context.error\n );\n } else {\n debug(\n convertToDebugString(message),\n context.status,\n context.user,\n context.remoteIP,\n context.request.method,\n context.request.url,\n context.bytes.in,\n context.bytes.out\n );\n }\n }\n } else {\n req.log.http(context, message);\n }\n\n cleanupSocketListeners();\n };\n\n const _end = res.end;\n // @ts-ignore\n res.end = function (...args): void {\n if (args[0]) {\n bytesout += args[0].length;\n }\n // @ts-ignore\n _end.apply(res, args);\n logCompletedRequest();\n };\n\n req.socket.on('close', onClose);\n req.socket.on('error', onError);\n\n next();\n };\n};\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';
1
+ import type { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';
2
2
  /**
3
3
  * Removes the host from the URL and turns it into a relative URL.
4
4
  * @param req
@@ -1 +1 @@
1
- {"version":3,"file":"make-url-relative.js","names":[],"sources":["../../src/middlewares/make-url-relative.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { URL } from 'node:url';\n\nimport { errorUtils } from '@verdaccio/core';\n\nimport type { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';\n\nconst debug = buildDebug('verdaccio:middleware:make-url-relative');\n\n/**\n * Removes the host from the URL and turns it into a relative URL.\n * @param req\n * @param res\n * @param next\n */\nexport function makeURLrelative(\n req: $RequestExtend,\n res: $ResponseExtend,\n next: $NextFunctionVer\n): void {\n const original = req.url;\n\n // npm requests can contain the full URL, including the hostname, for example:\n // tarball downloads. Removing the hostname makes the URL relative and allows\n // the application to handle requests in a more consistent way.\n\n let url;\n try {\n // In productive use, the URL is absolute (and base will be ignored)\n // In tests, the URL might brelative (and base will be used)\n // https://nodejs.org/docs/latest/api/url.html#new-urlinput-base\n url = new URL(req.url, `${req.protocol}://${req.headers.host}/`);\n } catch (error) {\n return next(errorUtils.getBadRequest(`Invalid URL: ${req.url} (${error})`));\n }\n\n // Rebuild the URL without hostname\n req.url = url.pathname + url.search + url.hash;\n\n if (original !== req.url) {\n debug('makeURLrelative: %o -> %o', original, req.url);\n } else {\n debug('makeURLrelative: %o (unchanged)', original);\n }\n next();\n}\n"],"mappings":";;;;;;AAOA,IAAM,WAAA,GAAA,MAAA,SAAmB,wCAAwC;;;;;;;AAQjE,SAAgB,gBACd,KACA,KACA,MACM;CACN,MAAM,WAAW,IAAI;CAMrB,IAAI;CACJ,IAAI;EAIF,MAAM,IAAI,SAAA,IAAI,IAAI,KAAK,GAAG,IAAI,SAAS,KAAK,IAAI,QAAQ,KAAK,EAAE;CACjE,SAAS,OAAO;EACd,OAAO,KAAK,gBAAA,WAAW,cAAc,gBAAgB,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;CAC5E;CAGA,IAAI,MAAM,IAAI,WAAW,IAAI,SAAS,IAAI;CAE1C,IAAI,aAAa,IAAI,KACnB,QAAM,6BAA6B,UAAU,IAAI,GAAG;MAEpD,QAAM,mCAAmC,QAAQ;CAEnD,KAAK;AACP"}
1
+ {"version":3,"file":"make-url-relative.js","names":[],"sources":["../../src/middlewares/make-url-relative.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { URL } from 'node:url';\n\nimport { errorUtils } from '@verdaccio/core';\n\nimport type { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';\n\nconst debug = buildDebug('verdaccio:middleware:make-url-relative');\n\n/**\n * Removes the host from the URL and turns it into a relative URL.\n * @param req\n * @param res\n * @param next\n */\nexport function makeURLrelative(\n req: $RequestExtend,\n res: $ResponseExtend,\n next: $NextFunctionVer\n): void {\n const original = req.url;\n\n // npm requests can contain the full URL, including the hostname, for example:\n // tarball downloads. Removing the hostname makes the URL relative and allows\n // the application to handle requests in a more consistent way.\n\n let url;\n try {\n // In productive use, the URL is absolute (and base will be ignored)\n // In tests, the URL might brelative (and base will be used)\n // https://nodejs.org/docs/latest/api/url.html#new-urlinput-base\n url = new URL(req.url, `${req.protocol}://${req.headers.host}/`);\n } catch (error) {\n return next(errorUtils.getBadRequest(`Invalid URL: ${req.url} (${error})`));\n }\n\n // Rebuild the URL without hostname\n req.url = url.pathname + url.search + url.hash;\n\n if (original !== req.url) {\n debug('makeURLrelative: %o -> %o', original, req.url);\n } else {\n debug('makeURLrelative: %o (unchanged)', original);\n }\n next();\n}\n"],"mappings":";;;;;;AAOA,IAAM,WAAA,GAAA,MAAA,QAAA,CAAmB,wCAAwC;;;;;;;AAQjE,SAAgB,gBACd,KACA,KACA,MACM;CACN,MAAM,WAAW,IAAI;CAMrB,IAAI;CACJ,IAAI;EAIF,MAAM,IAAI,SAAA,IAAI,IAAI,KAAK,GAAG,IAAI,SAAS,KAAK,IAAI,QAAQ,KAAK,EAAE;CACjE,SAAS,OAAO;EACd,OAAO,KAAK,gBAAA,WAAW,cAAc,gBAAgB,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;CAC5E;CAGA,IAAI,MAAM,IAAI,WAAW,IAAI,SAAS,IAAI;CAE1C,IAAI,aAAa,IAAI,KACnB,QAAM,6BAA6B,UAAU,IAAI,GAAG;MAEpD,QAAM,mCAAmC,QAAQ;CAEnD,KAAK;AACP"}
@@ -1 +1 @@
1
- {"version":3,"file":"media.js","names":[],"sources":["../../src/middlewares/media.ts"],"sourcesContent":["import { HEADER_TYPE, HTTP_STATUS, errorUtils } from '@verdaccio/core';\n\nimport type { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';\n\nexport function media(expect: string | null): any {\n return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {\n const header = req.headers[HEADER_TYPE.CONTENT_TYPE];\n if (!header) {\n next(\n errorUtils.getCode(\n HTTP_STATUS.UNSUPPORTED_MEDIA,\n 'content-type is missing, expect: ' + expect\n )\n );\n return;\n }\n\n if (typeof header !== 'string' || header.split(';')[0].trim() !== expect) {\n next(\n errorUtils.getCode(\n HTTP_STATUS.UNSUPPORTED_MEDIA,\n 'wrong content-type, expect: ' + expect + ', got: ' + req.get(HEADER_TYPE.CONTENT_TYPE)\n )\n );\n } else {\n next();\n }\n };\n}\n"],"mappings":";;AAIA,SAAgB,MAAM,QAA4B;CAChD,OAAO,SAAU,KAAqB,KAAsB,MAA8B;EACxF,MAAM,SAAS,IAAI,QAAQ,gBAAA,YAAY;EACvC,IAAI,CAAC,QAAQ;GACX,KACE,gBAAA,WAAW,QACT,gBAAA,YAAY,mBACZ,sCAAsC,MACxC,CACF;GACA;EACF;EAEA,IAAI,OAAO,WAAW,YAAY,OAAO,MAAM,GAAG,EAAE,GAAG,KAAK,MAAM,QAChE,KACE,gBAAA,WAAW,QACT,gBAAA,YAAY,mBACZ,iCAAiC,SAAS,YAAY,IAAI,IAAI,gBAAA,YAAY,YAAY,CACxF,CACF;OAEA,KAAK;CAET;AACF"}
1
+ {"version":3,"file":"media.js","names":[],"sources":["../../src/middlewares/media.ts"],"sourcesContent":["import { HEADER_TYPE, HTTP_STATUS, errorUtils } from '@verdaccio/core';\n\nimport type { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';\n\nexport function media(expect: string | null): any {\n return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {\n const header = req.headers[HEADER_TYPE.CONTENT_TYPE];\n if (!header) {\n next(\n errorUtils.getCode(\n HTTP_STATUS.UNSUPPORTED_MEDIA,\n 'content-type is missing, expect: ' + expect\n )\n );\n return;\n }\n\n if (typeof header !== 'string' || header.split(';')[0].trim() !== expect) {\n next(\n errorUtils.getCode(\n HTTP_STATUS.UNSUPPORTED_MEDIA,\n 'wrong content-type, expect: ' + expect + ', got: ' + req.get(HEADER_TYPE.CONTENT_TYPE)\n )\n );\n } else {\n next();\n }\n };\n}\n"],"mappings":";;AAIA,SAAgB,MAAM,QAA4B;CAChD,OAAO,SAAU,KAAqB,KAAsB,MAA8B;EACxF,MAAM,SAAS,IAAI,QAAQ,gBAAA,YAAY;EACvC,IAAI,CAAC,QAAQ;GACX,KACE,gBAAA,WAAW,QACT,gBAAA,YAAY,mBACZ,sCAAsC,MACxC,CACF;GACA;EACF;EAEA,IAAI,OAAO,WAAW,YAAY,OAAO,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,QAChE,KACE,gBAAA,WAAW,QACT,gBAAA,YAAY,mBACZ,iCAAiC,SAAS,YAAY,IAAI,IAAI,gBAAA,YAAY,YAAY,CACxF,CACF;OAEA,KAAK;CAET;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"media.mjs","names":[],"sources":["../../src/middlewares/media.ts"],"sourcesContent":["import { HEADER_TYPE, HTTP_STATUS, errorUtils } from '@verdaccio/core';\n\nimport type { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';\n\nexport function media(expect: string | null): any {\n return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {\n const header = req.headers[HEADER_TYPE.CONTENT_TYPE];\n if (!header) {\n next(\n errorUtils.getCode(\n HTTP_STATUS.UNSUPPORTED_MEDIA,\n 'content-type is missing, expect: ' + expect\n )\n );\n return;\n }\n\n if (typeof header !== 'string' || header.split(';')[0].trim() !== expect) {\n next(\n errorUtils.getCode(\n HTTP_STATUS.UNSUPPORTED_MEDIA,\n 'wrong content-type, expect: ' + expect + ', got: ' + req.get(HEADER_TYPE.CONTENT_TYPE)\n )\n );\n } else {\n next();\n }\n };\n}\n"],"mappings":";;AAIA,SAAgB,MAAM,QAA4B;CAChD,OAAO,SAAU,KAAqB,KAAsB,MAA8B;EACxF,MAAM,SAAS,IAAI,QAAQ,YAAY;EACvC,IAAI,CAAC,QAAQ;GACX,KACE,WAAW,QACT,YAAY,mBACZ,sCAAsC,MACxC,CACF;GACA;EACF;EAEA,IAAI,OAAO,WAAW,YAAY,OAAO,MAAM,GAAG,EAAE,GAAG,KAAK,MAAM,QAChE,KACE,WAAW,QACT,YAAY,mBACZ,iCAAiC,SAAS,YAAY,IAAI,IAAI,YAAY,YAAY,CACxF,CACF;OAEA,KAAK;CAET;AACF"}
1
+ {"version":3,"file":"media.mjs","names":[],"sources":["../../src/middlewares/media.ts"],"sourcesContent":["import { HEADER_TYPE, HTTP_STATUS, errorUtils } from '@verdaccio/core';\n\nimport type { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';\n\nexport function media(expect: string | null): any {\n return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {\n const header = req.headers[HEADER_TYPE.CONTENT_TYPE];\n if (!header) {\n next(\n errorUtils.getCode(\n HTTP_STATUS.UNSUPPORTED_MEDIA,\n 'content-type is missing, expect: ' + expect\n )\n );\n return;\n }\n\n if (typeof header !== 'string' || header.split(';')[0].trim() !== expect) {\n next(\n errorUtils.getCode(\n HTTP_STATUS.UNSUPPORTED_MEDIA,\n 'wrong content-type, expect: ' + expect + ', got: ' + req.get(HEADER_TYPE.CONTENT_TYPE)\n )\n );\n } else {\n next();\n }\n };\n}\n"],"mappings":";;AAIA,SAAgB,MAAM,QAA4B;CAChD,OAAO,SAAU,KAAqB,KAAsB,MAA8B;EACxF,MAAM,SAAS,IAAI,QAAQ,YAAY;EACvC,IAAI,CAAC,QAAQ;GACX,KACE,WAAW,QACT,YAAY,mBACZ,sCAAsC,MACxC,CACF;GACA;EACF;EAEA,IAAI,OAAO,WAAW,YAAY,OAAO,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,QAChE,KACE,WAAW,QACT,YAAY,mBACZ,iCAAiC,SAAS,YAAY,IAAI,IAAI,YAAY,YAAY,CACxF,CACF;OAEA,KAAK;CAET;AACF"}
@@ -1,2 +1,2 @@
1
- import { RateLimit as RateLimitType } from '@verdaccio/types';
2
- export declare function rateLimit(rateLimitOptions?: RateLimitType): import('express-rate-limit').RateLimitRequestHandler;
1
+ import type { RateLimit as RateLimitType } from '@verdaccio/types';
2
+ export declare function rateLimit(rateLimitOptions?: RateLimitType): import("express-rate-limit").RateLimitRequestHandler;
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.js","names":[],"sources":["../../src/middlewares/rate-limit.ts"],"sourcesContent":["import RateLimit from 'express-rate-limit';\n\nimport type { RateLimit as RateLimitType } from '@verdaccio/types';\n\nexport function rateLimit(rateLimitOptions?: RateLimitType) {\n const limiter = RateLimit(rateLimitOptions);\n return limiter;\n}\n"],"mappings":";;;;AAIA,SAAgB,UAAU,kBAAkC;CAE1D,QAAA,GAAA,mBAAA,SAD0B,gBACnB;AACT"}
1
+ {"version":3,"file":"rate-limit.js","names":[],"sources":["../../src/middlewares/rate-limit.ts"],"sourcesContent":["import RateLimit from 'express-rate-limit';\n\nimport type { RateLimit as RateLimitType } from '@verdaccio/types';\n\nexport function rateLimit(rateLimitOptions?: RateLimitType) {\n const limiter = RateLimit(rateLimitOptions);\n return limiter;\n}\n"],"mappings":";;;;AAIA,SAAgB,UAAU,kBAAkC;CAE1D,QAAA,GAAA,mBAAA,QAAA,CAD0B,gBACnB;AACT"}
@@ -1,3 +1,3 @@
1
- import { RequestOptions } from '@verdaccio/url';
2
- import { $RequestExtend } from '../types';
1
+ import type { RequestOptions } from '@verdaccio/url';
2
+ import type { $RequestExtend } from '../types';
3
3
  export declare function getRequestOptions(req: $RequestExtend): RequestOptions;
@@ -1,10 +1,6 @@
1
- const require_runtime = require("../_virtual/_rolldown/runtime.js");
2
- let debug = require("debug");
3
- debug = require_runtime.__toESM(debug);
4
1
  //#region src/middlewares/request-options.ts
5
- var debug$1 = (0, debug.default)("verdaccio:middleware:request-options");
6
2
  function getRequestOptions(req) {
7
- const requestOptions = {
3
+ return {
8
4
  host: req.host,
9
5
  protocol: req.protocol,
10
6
  headers: req.headers,
@@ -12,8 +8,6 @@ function getRequestOptions(req) {
12
8
  byPassCache: req.query.write === "true",
13
9
  username: req.remote_user?.name ?? void 0
14
10
  };
15
- debug$1("request options: %o", requestOptions);
16
- return requestOptions;
17
11
  }
18
12
  //#endregion
19
13
  exports.getRequestOptions = getRequestOptions;
@@ -1 +1 @@
1
- {"version":3,"file":"request-options.js","names":[],"sources":["../../src/middlewares/request-options.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport type { IncomingHttpHeaders } from 'node:http';\n\nimport type { RequestOptions } from '@verdaccio/url';\n\nimport type { $RequestExtend } from '../types';\n\nconst debug = buildDebug('verdaccio:middleware:request-options');\n\nexport function getRequestOptions(req: $RequestExtend): RequestOptions {\n const requestOptions = {\n // FIXME: decide if host should contain port or not\n //\n // Express 4:\n // - req.host is marked as deprecated since it does not include the port\n // - use req.headers['host'] instead, which includes the port\n // Express 5:\n // - req.host is fully supported and includes the port\n // - https://expressjs.com/en/api.html#req.host\n host: req.host,\n protocol: req.protocol,\n headers: req.headers as IncomingHttpHeaders,\n remoteAddress: req.socket.remoteAddress,\n byPassCache: req.query.write === 'true',\n username: req.remote_user?.name ?? undefined,\n };\n\n debug('request options: %o', requestOptions);\n\n return requestOptions;\n}\n"],"mappings":";;;;AAOA,IAAM,WAAA,GAAA,MAAA,SAAmB,sCAAsC;AAE/D,SAAgB,kBAAkB,KAAqC;CACrE,MAAM,iBAAiB;EASrB,MAAM,IAAI;EACV,UAAU,IAAI;EACd,SAAS,IAAI;EACb,eAAe,IAAI,OAAO;EAC1B,aAAa,IAAI,MAAM,UAAU;EACjC,UAAU,IAAI,aAAa,QAAQ,KAAA;CACrC;CAEA,QAAM,uBAAuB,cAAc;CAE3C,OAAO;AACT"}
1
+ {"version":3,"file":"request-options.js","names":[],"sources":["../../src/middlewares/request-options.ts"],"sourcesContent":["import type { IncomingHttpHeaders } from 'node:http';\n\nimport type { RequestOptions } from '@verdaccio/url';\n\nimport type { $RequestExtend } from '../types';\n\nexport function getRequestOptions(req: $RequestExtend): RequestOptions {\n return {\n // Express 5:\n // - req.host is fully supported and includes the port\n // - https://expressjs.com/en/api.html#req.host\n host: req.host,\n protocol: req.protocol,\n headers: req.headers as IncomingHttpHeaders,\n remoteAddress: req.socket.remoteAddress,\n byPassCache: req.query.write === 'true',\n username: req.remote_user?.name ?? undefined,\n };\n}\n"],"mappings":";AAMA,SAAgB,kBAAkB,KAAqC;CACrE,OAAO;EAIL,MAAM,IAAI;EACV,UAAU,IAAI;EACd,SAAS,IAAI;EACb,eAAe,IAAI,OAAO;EAC1B,aAAa,IAAI,MAAM,UAAU;EACjC,UAAU,IAAI,aAAa,QAAQ,KAAA;CACrC;AACF"}
@@ -1,8 +1,6 @@
1
- import buildDebug from "debug";
2
1
  //#region src/middlewares/request-options.ts
3
- var debug = buildDebug("verdaccio:middleware:request-options");
4
2
  function getRequestOptions(req) {
5
- const requestOptions = {
3
+ return {
6
4
  host: req.host,
7
5
  protocol: req.protocol,
8
6
  headers: req.headers,
@@ -10,8 +8,6 @@ function getRequestOptions(req) {
10
8
  byPassCache: req.query.write === "true",
11
9
  username: req.remote_user?.name ?? void 0
12
10
  };
13
- debug("request options: %o", requestOptions);
14
- return requestOptions;
15
11
  }
16
12
  //#endregion
17
13
  export { getRequestOptions };
@@ -1 +1 @@
1
- {"version":3,"file":"request-options.mjs","names":[],"sources":["../../src/middlewares/request-options.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport type { IncomingHttpHeaders } from 'node:http';\n\nimport type { RequestOptions } from '@verdaccio/url';\n\nimport type { $RequestExtend } from '../types';\n\nconst debug = buildDebug('verdaccio:middleware:request-options');\n\nexport function getRequestOptions(req: $RequestExtend): RequestOptions {\n const requestOptions = {\n // FIXME: decide if host should contain port or not\n //\n // Express 4:\n // - req.host is marked as deprecated since it does not include the port\n // - use req.headers['host'] instead, which includes the port\n // Express 5:\n // - req.host is fully supported and includes the port\n // - https://expressjs.com/en/api.html#req.host\n host: req.host,\n protocol: req.protocol,\n headers: req.headers as IncomingHttpHeaders,\n remoteAddress: req.socket.remoteAddress,\n byPassCache: req.query.write === 'true',\n username: req.remote_user?.name ?? undefined,\n };\n\n debug('request options: %o', requestOptions);\n\n return requestOptions;\n}\n"],"mappings":";;AAOA,IAAM,QAAQ,WAAW,sCAAsC;AAE/D,SAAgB,kBAAkB,KAAqC;CACrE,MAAM,iBAAiB;EASrB,MAAM,IAAI;EACV,UAAU,IAAI;EACd,SAAS,IAAI;EACb,eAAe,IAAI,OAAO;EAC1B,aAAa,IAAI,MAAM,UAAU;EACjC,UAAU,IAAI,aAAa,QAAQ,KAAA;CACrC;CAEA,MAAM,uBAAuB,cAAc;CAE3C,OAAO;AACT"}
1
+ {"version":3,"file":"request-options.mjs","names":[],"sources":["../../src/middlewares/request-options.ts"],"sourcesContent":["import type { IncomingHttpHeaders } from 'node:http';\n\nimport type { RequestOptions } from '@verdaccio/url';\n\nimport type { $RequestExtend } from '../types';\n\nexport function getRequestOptions(req: $RequestExtend): RequestOptions {\n return {\n // Express 5:\n // - req.host is fully supported and includes the port\n // - https://expressjs.com/en/api.html#req.host\n host: req.host,\n protocol: req.protocol,\n headers: req.headers as IncomingHttpHeaders,\n remoteAddress: req.socket.remoteAddress,\n byPassCache: req.query.write === 'true',\n username: req.remote_user?.name ?? undefined,\n };\n}\n"],"mappings":";AAMA,SAAgB,kBAAkB,KAAqC;CACrE,OAAO;EAIL,MAAM,IAAI;EACV,UAAU,IAAI;EACd,SAAS,IAAI;EACb,eAAe,IAAI,OAAO;EAC1B,aAAa,IAAI,MAAM,UAAU;EACjC,UAAU,IAAI,aAAa,QAAQ,KAAA;CACrC;AACF"}
@@ -1,5 +1,5 @@
1
- import { RequestHandler } from 'express';
2
- import { Logger, Token } from '@verdaccio/types';
1
+ import type { RequestHandler } from 'express';
2
+ import type { Logger, Token } from '@verdaccio/types';
3
3
  /**
4
4
  * Minimal storage surface required to enforce generated token metadata. Typed
5
5
  * structurally so this package does not need to depend on `@verdaccio/store`.
@@ -1,6 +1,6 @@
1
1
  let _verdaccio_core = require("@verdaccio/core");
2
2
  //#region src/middlewares/token-auth.ts
3
- var WRITE_METHODS = new Set([
3
+ var WRITE_METHODS = /* @__PURE__ */ new Set([
4
4
  "DELETE",
5
5
  "PATCH",
6
6
  "POST",
@@ -1 +1 @@
1
- {"version":3,"file":"token-auth.js","names":[],"sources":["../../src/middlewares/token-auth.ts"],"sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from 'express';\n\nimport { API_ERROR, errorUtils, ipUtils } from '@verdaccio/core';\nimport type { Logger, Token } from '@verdaccio/types';\n\nimport type { $RequestExtend } from '../types';\n\nconst WRITE_METHODS = new Set(['DELETE', 'PATCH', 'POST', 'PUT']);\n\n/**\n * Minimal storage surface required to enforce generated token metadata. Typed\n * structurally so this package does not need to depend on `@verdaccio/store`.\n */\nexport interface TokenReadableStorage {\n /**\n * Load the generated tokens stored for a user.\n *\n * @param filter narrows the lookup to a single `user`\n * @returns the user's persisted tokens\n */\n readTokens(filter: { user: string }): Promise<Token[]>;\n}\n\n/**\n * Resolve the client address of an incoming request for CIDR matching.\n *\n * Uses Express' `req.ip`, which already honors the application's `trust proxy`\n * setting (`config.server.trustProxy`): when the operator has declared trusted\n * proxies, `req.ip` is the upstream-most untrusted address parsed from\n * `X-Forwarded-For`; otherwise it is the direct socket peer and the forwarded\n * header is ignored entirely.\n *\n * The middleware must NOT read `X-Forwarded-For` on its own — doing so\n * unconditionally would let any client spoof its address (and bypass a token's\n * CIDR whitelist) simply by sending the header. Falls back to the raw socket\n * address and is normalized (trimmed, IPv4-mapped IPv6 prefix stripped) via\n * {@link ipUtils.normalizeAddress}.\n *\n * @param req the incoming Express request\n * @returns the normalized client address, or `undefined` when none can be determined\n */\nfunction getClientAddress(req: Request): string | undefined {\n return ipUtils.normalizeAddress(req.ip || req.socket.remoteAddress);\n}\n\n/**\n * Find the stored token whose `key` matches the key carried by the request.\n *\n * @param tokens the tokens persisted for the authenticated user\n * @param tokenKey the key extracted from the presented generated token\n * @returns the matching {@link Token}, or `undefined` when it has been revoked / never existed\n */\nfunction findToken(tokens: Token[], tokenKey: string): Token | undefined {\n return tokens.find(({ key }) => key === tokenKey);\n}\n\n/**\n * Express middleware that enforces the metadata of npm-style generated tokens\n * (created via `POST /-/npm/v1/tokens`).\n *\n * A generated token embeds a server-issued `key` that is recovered from the\n * verified credentials as `remote_user.token.key`. This middleware looks that\n * key up in storage and rejects the request when the token:\n * - is missing from storage (revoked or never issued);\n * - is used from a client address outside its `cidr` whitelist;\n * - is `readonly` and the request uses a write method (`DELETE`/`PATCH`/`POST`/`PUT`).\n *\n * Requests that do not carry a generated token key (e.g. interactive logins)\n * pass through untouched. It must run after the JWT/auth middleware has\n * populated `remote_user`. Failures fail closed: a storage lookup error yields\n * an internal error rather than allowing the request.\n *\n * @param storage storage exposing {@link TokenReadableStorage.readTokens} to load the user's tokens\n * @param logger logger used to record rejected attempts and lookup failures\n * @returns an Express {@link RequestHandler} that calls `next()` to allow or `next(error)` to reject\n */\nexport function enforceGeneratedTokenMetadata(\n storage: TokenReadableStorage,\n logger: Logger\n): RequestHandler {\n return async function (req: Request, _res: Response, next: NextFunction): Promise<void> {\n const remoteUser = (req as $RequestExtend).remote_user;\n const tokenKey = remoteUser?.token?.key;\n\n if (!tokenKey) {\n return next();\n }\n\n const user = remoteUser?.name;\n\n if (typeof user !== 'string') {\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n try {\n const token = findToken(await storage.readTokens({ user }), tokenKey);\n\n if (!token) {\n logger.warn({ tokenKey, user }, 'generated token @{tokenKey} for user @{user} is missing');\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n if (!ipUtils.isAddressAllowed(getClientAddress(req), token.cidr)) {\n logger.warn(\n { tokenKey, user },\n 'generated token @{tokenKey} for user @{user} was used outside its CIDR whitelist'\n );\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n if (token.readonly && WRITE_METHODS.has(req.method)) {\n logger.warn(\n { tokenKey, user },\n 'readonly generated token @{tokenKey} for user @{user} was used for a write request'\n );\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n return next();\n } catch (error: any) {\n logger.error({ error: error.msg }, 'generated token metadata lookup failed: @{error}');\n return next(errorUtils.getInternalError(error.message));\n }\n };\n}\n"],"mappings":";;AAOA,IAAM,gBAAgB,IAAI,IAAI;CAAC;CAAU;CAAS;CAAQ;AAAK,CAAC;;;;;;;;;;;;;;;;;;;AAkChE,SAAS,iBAAiB,KAAkC;CAC1D,OAAO,gBAAA,QAAQ,iBAAiB,IAAI,MAAM,IAAI,OAAO,aAAa;AACpE;;;;;;;;AASA,SAAS,UAAU,QAAiB,UAAqC;CACvE,OAAO,OAAO,MAAM,EAAE,UAAU,QAAQ,QAAQ;AAClD;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,8BACd,SACA,QACgB;CAChB,OAAO,eAAgB,KAAc,MAAgB,MAAmC;EACtF,MAAM,aAAc,IAAuB;EAC3C,MAAM,WAAW,YAAY,OAAO;EAEpC,IAAI,CAAC,UACH,OAAO,KAAK;EAGd,MAAM,OAAO,YAAY;EAEzB,IAAI,OAAO,SAAS,UAClB,OAAO,KAAK,gBAAA,WAAW,aAAa,gBAAA,UAAU,mBAAmB,CAAC;EAGpE,IAAI;GACF,MAAM,QAAQ,UAAU,MAAM,QAAQ,WAAW,EAAE,KAAK,CAAC,GAAG,QAAQ;GAEpE,IAAI,CAAC,OAAO;IACV,OAAO,KAAK;KAAE;KAAU;IAAK,GAAG,yDAAyD;IACzF,OAAO,KAAK,gBAAA,WAAW,aAAa,gBAAA,UAAU,mBAAmB,CAAC;GACpE;GAEA,IAAI,CAAC,gBAAA,QAAQ,iBAAiB,iBAAiB,GAAG,GAAG,MAAM,IAAI,GAAG;IAChE,OAAO,KACL;KAAE;KAAU;IAAK,GACjB,kFACF;IACA,OAAO,KAAK,gBAAA,WAAW,aAAa,gBAAA,UAAU,mBAAmB,CAAC;GACpE;GAEA,IAAI,MAAM,YAAY,cAAc,IAAI,IAAI,MAAM,GAAG;IACnD,OAAO,KACL;KAAE;KAAU;IAAK,GACjB,oFACF;IACA,OAAO,KAAK,gBAAA,WAAW,aAAa,gBAAA,UAAU,mBAAmB,CAAC;GACpE;GAEA,OAAO,KAAK;EACd,SAAS,OAAY;GACnB,OAAO,MAAM,EAAE,OAAO,MAAM,IAAI,GAAG,kDAAkD;GACrF,OAAO,KAAK,gBAAA,WAAW,iBAAiB,MAAM,OAAO,CAAC;EACxD;CACF;AACF"}
1
+ {"version":3,"file":"token-auth.js","names":[],"sources":["../../src/middlewares/token-auth.ts"],"sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from 'express';\n\nimport { API_ERROR, errorUtils, ipUtils } from '@verdaccio/core';\nimport type { Logger, Token } from '@verdaccio/types';\n\nimport type { $RequestExtend } from '../types';\n\nconst WRITE_METHODS = new Set(['DELETE', 'PATCH', 'POST', 'PUT']);\n\n/**\n * Minimal storage surface required to enforce generated token metadata. Typed\n * structurally so this package does not need to depend on `@verdaccio/store`.\n */\nexport interface TokenReadableStorage {\n /**\n * Load the generated tokens stored for a user.\n *\n * @param filter narrows the lookup to a single `user`\n * @returns the user's persisted tokens\n */\n readTokens(filter: { user: string }): Promise<Token[]>;\n}\n\n/**\n * Resolve the client address of an incoming request for CIDR matching.\n *\n * Uses Express' `req.ip`, which already honors the application's `trust proxy`\n * setting (`config.server.trustProxy`): when the operator has declared trusted\n * proxies, `req.ip` is the upstream-most untrusted address parsed from\n * `X-Forwarded-For`; otherwise it is the direct socket peer and the forwarded\n * header is ignored entirely.\n *\n * The middleware must NOT read `X-Forwarded-For` on its own — doing so\n * unconditionally would let any client spoof its address (and bypass a token's\n * CIDR whitelist) simply by sending the header. Falls back to the raw socket\n * address and is normalized (trimmed, IPv4-mapped IPv6 prefix stripped) via\n * {@link ipUtils.normalizeAddress}.\n *\n * @param req the incoming Express request\n * @returns the normalized client address, or `undefined` when none can be determined\n */\nfunction getClientAddress(req: Request): string | undefined {\n return ipUtils.normalizeAddress(req.ip || req.socket.remoteAddress);\n}\n\n/**\n * Find the stored token whose `key` matches the key carried by the request.\n *\n * @param tokens the tokens persisted for the authenticated user\n * @param tokenKey the key extracted from the presented generated token\n * @returns the matching {@link Token}, or `undefined` when it has been revoked / never existed\n */\nfunction findToken(tokens: Token[], tokenKey: string): Token | undefined {\n return tokens.find(({ key }) => key === tokenKey);\n}\n\n/**\n * Express middleware that enforces the metadata of npm-style generated tokens\n * (created via `POST /-/npm/v1/tokens`).\n *\n * A generated token embeds a server-issued `key` that is recovered from the\n * verified credentials as `remote_user.token.key`. This middleware looks that\n * key up in storage and rejects the request when the token:\n * - is missing from storage (revoked or never issued);\n * - is used from a client address outside its `cidr` whitelist;\n * - is `readonly` and the request uses a write method (`DELETE`/`PATCH`/`POST`/`PUT`).\n *\n * Requests that do not carry a generated token key (e.g. interactive logins)\n * pass through untouched. It must run after the JWT/auth middleware has\n * populated `remote_user`. Failures fail closed: a storage lookup error yields\n * an internal error rather than allowing the request.\n *\n * @param storage storage exposing {@link TokenReadableStorage.readTokens} to load the user's tokens\n * @param logger logger used to record rejected attempts and lookup failures\n * @returns an Express {@link RequestHandler} that calls `next()` to allow or `next(error)` to reject\n */\nexport function enforceGeneratedTokenMetadata(\n storage: TokenReadableStorage,\n logger: Logger\n): RequestHandler {\n return async function (req: Request, _res: Response, next: NextFunction): Promise<void> {\n const remoteUser = (req as $RequestExtend).remote_user;\n const tokenKey = remoteUser?.token?.key;\n\n if (!tokenKey) {\n return next();\n }\n\n const user = remoteUser?.name;\n\n if (typeof user !== 'string') {\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n try {\n const token = findToken(await storage.readTokens({ user }), tokenKey);\n\n if (!token) {\n logger.warn({ tokenKey, user }, 'generated token @{tokenKey} for user @{user} is missing');\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n if (!ipUtils.isAddressAllowed(getClientAddress(req), token.cidr)) {\n logger.warn(\n { tokenKey, user },\n 'generated token @{tokenKey} for user @{user} was used outside its CIDR whitelist'\n );\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n if (token.readonly && WRITE_METHODS.has(req.method)) {\n logger.warn(\n { tokenKey, user },\n 'readonly generated token @{tokenKey} for user @{user} was used for a write request'\n );\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n return next();\n } catch (error: any) {\n logger.error({ error: error.msg }, 'generated token metadata lookup failed: @{error}');\n return next(errorUtils.getInternalError(error.message));\n }\n };\n}\n"],"mappings":";;AAOA,IAAM,gCAAgB,IAAI,IAAI;CAAC;CAAU;CAAS;CAAQ;AAAK,CAAC;;;;;;;;;;;;;;;;;;;AAkChE,SAAS,iBAAiB,KAAkC;CAC1D,OAAO,gBAAA,QAAQ,iBAAiB,IAAI,MAAM,IAAI,OAAO,aAAa;AACpE;;;;;;;;AASA,SAAS,UAAU,QAAiB,UAAqC;CACvE,OAAO,OAAO,MAAM,EAAE,UAAU,QAAQ,QAAQ;AAClD;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,8BACd,SACA,QACgB;CAChB,OAAO,eAAgB,KAAc,MAAgB,MAAmC;EACtF,MAAM,aAAc,IAAuB;EAC3C,MAAM,WAAW,YAAY,OAAO;EAEpC,IAAI,CAAC,UACH,OAAO,KAAK;EAGd,MAAM,OAAO,YAAY;EAEzB,IAAI,OAAO,SAAS,UAClB,OAAO,KAAK,gBAAA,WAAW,aAAa,gBAAA,UAAU,mBAAmB,CAAC;EAGpE,IAAI;GACF,MAAM,QAAQ,UAAU,MAAM,QAAQ,WAAW,EAAE,KAAK,CAAC,GAAG,QAAQ;GAEpE,IAAI,CAAC,OAAO;IACV,OAAO,KAAK;KAAE;KAAU;IAAK,GAAG,yDAAyD;IACzF,OAAO,KAAK,gBAAA,WAAW,aAAa,gBAAA,UAAU,mBAAmB,CAAC;GACpE;GAEA,IAAI,CAAC,gBAAA,QAAQ,iBAAiB,iBAAiB,GAAG,GAAG,MAAM,IAAI,GAAG;IAChE,OAAO,KACL;KAAE;KAAU;IAAK,GACjB,kFACF;IACA,OAAO,KAAK,gBAAA,WAAW,aAAa,gBAAA,UAAU,mBAAmB,CAAC;GACpE;GAEA,IAAI,MAAM,YAAY,cAAc,IAAI,IAAI,MAAM,GAAG;IACnD,OAAO,KACL;KAAE;KAAU;IAAK,GACjB,oFACF;IACA,OAAO,KAAK,gBAAA,WAAW,aAAa,gBAAA,UAAU,mBAAmB,CAAC;GACpE;GAEA,OAAO,KAAK;EACd,SAAS,OAAY;GACnB,OAAO,MAAM,EAAE,OAAO,MAAM,IAAI,GAAG,kDAAkD;GACrF,OAAO,KAAK,gBAAA,WAAW,iBAAiB,MAAM,OAAO,CAAC;EACxD;CACF;AACF"}
@@ -1,6 +1,6 @@
1
1
  import { API_ERROR, errorUtils, ipUtils } from "@verdaccio/core";
2
2
  //#region src/middlewares/token-auth.ts
3
- var WRITE_METHODS = new Set([
3
+ var WRITE_METHODS = /* @__PURE__ */ new Set([
4
4
  "DELETE",
5
5
  "PATCH",
6
6
  "POST",
@@ -1 +1 @@
1
- {"version":3,"file":"token-auth.mjs","names":[],"sources":["../../src/middlewares/token-auth.ts"],"sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from 'express';\n\nimport { API_ERROR, errorUtils, ipUtils } from '@verdaccio/core';\nimport type { Logger, Token } from '@verdaccio/types';\n\nimport type { $RequestExtend } from '../types';\n\nconst WRITE_METHODS = new Set(['DELETE', 'PATCH', 'POST', 'PUT']);\n\n/**\n * Minimal storage surface required to enforce generated token metadata. Typed\n * structurally so this package does not need to depend on `@verdaccio/store`.\n */\nexport interface TokenReadableStorage {\n /**\n * Load the generated tokens stored for a user.\n *\n * @param filter narrows the lookup to a single `user`\n * @returns the user's persisted tokens\n */\n readTokens(filter: { user: string }): Promise<Token[]>;\n}\n\n/**\n * Resolve the client address of an incoming request for CIDR matching.\n *\n * Uses Express' `req.ip`, which already honors the application's `trust proxy`\n * setting (`config.server.trustProxy`): when the operator has declared trusted\n * proxies, `req.ip` is the upstream-most untrusted address parsed from\n * `X-Forwarded-For`; otherwise it is the direct socket peer and the forwarded\n * header is ignored entirely.\n *\n * The middleware must NOT read `X-Forwarded-For` on its own — doing so\n * unconditionally would let any client spoof its address (and bypass a token's\n * CIDR whitelist) simply by sending the header. Falls back to the raw socket\n * address and is normalized (trimmed, IPv4-mapped IPv6 prefix stripped) via\n * {@link ipUtils.normalizeAddress}.\n *\n * @param req the incoming Express request\n * @returns the normalized client address, or `undefined` when none can be determined\n */\nfunction getClientAddress(req: Request): string | undefined {\n return ipUtils.normalizeAddress(req.ip || req.socket.remoteAddress);\n}\n\n/**\n * Find the stored token whose `key` matches the key carried by the request.\n *\n * @param tokens the tokens persisted for the authenticated user\n * @param tokenKey the key extracted from the presented generated token\n * @returns the matching {@link Token}, or `undefined` when it has been revoked / never existed\n */\nfunction findToken(tokens: Token[], tokenKey: string): Token | undefined {\n return tokens.find(({ key }) => key === tokenKey);\n}\n\n/**\n * Express middleware that enforces the metadata of npm-style generated tokens\n * (created via `POST /-/npm/v1/tokens`).\n *\n * A generated token embeds a server-issued `key` that is recovered from the\n * verified credentials as `remote_user.token.key`. This middleware looks that\n * key up in storage and rejects the request when the token:\n * - is missing from storage (revoked or never issued);\n * - is used from a client address outside its `cidr` whitelist;\n * - is `readonly` and the request uses a write method (`DELETE`/`PATCH`/`POST`/`PUT`).\n *\n * Requests that do not carry a generated token key (e.g. interactive logins)\n * pass through untouched. It must run after the JWT/auth middleware has\n * populated `remote_user`. Failures fail closed: a storage lookup error yields\n * an internal error rather than allowing the request.\n *\n * @param storage storage exposing {@link TokenReadableStorage.readTokens} to load the user's tokens\n * @param logger logger used to record rejected attempts and lookup failures\n * @returns an Express {@link RequestHandler} that calls `next()` to allow or `next(error)` to reject\n */\nexport function enforceGeneratedTokenMetadata(\n storage: TokenReadableStorage,\n logger: Logger\n): RequestHandler {\n return async function (req: Request, _res: Response, next: NextFunction): Promise<void> {\n const remoteUser = (req as $RequestExtend).remote_user;\n const tokenKey = remoteUser?.token?.key;\n\n if (!tokenKey) {\n return next();\n }\n\n const user = remoteUser?.name;\n\n if (typeof user !== 'string') {\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n try {\n const token = findToken(await storage.readTokens({ user }), tokenKey);\n\n if (!token) {\n logger.warn({ tokenKey, user }, 'generated token @{tokenKey} for user @{user} is missing');\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n if (!ipUtils.isAddressAllowed(getClientAddress(req), token.cidr)) {\n logger.warn(\n { tokenKey, user },\n 'generated token @{tokenKey} for user @{user} was used outside its CIDR whitelist'\n );\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n if (token.readonly && WRITE_METHODS.has(req.method)) {\n logger.warn(\n { tokenKey, user },\n 'readonly generated token @{tokenKey} for user @{user} was used for a write request'\n );\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n return next();\n } catch (error: any) {\n logger.error({ error: error.msg }, 'generated token metadata lookup failed: @{error}');\n return next(errorUtils.getInternalError(error.message));\n }\n };\n}\n"],"mappings":";;AAOA,IAAM,gBAAgB,IAAI,IAAI;CAAC;CAAU;CAAS;CAAQ;AAAK,CAAC;;;;;;;;;;;;;;;;;;;AAkChE,SAAS,iBAAiB,KAAkC;CAC1D,OAAO,QAAQ,iBAAiB,IAAI,MAAM,IAAI,OAAO,aAAa;AACpE;;;;;;;;AASA,SAAS,UAAU,QAAiB,UAAqC;CACvE,OAAO,OAAO,MAAM,EAAE,UAAU,QAAQ,QAAQ;AAClD;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,8BACd,SACA,QACgB;CAChB,OAAO,eAAgB,KAAc,MAAgB,MAAmC;EACtF,MAAM,aAAc,IAAuB;EAC3C,MAAM,WAAW,YAAY,OAAO;EAEpC,IAAI,CAAC,UACH,OAAO,KAAK;EAGd,MAAM,OAAO,YAAY;EAEzB,IAAI,OAAO,SAAS,UAClB,OAAO,KAAK,WAAW,aAAa,UAAU,mBAAmB,CAAC;EAGpE,IAAI;GACF,MAAM,QAAQ,UAAU,MAAM,QAAQ,WAAW,EAAE,KAAK,CAAC,GAAG,QAAQ;GAEpE,IAAI,CAAC,OAAO;IACV,OAAO,KAAK;KAAE;KAAU;IAAK,GAAG,yDAAyD;IACzF,OAAO,KAAK,WAAW,aAAa,UAAU,mBAAmB,CAAC;GACpE;GAEA,IAAI,CAAC,QAAQ,iBAAiB,iBAAiB,GAAG,GAAG,MAAM,IAAI,GAAG;IAChE,OAAO,KACL;KAAE;KAAU;IAAK,GACjB,kFACF;IACA,OAAO,KAAK,WAAW,aAAa,UAAU,mBAAmB,CAAC;GACpE;GAEA,IAAI,MAAM,YAAY,cAAc,IAAI,IAAI,MAAM,GAAG;IACnD,OAAO,KACL;KAAE;KAAU;IAAK,GACjB,oFACF;IACA,OAAO,KAAK,WAAW,aAAa,UAAU,mBAAmB,CAAC;GACpE;GAEA,OAAO,KAAK;EACd,SAAS,OAAY;GACnB,OAAO,MAAM,EAAE,OAAO,MAAM,IAAI,GAAG,kDAAkD;GACrF,OAAO,KAAK,WAAW,iBAAiB,MAAM,OAAO,CAAC;EACxD;CACF;AACF"}
1
+ {"version":3,"file":"token-auth.mjs","names":[],"sources":["../../src/middlewares/token-auth.ts"],"sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from 'express';\n\nimport { API_ERROR, errorUtils, ipUtils } from '@verdaccio/core';\nimport type { Logger, Token } from '@verdaccio/types';\n\nimport type { $RequestExtend } from '../types';\n\nconst WRITE_METHODS = new Set(['DELETE', 'PATCH', 'POST', 'PUT']);\n\n/**\n * Minimal storage surface required to enforce generated token metadata. Typed\n * structurally so this package does not need to depend on `@verdaccio/store`.\n */\nexport interface TokenReadableStorage {\n /**\n * Load the generated tokens stored for a user.\n *\n * @param filter narrows the lookup to a single `user`\n * @returns the user's persisted tokens\n */\n readTokens(filter: { user: string }): Promise<Token[]>;\n}\n\n/**\n * Resolve the client address of an incoming request for CIDR matching.\n *\n * Uses Express' `req.ip`, which already honors the application's `trust proxy`\n * setting (`config.server.trustProxy`): when the operator has declared trusted\n * proxies, `req.ip` is the upstream-most untrusted address parsed from\n * `X-Forwarded-For`; otherwise it is the direct socket peer and the forwarded\n * header is ignored entirely.\n *\n * The middleware must NOT read `X-Forwarded-For` on its own — doing so\n * unconditionally would let any client spoof its address (and bypass a token's\n * CIDR whitelist) simply by sending the header. Falls back to the raw socket\n * address and is normalized (trimmed, IPv4-mapped IPv6 prefix stripped) via\n * {@link ipUtils.normalizeAddress}.\n *\n * @param req the incoming Express request\n * @returns the normalized client address, or `undefined` when none can be determined\n */\nfunction getClientAddress(req: Request): string | undefined {\n return ipUtils.normalizeAddress(req.ip || req.socket.remoteAddress);\n}\n\n/**\n * Find the stored token whose `key` matches the key carried by the request.\n *\n * @param tokens the tokens persisted for the authenticated user\n * @param tokenKey the key extracted from the presented generated token\n * @returns the matching {@link Token}, or `undefined` when it has been revoked / never existed\n */\nfunction findToken(tokens: Token[], tokenKey: string): Token | undefined {\n return tokens.find(({ key }) => key === tokenKey);\n}\n\n/**\n * Express middleware that enforces the metadata of npm-style generated tokens\n * (created via `POST /-/npm/v1/tokens`).\n *\n * A generated token embeds a server-issued `key` that is recovered from the\n * verified credentials as `remote_user.token.key`. This middleware looks that\n * key up in storage and rejects the request when the token:\n * - is missing from storage (revoked or never issued);\n * - is used from a client address outside its `cidr` whitelist;\n * - is `readonly` and the request uses a write method (`DELETE`/`PATCH`/`POST`/`PUT`).\n *\n * Requests that do not carry a generated token key (e.g. interactive logins)\n * pass through untouched. It must run after the JWT/auth middleware has\n * populated `remote_user`. Failures fail closed: a storage lookup error yields\n * an internal error rather than allowing the request.\n *\n * @param storage storage exposing {@link TokenReadableStorage.readTokens} to load the user's tokens\n * @param logger logger used to record rejected attempts and lookup failures\n * @returns an Express {@link RequestHandler} that calls `next()` to allow or `next(error)` to reject\n */\nexport function enforceGeneratedTokenMetadata(\n storage: TokenReadableStorage,\n logger: Logger\n): RequestHandler {\n return async function (req: Request, _res: Response, next: NextFunction): Promise<void> {\n const remoteUser = (req as $RequestExtend).remote_user;\n const tokenKey = remoteUser?.token?.key;\n\n if (!tokenKey) {\n return next();\n }\n\n const user = remoteUser?.name;\n\n if (typeof user !== 'string') {\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n try {\n const token = findToken(await storage.readTokens({ user }), tokenKey);\n\n if (!token) {\n logger.warn({ tokenKey, user }, 'generated token @{tokenKey} for user @{user} is missing');\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n if (!ipUtils.isAddressAllowed(getClientAddress(req), token.cidr)) {\n logger.warn(\n { tokenKey, user },\n 'generated token @{tokenKey} for user @{user} was used outside its CIDR whitelist'\n );\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n if (token.readonly && WRITE_METHODS.has(req.method)) {\n logger.warn(\n { tokenKey, user },\n 'readonly generated token @{tokenKey} for user @{user} was used for a write request'\n );\n return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));\n }\n\n return next();\n } catch (error: any) {\n logger.error({ error: error.msg }, 'generated token metadata lookup failed: @{error}');\n return next(errorUtils.getInternalError(error.message));\n }\n };\n}\n"],"mappings":";;AAOA,IAAM,gCAAgB,IAAI,IAAI;CAAC;CAAU;CAAS;CAAQ;AAAK,CAAC;;;;;;;;;;;;;;;;;;;AAkChE,SAAS,iBAAiB,KAAkC;CAC1D,OAAO,QAAQ,iBAAiB,IAAI,MAAM,IAAI,OAAO,aAAa;AACpE;;;;;;;;AASA,SAAS,UAAU,QAAiB,UAAqC;CACvE,OAAO,OAAO,MAAM,EAAE,UAAU,QAAQ,QAAQ;AAClD;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,8BACd,SACA,QACgB;CAChB,OAAO,eAAgB,KAAc,MAAgB,MAAmC;EACtF,MAAM,aAAc,IAAuB;EAC3C,MAAM,WAAW,YAAY,OAAO;EAEpC,IAAI,CAAC,UACH,OAAO,KAAK;EAGd,MAAM,OAAO,YAAY;EAEzB,IAAI,OAAO,SAAS,UAClB,OAAO,KAAK,WAAW,aAAa,UAAU,mBAAmB,CAAC;EAGpE,IAAI;GACF,MAAM,QAAQ,UAAU,MAAM,QAAQ,WAAW,EAAE,KAAK,CAAC,GAAG,QAAQ;GAEpE,IAAI,CAAC,OAAO;IACV,OAAO,KAAK;KAAE;KAAU;IAAK,GAAG,yDAAyD;IACzF,OAAO,KAAK,WAAW,aAAa,UAAU,mBAAmB,CAAC;GACpE;GAEA,IAAI,CAAC,QAAQ,iBAAiB,iBAAiB,GAAG,GAAG,MAAM,IAAI,GAAG;IAChE,OAAO,KACL;KAAE;KAAU;IAAK,GACjB,kFACF;IACA,OAAO,KAAK,WAAW,aAAa,UAAU,mBAAmB,CAAC;GACpE;GAEA,IAAI,MAAM,YAAY,cAAc,IAAI,IAAI,MAAM,GAAG;IACnD,OAAO,KACL;KAAE;KAAU;IAAK,GACjB,oFACF;IACA,OAAO,KAAK,WAAW,aAAa,UAAU,mBAAmB,CAAC;GACpE;GAEA,OAAO,KAAK;EACd,SAAS,OAAY;GACnB,OAAO,MAAM,EAAE,OAAO,MAAM,IAAI,GAAG,kDAAkD;GACrF,OAAO,KAAK,WAAW,iBAAiB,MAAM,OAAO,CAAC;EACxD;CACF;AACF"}
@@ -1,2 +1,2 @@
1
- import { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';
1
+ import type { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';
2
2
  export declare function userAgent(config: any): (_req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"user-agent.js","names":[],"sources":["../../src/middlewares/user-agent.ts"],"sourcesContent":["import { getUserAgent } from '@verdaccio/config';\nimport { HEADERS } from '@verdaccio/core';\n\nimport type { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';\n\nexport function userAgent(config) {\n return function (_req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {\n res.setHeader(HEADERS.POWERED_BY, getUserAgent(config?.user_agent));\n next();\n };\n}\n"],"mappings":";;;AAKA,SAAgB,UAAU,QAAQ;CAChC,OAAO,SAAU,MAAsB,KAAsB,MAA8B;EACzF,IAAI,UAAU,gBAAA,QAAQ,aAAA,GAAA,kBAAA,cAAyB,QAAQ,UAAU,CAAC;EAClE,KAAK;CACP;AACF"}
1
+ {"version":3,"file":"user-agent.js","names":[],"sources":["../../src/middlewares/user-agent.ts"],"sourcesContent":["import { getUserAgent } from '@verdaccio/config';\nimport { HEADERS } from '@verdaccio/core';\n\nimport type { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';\n\nexport function userAgent(config) {\n return function (_req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {\n res.setHeader(HEADERS.POWERED_BY, getUserAgent(config?.user_agent));\n next();\n };\n}\n"],"mappings":";;;AAKA,SAAgB,UAAU,QAAQ;CAChC,OAAO,SAAU,MAAsB,KAAsB,MAA8B;EACzF,IAAI,UAAU,gBAAA,QAAQ,aAAA,GAAA,kBAAA,aAAA,CAAyB,QAAQ,UAAU,CAAC;EAClE,KAAK;CACP;AACF"}
@@ -1,3 +1,3 @@
1
- import { NextFunction, Request, Response } from 'express';
1
+ import type { NextFunction, Request, Response } from 'express';
2
2
  export declare function validateName(_req: Request, _res: Response, next: NextFunction, value: string, name: string): void;
3
3
  export declare function validatePackage(_req: Request, _res: any, next: NextFunction, value: string, name: string): void;
@@ -1,4 +1,7 @@
1
- require("./security.js");
2
- require("./render-web.js");
1
+ const require_security = require("./security.js");
2
+ const require_render_web = require("./render-web.js");
3
3
  require("./web-api.js");
4
- require("./web-middleware.js");
4
+ const require_web_middleware = require("./web-middleware.js");
5
+ exports.renderWebMiddleware = require_render_web.renderWebMiddleware;
6
+ exports.setSecurityWebHeaders = require_security.setSecurityWebHeaders;
7
+ exports.webMiddleware = require_web_middleware.default;
@@ -1,4 +1,5 @@
1
- import "./security.mjs";
2
- import "./render-web.mjs";
1
+ import { setSecurityWebHeaders } from "./security.mjs";
2
+ import { renderWebMiddleware } from "./render-web.mjs";
3
3
  import "./web-api.mjs";
4
- import "./web-middleware.mjs";
4
+ import web_middleware_default from "./web-middleware.mjs";
5
+ export { renderWebMiddleware, setSecurityWebHeaders, web_middleware_default as webMiddleware };
@@ -1 +1 @@
1
- export declare function renderWebMiddleware(config: any, tokenMiddleware: any, pluginOptions: any): import('express-serve-static-core').Router;
1
+ export declare function renderWebMiddleware(config: any, tokenMiddleware: any, pluginOptions: any): import("express-serve-static-core").Router;
@@ -76,11 +76,13 @@ function renderWebMiddleware(config, tokenMiddleware, pluginOptions) {
76
76
  res.send(script);
77
77
  });
78
78
  router.get(require_web_urls.WebUrlsNamespace.web, function(req, res) {
79
- require_renderHTML.default(config, manifest, manifestFiles, require_ui_options.getUIOptions(config, req, res), res);
79
+ const options = require_ui_options.getUIOptions(config, req, res);
80
+ require_renderHTML.default(config, manifest, manifestFiles, options, res);
80
81
  debug$1("render html section");
81
82
  });
82
83
  router.get(require_web_urls.WebUrlsNamespace.root, function(req, res) {
83
- require_renderHTML.default(config, manifest, manifestFiles, require_ui_options.getUIOptions(config, req, res), res);
84
+ const options = require_ui_options.getUIOptions(config, req, res);
85
+ require_renderHTML.default(config, manifest, manifestFiles, options, res);
84
86
  debug$1("render root");
85
87
  });
86
88
  if (config?.web?.assetFolder) router.get(require_web_urls.WebUrlsNamespace.assets, function(req, res, next) {
@@ -1 +1 @@
1
- {"version":3,"file":"render-web.js","names":[],"sources":["../../../src/middlewares/web/render-web.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport express from 'express';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { HEADERS } from '@verdaccio/core';\nimport { isURLhasValidProtocol } from '@verdaccio/url';\n\nimport { setSecurityWebHeaders } from './security';\nimport { sendFileCallback, sendFileSafe } from './utils/file-utils';\nimport renderHTML from './utils/renderHTML';\nimport { getUIOptions } from './utils/ui-options';\nimport { WebUrlsNamespace } from './web-urls';\n\nconst debug = buildDebug('verdaccio:middleware:web:render');\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(\n WebUrlsNamespace.static,\n function (req: express.Request<{ all: string | string[] }>, res, next) {\n const filename = Array.isArray(req.params.all) ? req.params.all.join('/') : req.params.all;\n if (filename === 'favicon.ico' && config?.web?.favicon) {\n const file = config?.web?.favicon;\n if (isURLhasValidProtocol(file)) {\n debug('redirect to favicon %s', file);\n req.url = file;\n return next();\n }\n debug('render custom favicon %o', file);\n // see sendFileSafe for the reasoning behind `dotfiles: 'allow'`\n res.sendFile(file, { dotfiles: 'allow' }, sendFileCallback(next));\n return;\n }\n debug('render static file %o', filename);\n sendFileSafe(staticPath, filename, res, next);\n }\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 = `/-/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(\n path.basename(absoluteLocalFile),\n { root: path.dirname(absoluteLocalFile), dotfiles: 'allow' },\n sendFileCallback(next)\n );\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 // Serve external script that loads UI options\n router.get(WebUrlsNamespace.static + 'ui-options.js', function (req, res) {\n const options = getUIOptions(config, req, res);\n const script = `window.__VERDACCIO_BASENAME_UI_OPTIONS=${JSON.stringify(options)};`;\n res.setHeader(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n res.setHeader(HEADERS.CONTENT_TYPE, HEADERS.JAVASCRIPT_CHARSET);\n res.send(script);\n });\n\n // Handle all web routes including security routes\n router.get(WebUrlsNamespace.web, function (req, res) {\n const options = getUIOptions(config, req, res);\n renderHTML(config, manifest, manifestFiles, options, res);\n debug('render html section');\n });\n\n router.get(WebUrlsNamespace.root, function (req, res) {\n const options = getUIOptions(config, req, res);\n renderHTML(config, manifest, manifestFiles, options, res);\n debug('render root');\n });\n\n // any match within the asset folder is routed to the file system\n if (config?.web?.assetFolder) {\n router.get(\n WebUrlsNamespace.assets,\n function (req: express.Request<{ all: string | string[] }>, res, next) {\n const filename = Array.isArray(req.params.all) ? req.params.all.join('/') : req.params.all;\n sendFileSafe(config.web.assetFolder, filename, res, next);\n }\n );\n }\n\n return router;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"render-web.js","names":[],"sources":["../../../src/middlewares/web/render-web.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport express from 'express';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { HEADERS } from '@verdaccio/core';\nimport { isURLhasValidProtocol } from '@verdaccio/url';\n\nimport { setSecurityWebHeaders } from './security';\nimport { sendFileCallback, sendFileSafe } from './utils/file-utils';\nimport renderHTML from './utils/renderHTML';\nimport { getUIOptions } from './utils/ui-options';\nimport { WebUrlsNamespace } from './web-urls';\n\nconst debug = buildDebug('verdaccio:middleware:web:render');\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(\n WebUrlsNamespace.static,\n function (req: express.Request<{ all: string | string[] }>, res, next) {\n const filename = Array.isArray(req.params.all) ? req.params.all.join('/') : req.params.all;\n if (filename === 'favicon.ico' && config?.web?.favicon) {\n const file = config?.web?.favicon;\n if (isURLhasValidProtocol(file)) {\n debug('redirect to favicon %s', file);\n req.url = file;\n return next();\n }\n debug('render custom favicon %o', file);\n // see sendFileSafe for the reasoning behind `dotfiles: 'allow'`\n res.sendFile(file, { dotfiles: 'allow' }, sendFileCallback(next));\n return;\n }\n debug('render static file %o', filename);\n sendFileSafe(staticPath, filename, res, next);\n }\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 = `/-/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(\n path.basename(absoluteLocalFile),\n { root: path.dirname(absoluteLocalFile), dotfiles: 'allow' },\n sendFileCallback(next)\n );\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 // Serve external script that loads UI options\n router.get(WebUrlsNamespace.static + 'ui-options.js', function (req, res) {\n const options = getUIOptions(config, req, res);\n const script = `window.__VERDACCIO_BASENAME_UI_OPTIONS=${JSON.stringify(options)};`;\n res.setHeader(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n res.setHeader(HEADERS.CONTENT_TYPE, HEADERS.JAVASCRIPT_CHARSET);\n res.send(script);\n });\n\n // Handle all web routes including security routes\n router.get(WebUrlsNamespace.web, function (req, res) {\n const options = getUIOptions(config, req, res);\n renderHTML(config, manifest, manifestFiles, options, res);\n debug('render html section');\n });\n\n router.get(WebUrlsNamespace.root, function (req, res) {\n const options = getUIOptions(config, req, res);\n renderHTML(config, manifest, manifestFiles, options, res);\n debug('render root');\n });\n\n // any match within the asset folder is routed to the file system\n if (config?.web?.assetFolder) {\n router.get(\n WebUrlsNamespace.assets,\n function (req: express.Request<{ all: string | string[] }>, res, next) {\n const filename = Array.isArray(req.params.all) ? req.params.all.join('/') : req.params.all;\n sendFileSafe(config.web.assetFolder, filename, res, next);\n }\n );\n }\n\n return router;\n}\n"],"mappings":"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"}
@@ -71,11 +71,13 @@ function renderWebMiddleware(config, tokenMiddleware, pluginOptions) {
71
71
  res.send(script);
72
72
  });
73
73
  router.get(WebUrlsNamespace.web, function(req, res) {
74
- renderHTML(config, manifest, manifestFiles, getUIOptions(config, req, res), res);
74
+ const options = getUIOptions(config, req, res);
75
+ renderHTML(config, manifest, manifestFiles, options, res);
75
76
  debug("render html section");
76
77
  });
77
78
  router.get(WebUrlsNamespace.root, function(req, res) {
78
- renderHTML(config, manifest, manifestFiles, getUIOptions(config, req, res), res);
79
+ const options = getUIOptions(config, req, res);
80
+ renderHTML(config, manifest, manifestFiles, options, res);
79
81
  debug("render root");
80
82
  });
81
83
  if (config?.web?.assetFolder) router.get(WebUrlsNamespace.assets, function(req, res, next) {
@@ -1 +1 @@
1
- {"version":3,"file":"render-web.mjs","names":[],"sources":["../../../src/middlewares/web/render-web.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport express from 'express';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { HEADERS } from '@verdaccio/core';\nimport { isURLhasValidProtocol } from '@verdaccio/url';\n\nimport { setSecurityWebHeaders } from './security';\nimport { sendFileCallback, sendFileSafe } from './utils/file-utils';\nimport renderHTML from './utils/renderHTML';\nimport { getUIOptions } from './utils/ui-options';\nimport { WebUrlsNamespace } from './web-urls';\n\nconst debug = buildDebug('verdaccio:middleware:web:render');\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(\n WebUrlsNamespace.static,\n function (req: express.Request<{ all: string | string[] }>, res, next) {\n const filename = Array.isArray(req.params.all) ? req.params.all.join('/') : req.params.all;\n if (filename === 'favicon.ico' && config?.web?.favicon) {\n const file = config?.web?.favicon;\n if (isURLhasValidProtocol(file)) {\n debug('redirect to favicon %s', file);\n req.url = file;\n return next();\n }\n debug('render custom favicon %o', file);\n // see sendFileSafe for the reasoning behind `dotfiles: 'allow'`\n res.sendFile(file, { dotfiles: 'allow' }, sendFileCallback(next));\n return;\n }\n debug('render static file %o', filename);\n sendFileSafe(staticPath, filename, res, next);\n }\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 = `/-/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(\n path.basename(absoluteLocalFile),\n { root: path.dirname(absoluteLocalFile), dotfiles: 'allow' },\n sendFileCallback(next)\n );\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 // Serve external script that loads UI options\n router.get(WebUrlsNamespace.static + 'ui-options.js', function (req, res) {\n const options = getUIOptions(config, req, res);\n const script = `window.__VERDACCIO_BASENAME_UI_OPTIONS=${JSON.stringify(options)};`;\n res.setHeader(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n res.setHeader(HEADERS.CONTENT_TYPE, HEADERS.JAVASCRIPT_CHARSET);\n res.send(script);\n });\n\n // Handle all web routes including security routes\n router.get(WebUrlsNamespace.web, function (req, res) {\n const options = getUIOptions(config, req, res);\n renderHTML(config, manifest, manifestFiles, options, res);\n debug('render html section');\n });\n\n router.get(WebUrlsNamespace.root, function (req, res) {\n const options = getUIOptions(config, req, res);\n renderHTML(config, manifest, manifestFiles, options, res);\n debug('render root');\n });\n\n // any match within the asset folder is routed to the file system\n if (config?.web?.assetFolder) {\n router.get(\n WebUrlsNamespace.assets,\n function (req: express.Request<{ all: string | string[] }>, res, next) {\n const filename = Array.isArray(req.params.all) ? req.params.all.join('/') : req.params.all;\n sendFileSafe(config.web.assetFolder, filename, res, next);\n }\n );\n }\n\n return router;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"render-web.mjs","names":[],"sources":["../../../src/middlewares/web/render-web.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport express from 'express';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { HEADERS } from '@verdaccio/core';\nimport { isURLhasValidProtocol } from '@verdaccio/url';\n\nimport { setSecurityWebHeaders } from './security';\nimport { sendFileCallback, sendFileSafe } from './utils/file-utils';\nimport renderHTML from './utils/renderHTML';\nimport { getUIOptions } from './utils/ui-options';\nimport { WebUrlsNamespace } from './web-urls';\n\nconst debug = buildDebug('verdaccio:middleware:web:render');\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(\n WebUrlsNamespace.static,\n function (req: express.Request<{ all: string | string[] }>, res, next) {\n const filename = Array.isArray(req.params.all) ? req.params.all.join('/') : req.params.all;\n if (filename === 'favicon.ico' && config?.web?.favicon) {\n const file = config?.web?.favicon;\n if (isURLhasValidProtocol(file)) {\n debug('redirect to favicon %s', file);\n req.url = file;\n return next();\n }\n debug('render custom favicon %o', file);\n // see sendFileSafe for the reasoning behind `dotfiles: 'allow'`\n res.sendFile(file, { dotfiles: 'allow' }, sendFileCallback(next));\n return;\n }\n debug('render static file %o', filename);\n sendFileSafe(staticPath, filename, res, next);\n }\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 = `/-/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(\n path.basename(absoluteLocalFile),\n { root: path.dirname(absoluteLocalFile), dotfiles: 'allow' },\n sendFileCallback(next)\n );\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 // Serve external script that loads UI options\n router.get(WebUrlsNamespace.static + 'ui-options.js', function (req, res) {\n const options = getUIOptions(config, req, res);\n const script = `window.__VERDACCIO_BASENAME_UI_OPTIONS=${JSON.stringify(options)};`;\n res.setHeader(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n res.setHeader(HEADERS.CONTENT_TYPE, HEADERS.JAVASCRIPT_CHARSET);\n res.send(script);\n });\n\n // Handle all web routes including security routes\n router.get(WebUrlsNamespace.web, function (req, res) {\n const options = getUIOptions(config, req, res);\n renderHTML(config, manifest, manifestFiles, options, res);\n debug('render html section');\n });\n\n router.get(WebUrlsNamespace.root, function (req, res) {\n const options = getUIOptions(config, req, res);\n renderHTML(config, manifest, manifestFiles, options, res);\n debug('render root');\n });\n\n // any match within the asset folder is routed to the file system\n if (config?.web?.assetFolder) {\n router.get(\n WebUrlsNamespace.assets,\n function (req: express.Request<{ all: string | string[] }>, res, next) {\n const filename = Array.isArray(req.params.all) ? req.params.all.join('/') : req.params.all;\n sendFileSafe(config.web.assetFolder, filename, res, next);\n }\n );\n }\n\n return router;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"security.js","names":[],"sources":["../../../src/middlewares/web/security.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport { HEADERS } from '@verdaccio/core';\n\nconst debug = buildDebug('verdaccio:middleware:web:security');\n\nexport function setSecurityWebHeaders(_req, res, next): void {\n // disable loading in frames (clickjacking, etc.)\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Frame-Options\n const framesOptions = res.getHeader(HEADERS.FRAMES_OPTIONS);\n if (!framesOptions || !(framesOptions === 'deny' || framesOptions === 'sameorigin')) {\n debug('Missing or invalid X-Frame-Options header; setting to \"deny\"');\n res.header(HEADERS.FRAMES_OPTIONS, 'deny');\n }\n\n // avoid establishing connections outside of domain\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy\n if (!res.getHeader(HEADERS.CSP)) {\n debug('Missing Content-Security-Policy header; setting to \"connect-src \\'self\\'\"');\n res.header(HEADERS.CSP, \"connect-src 'self'\");\n }\n\n // respect the content type of the response\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Content-Type-Options\n const cto = res.getHeader(HEADERS.CTO);\n if (!cto || cto !== 'nosniff') {\n debug('Missing or invalid X-Content-Type-Options header; setting to \"nosniff\"');\n res.header(HEADERS.CTO, 'nosniff');\n }\n\n // block rendering of the page in case of XSS attack\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-XSS-Protection\n const xss = res.getHeader(HEADERS.XSS);\n if (!xss || !(xss === '0' || xss.startsWith('1'))) {\n debug('Missing or invalid X-XSS-Protection header; setting to \"1; mode=block\"');\n res.header(HEADERS.XSS, '1; mode=block');\n }\n next();\n}\n"],"mappings":";;;;;AAIA,IAAM,WAAA,GAAA,MAAA,SAAmB,mCAAmC;AAE5D,SAAgB,sBAAsB,MAAM,KAAK,MAAY;CAG3D,MAAM,gBAAgB,IAAI,UAAU,gBAAA,QAAQ,cAAc;CAC1D,IAAI,CAAC,iBAAiB,EAAE,kBAAkB,UAAU,kBAAkB,eAAe;EACnF,QAAM,gEAA8D;EACpE,IAAI,OAAO,gBAAA,QAAQ,gBAAgB,MAAM;CAC3C;CAIA,IAAI,CAAC,IAAI,UAAU,gBAAA,QAAQ,GAAG,GAAG;EAC/B,QAAM,2EAA2E;EACjF,IAAI,OAAO,gBAAA,QAAQ,KAAK,oBAAoB;CAC9C;CAIA,MAAM,MAAM,IAAI,UAAU,gBAAA,QAAQ,GAAG;CACrC,IAAI,CAAC,OAAO,QAAQ,WAAW;EAC7B,QAAM,0EAAwE;EAC9E,IAAI,OAAO,gBAAA,QAAQ,KAAK,SAAS;CACnC;CAIA,MAAM,MAAM,IAAI,UAAU,gBAAA,QAAQ,GAAG;CACrC,IAAI,CAAC,OAAO,EAAE,QAAQ,OAAO,IAAI,WAAW,GAAG,IAAI;EACjD,QAAM,0EAAwE;EAC9E,IAAI,OAAO,gBAAA,QAAQ,KAAK,eAAe;CACzC;CACA,KAAK;AACP"}
1
+ {"version":3,"file":"security.js","names":[],"sources":["../../../src/middlewares/web/security.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport { HEADERS } from '@verdaccio/core';\n\nconst debug = buildDebug('verdaccio:middleware:web:security');\n\nexport function setSecurityWebHeaders(_req, res, next): void {\n // disable loading in frames (clickjacking, etc.)\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Frame-Options\n const framesOptions = res.getHeader(HEADERS.FRAMES_OPTIONS);\n if (!framesOptions || !(framesOptions === 'deny' || framesOptions === 'sameorigin')) {\n debug('Missing or invalid X-Frame-Options header; setting to \"deny\"');\n res.header(HEADERS.FRAMES_OPTIONS, 'deny');\n }\n\n // avoid establishing connections outside of domain\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy\n if (!res.getHeader(HEADERS.CSP)) {\n debug('Missing Content-Security-Policy header; setting to \"connect-src \\'self\\'\"');\n res.header(HEADERS.CSP, \"connect-src 'self'\");\n }\n\n // respect the content type of the response\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Content-Type-Options\n const cto = res.getHeader(HEADERS.CTO);\n if (!cto || cto !== 'nosniff') {\n debug('Missing or invalid X-Content-Type-Options header; setting to \"nosniff\"');\n res.header(HEADERS.CTO, 'nosniff');\n }\n\n // block rendering of the page in case of XSS attack\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-XSS-Protection\n const xss = res.getHeader(HEADERS.XSS);\n if (!xss || !(xss === '0' || xss.startsWith('1'))) {\n debug('Missing or invalid X-XSS-Protection header; setting to \"1; mode=block\"');\n res.header(HEADERS.XSS, '1; mode=block');\n }\n next();\n}\n"],"mappings":";;;;;AAIA,IAAM,WAAA,GAAA,MAAA,QAAA,CAAmB,mCAAmC;AAE5D,SAAgB,sBAAsB,MAAM,KAAK,MAAY;CAG3D,MAAM,gBAAgB,IAAI,UAAU,gBAAA,QAAQ,cAAc;CAC1D,IAAI,CAAC,iBAAiB,EAAE,kBAAkB,UAAU,kBAAkB,eAAe;EACnF,QAAM,gEAA8D;EACpE,IAAI,OAAO,gBAAA,QAAQ,gBAAgB,MAAM;CAC3C;CAIA,IAAI,CAAC,IAAI,UAAU,gBAAA,QAAQ,GAAG,GAAG;EAC/B,QAAM,2EAA2E;EACjF,IAAI,OAAO,gBAAA,QAAQ,KAAK,oBAAoB;CAC9C;CAIA,MAAM,MAAM,IAAI,UAAU,gBAAA,QAAQ,GAAG;CACrC,IAAI,CAAC,OAAO,QAAQ,WAAW;EAC7B,QAAM,0EAAwE;EAC9E,IAAI,OAAO,gBAAA,QAAQ,KAAK,SAAS;CACnC;CAIA,MAAM,MAAM,IAAI,UAAU,gBAAA,QAAQ,GAAG;CACrC,IAAI,CAAC,OAAO,EAAE,QAAQ,OAAO,IAAI,WAAW,GAAG,IAAI;EACjD,QAAM,0EAAwE;EAC9E,IAAI,OAAO,gBAAA,QAAQ,KAAK,eAAe;CACzC;CACA,KAAK;AACP"}
@@ -1 +1 @@
1
- {"version":3,"file":"file-utils.js","names":[],"sources":["../../../../src/middlewares/web/utils/file-utils.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport path from 'node:path';\n\nimport { HTTP_STATUS, fileUtils } from '@verdaccio/core';\n\nconst debug = buildDebug('verdaccio:middleware:web:utils');\n\nexport const 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\n/**\n * Safely serve a file by validating and resolving the path to prevent directory traversal.\n * @param baseDir Base directory path\n * @param filename Requested filename (user input)\n * @param res Express response object\n * @param next Express next function\n */\nexport function sendFileSafe(baseDir: string, filename: string, res, next) {\n // The splat may match nothing (e.g. \"/-/assets/.\" collapses to no segment),\n // leaving filename undefined/empty. Nothing to serve, fall through to 404.\n if (!filename) {\n debug('empty filename requested');\n return next();\n }\n\n // First validation layer: check filename for basic validity\n const safeFilename = fileUtils.sanitizeFilename(filename);\n\n // Second validation layer: resolve safe path within base directory\n const safe = fileUtils.resolveSafePath(baseDir, safeFilename);\n if (!safe) {\n debug('unsafe filename requested %o', safeFilename);\n return next();\n }\n\n // Prevent attempting to send a directory (e.g., base directory via \".\" or \"subdir/..\")\n const baseDirResolved = path.resolve(baseDir);\n if (safe === baseDirResolved) {\n debug('directory requested instead of file %o', safe);\n return next();\n }\n\n debug('serve file %o', safe);\n // `dotfiles: 'allow'` is required because the absolute path may legitimately\n // contain dot-prefixed ancestor directories (e.g. pnpm's `node_modules/.pnpm/...`\n // content-addressable store). Path traversal is already enforced by\n // `resolveSafePath` above.\n // Requests for dot-prefixed URL segments are handled separately by the global `dotfiles` middleware.\n res.sendFile(safe, { dotfiles: 'allow' }, sendFileCallback(next));\n}\n"],"mappings":";;;;;;;AAKA,IAAM,WAAA,GAAA,MAAA,SAAmB,gCAAgC;AAEzD,IAAa,oBAAoB,UAAU,QAAQ;CACjD,IAAI,CAAC,KACH;CAEF,IAAI,IAAI,WAAW,gBAAA,YAAY,WAC7B,KAAK;MAEL,KAAK,GAAG;AAEZ;;;;;;;;AASA,SAAgB,aAAa,SAAiB,UAAkB,KAAK,MAAM;CAGzE,IAAI,CAAC,UAAU;EACb,QAAM,0BAA0B;EAChC,OAAO,KAAK;CACd;CAGA,MAAM,eAAe,gBAAA,UAAU,iBAAiB,QAAQ;CAGxD,MAAM,OAAO,gBAAA,UAAU,gBAAgB,SAAS,YAAY;CAC5D,IAAI,CAAC,MAAM;EACT,QAAM,gCAAgC,YAAY;EAClD,OAAO,KAAK;CACd;CAIA,IAAI,SADoB,UAAA,QAAK,QAAQ,OACxB,GAAiB;EAC5B,QAAM,0CAA0C,IAAI;EACpD,OAAO,KAAK;CACd;CAEA,QAAM,iBAAiB,IAAI;CAM3B,IAAI,SAAS,MAAM,EAAE,UAAU,QAAQ,GAAG,iBAAiB,IAAI,CAAC;AAClE"}
1
+ {"version":3,"file":"file-utils.js","names":[],"sources":["../../../../src/middlewares/web/utils/file-utils.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport path from 'node:path';\n\nimport { HTTP_STATUS, fileUtils } from '@verdaccio/core';\n\nconst debug = buildDebug('verdaccio:middleware:web:utils');\n\nexport const 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\n/**\n * Safely serve a file by validating and resolving the path to prevent directory traversal.\n * @param baseDir Base directory path\n * @param filename Requested filename (user input)\n * @param res Express response object\n * @param next Express next function\n */\nexport function sendFileSafe(baseDir: string, filename: string, res, next) {\n // The splat may match nothing (e.g. \"/-/assets/.\" collapses to no segment),\n // leaving filename undefined/empty. Nothing to serve, fall through to 404.\n if (!filename) {\n debug('empty filename requested');\n return next();\n }\n\n // First validation layer: check filename for basic validity\n const safeFilename = fileUtils.sanitizeFilename(filename);\n\n // Second validation layer: resolve safe path within base directory\n const safe = fileUtils.resolveSafePath(baseDir, safeFilename);\n if (!safe) {\n debug('unsafe filename requested %o', safeFilename);\n return next();\n }\n\n // Prevent attempting to send a directory (e.g., base directory via \".\" or \"subdir/..\")\n const baseDirResolved = path.resolve(baseDir);\n if (safe === baseDirResolved) {\n debug('directory requested instead of file %o', safe);\n return next();\n }\n\n debug('serve file %o', safe);\n // `dotfiles: 'allow'` is required because the absolute path may legitimately\n // contain dot-prefixed ancestor directories (e.g. pnpm's `node_modules/.pnpm/...`\n // content-addressable store). Path traversal is already enforced by\n // `resolveSafePath` above.\n // Requests for dot-prefixed URL segments are handled separately by the global `dotfiles` middleware.\n res.sendFile(safe, { dotfiles: 'allow' }, sendFileCallback(next));\n}\n"],"mappings":";;;;;;;AAKA,IAAM,WAAA,GAAA,MAAA,QAAA,CAAmB,gCAAgC;AAEzD,IAAa,oBAAoB,UAAU,QAAQ;CACjD,IAAI,CAAC,KACH;CAEF,IAAI,IAAI,WAAW,gBAAA,YAAY,WAC7B,KAAK;MAEL,KAAK,GAAG;AAEZ;;;;;;;;AASA,SAAgB,aAAa,SAAiB,UAAkB,KAAK,MAAM;CAGzE,IAAI,CAAC,UAAU;EACb,QAAM,0BAA0B;EAChC,OAAO,KAAK;CACd;CAGA,MAAM,eAAe,gBAAA,UAAU,iBAAiB,QAAQ;CAGxD,MAAM,OAAO,gBAAA,UAAU,gBAAgB,SAAS,YAAY;CAC5D,IAAI,CAAC,MAAM;EACT,QAAM,gCAAgC,YAAY;EAClD,OAAO,KAAK;CACd;CAIA,IAAI,SADoB,UAAA,QAAK,QAAQ,OACxB,GAAiB;EAC5B,QAAM,0CAA0C,IAAI;EACpD,OAAO,KAAK;CACd;CAEA,QAAM,iBAAiB,IAAI;CAM3B,IAAI,SAAS,MAAM,EAAE,UAAU,QAAQ,GAAG,iBAAiB,IAAI,CAAC;AAClE"}