@verdaccio/middleware 9.0.0-next-9.16 → 9.0.0-next-9.18

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 (77) hide show
  1. package/build/index.d.ts +2 -0
  2. package/build/index.js +2 -0
  3. package/build/index.mjs +2 -1
  4. package/build/middlewares/allow.js.map +1 -1
  5. package/build/middlewares/allow.mjs.map +1 -1
  6. package/build/middlewares/antiLoop.js +0 -1
  7. package/build/middlewares/antiLoop.js.map +1 -1
  8. package/build/middlewares/antiLoop.mjs.map +1 -1
  9. package/build/middlewares/api_urls.js.map +1 -1
  10. package/build/middlewares/api_urls.mjs.map +1 -1
  11. package/build/middlewares/body-parser.js.map +1 -1
  12. package/build/middlewares/body-parser.mjs.map +1 -1
  13. package/build/middlewares/dotfiles.js +0 -1
  14. package/build/middlewares/dotfiles.js.map +1 -1
  15. package/build/middlewares/dotfiles.mjs.map +1 -1
  16. package/build/middlewares/encode-pkg.js.map +1 -1
  17. package/build/middlewares/encode-pkg.mjs.map +1 -1
  18. package/build/middlewares/error.js.map +1 -1
  19. package/build/middlewares/error.mjs.map +1 -1
  20. package/build/middlewares/final.js.map +1 -1
  21. package/build/middlewares/final.mjs.map +1 -1
  22. package/build/middlewares/json.js +0 -1
  23. package/build/middlewares/json.js.map +1 -1
  24. package/build/middlewares/json.mjs.map +1 -1
  25. package/build/middlewares/log.js.map +1 -1
  26. package/build/middlewares/log.mjs.map +1 -1
  27. package/build/middlewares/make-url-relative.js.map +1 -1
  28. package/build/middlewares/make-url-relative.mjs.map +1 -1
  29. package/build/middlewares/match.js.map +1 -1
  30. package/build/middlewares/match.mjs.map +1 -1
  31. package/build/middlewares/media.js +0 -1
  32. package/build/middlewares/media.js.map +1 -1
  33. package/build/middlewares/media.mjs.map +1 -1
  34. package/build/middlewares/rate-limit.js.map +1 -1
  35. package/build/middlewares/rate-limit.mjs.map +1 -1
  36. package/build/middlewares/request-options.js.map +1 -1
  37. package/build/middlewares/request-options.mjs.map +1 -1
  38. package/build/middlewares/token-auth.d.ts +38 -0
  39. package/build/middlewares/token-auth.js +96 -0
  40. package/build/middlewares/token-auth.js.map +1 -0
  41. package/build/middlewares/token-auth.mjs +96 -0
  42. package/build/middlewares/token-auth.mjs.map +1 -0
  43. package/build/middlewares/user-agent.js +0 -1
  44. package/build/middlewares/user-agent.js.map +1 -1
  45. package/build/middlewares/user-agent.mjs.map +1 -1
  46. package/build/middlewares/validation.js +0 -1
  47. package/build/middlewares/validation.js.map +1 -1
  48. package/build/middlewares/validation.mjs.map +1 -1
  49. package/build/middlewares/web/render-web.js +1 -1
  50. package/build/middlewares/web/render-web.js.map +1 -1
  51. package/build/middlewares/web/render-web.mjs +1 -1
  52. package/build/middlewares/web/render-web.mjs.map +1 -1
  53. package/build/middlewares/web/security.js.map +1 -1
  54. package/build/middlewares/web/security.mjs.map +1 -1
  55. package/build/middlewares/web/utils/file-utils.js +4 -0
  56. package/build/middlewares/web/utils/file-utils.js.map +1 -1
  57. package/build/middlewares/web/utils/file-utils.mjs +4 -0
  58. package/build/middlewares/web/utils/file-utils.mjs.map +1 -1
  59. package/build/middlewares/web/utils/manifest.js.map +1 -1
  60. package/build/middlewares/web/utils/manifest.mjs.map +1 -1
  61. package/build/middlewares/web/utils/renderHTML.js +1 -1
  62. package/build/middlewares/web/utils/renderHTML.js.map +1 -1
  63. package/build/middlewares/web/utils/renderHTML.mjs +1 -1
  64. package/build/middlewares/web/utils/renderHTML.mjs.map +1 -1
  65. package/build/middlewares/web/utils/template.js.map +1 -1
  66. package/build/middlewares/web/utils/template.mjs.map +1 -1
  67. package/build/middlewares/web/utils/ui-options.js.map +1 -1
  68. package/build/middlewares/web/utils/ui-options.mjs.map +1 -1
  69. package/build/middlewares/web/utils/web-utils.js.map +1 -1
  70. package/build/middlewares/web/utils/web-utils.mjs.map +1 -1
  71. package/build/middlewares/web/web-api.js.map +1 -1
  72. package/build/middlewares/web/web-api.mjs.map +1 -1
  73. package/build/middlewares/web/web-middleware.js.map +1 -1
  74. package/build/middlewares/web/web-middleware.mjs.map +1 -1
  75. package/build/middlewares/web/web-urls.js.map +1 -1
  76. package/build/middlewares/web/web-urls.mjs.map +1 -1
  77. package/package.json +9 -9
@@ -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,uCAAuC;AAEhE,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;EACpC;AAED,OAAM,uBAAuB,eAAe;AAE5C,QAAO"}
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"}
@@ -0,0 +1,38 @@
1
+ import { RequestHandler } from 'express';
2
+ import { Logger, Token } from '@verdaccio/types';
3
+ /**
4
+ * Minimal storage surface required to enforce generated token metadata. Typed
5
+ * structurally so this package does not need to depend on `@verdaccio/store`.
6
+ */
7
+ export interface TokenReadableStorage {
8
+ /**
9
+ * Load the generated tokens stored for a user.
10
+ *
11
+ * @param filter narrows the lookup to a single `user`
12
+ * @returns the user's persisted tokens
13
+ */
14
+ readTokens(filter: {
15
+ user: string;
16
+ }): Promise<Token[]>;
17
+ }
18
+ /**
19
+ * Express middleware that enforces the metadata of npm-style generated tokens
20
+ * (created via `POST /-/npm/v1/tokens`).
21
+ *
22
+ * A generated token embeds a server-issued `key` that is recovered from the
23
+ * verified credentials as `remote_user.token.key`. This middleware looks that
24
+ * key up in storage and rejects the request when the token:
25
+ * - is missing from storage (revoked or never issued);
26
+ * - is used from a client address outside its `cidr` whitelist;
27
+ * - is `readonly` and the request uses a write method (`DELETE`/`PATCH`/`POST`/`PUT`).
28
+ *
29
+ * Requests that do not carry a generated token key (e.g. interactive logins)
30
+ * pass through untouched. It must run after the JWT/auth middleware has
31
+ * populated `remote_user`. Failures fail closed: a storage lookup error yields
32
+ * an internal error rather than allowing the request.
33
+ *
34
+ * @param storage storage exposing {@link TokenReadableStorage.readTokens} to load the user's tokens
35
+ * @param logger logger used to record rejected attempts and lookup failures
36
+ * @returns an Express {@link RequestHandler} that calls `next()` to allow or `next(error)` to reject
37
+ */
38
+ export declare function enforceGeneratedTokenMetadata(storage: TokenReadableStorage, logger: Logger): RequestHandler;
@@ -0,0 +1,96 @@
1
+ let _verdaccio_core = require("@verdaccio/core");
2
+ //#region src/middlewares/token-auth.ts
3
+ var WRITE_METHODS = new Set([
4
+ "DELETE",
5
+ "PATCH",
6
+ "POST",
7
+ "PUT"
8
+ ]);
9
+ /**
10
+ * Resolve the client address of an incoming request for CIDR matching.
11
+ *
12
+ * Prefers the `x-forwarded-for` header (taking the first, client-most entry of
13
+ * the comma-separated list) and falls back to `req.ip` and then the raw socket
14
+ * address. The result is normalized (trimmed and IPv4-mapped IPv6 prefixes
15
+ * stripped) via {@link ipUtils.normalizeAddress}.
16
+ *
17
+ * @param req the incoming Express request
18
+ * @returns the normalized client address, or `undefined` when none can be determined
19
+ */
20
+ function getClientAddress(req) {
21
+ const forwardedFor = req.headers["x-forwarded-for"];
22
+ if (Array.isArray(forwardedFor)) return _verdaccio_core.ipUtils.normalizeAddress(forwardedFor[0]?.split(",")[0]);
23
+ if (typeof forwardedFor === "string") return _verdaccio_core.ipUtils.normalizeAddress(forwardedFor.split(",")[0]);
24
+ return _verdaccio_core.ipUtils.normalizeAddress(req.ip || req.socket.remoteAddress);
25
+ }
26
+ /**
27
+ * Find the stored token whose `key` matches the key carried by the request.
28
+ *
29
+ * @param tokens the tokens persisted for the authenticated user
30
+ * @param tokenKey the key extracted from the presented generated token
31
+ * @returns the matching {@link Token}, or `undefined` when it has been revoked / never existed
32
+ */
33
+ function findToken(tokens, tokenKey) {
34
+ return tokens.find(({ key }) => key === tokenKey);
35
+ }
36
+ /**
37
+ * Express middleware that enforces the metadata of npm-style generated tokens
38
+ * (created via `POST /-/npm/v1/tokens`).
39
+ *
40
+ * A generated token embeds a server-issued `key` that is recovered from the
41
+ * verified credentials as `remote_user.token.key`. This middleware looks that
42
+ * key up in storage and rejects the request when the token:
43
+ * - is missing from storage (revoked or never issued);
44
+ * - is used from a client address outside its `cidr` whitelist;
45
+ * - is `readonly` and the request uses a write method (`DELETE`/`PATCH`/`POST`/`PUT`).
46
+ *
47
+ * Requests that do not carry a generated token key (e.g. interactive logins)
48
+ * pass through untouched. It must run after the JWT/auth middleware has
49
+ * populated `remote_user`. Failures fail closed: a storage lookup error yields
50
+ * an internal error rather than allowing the request.
51
+ *
52
+ * @param storage storage exposing {@link TokenReadableStorage.readTokens} to load the user's tokens
53
+ * @param logger logger used to record rejected attempts and lookup failures
54
+ * @returns an Express {@link RequestHandler} that calls `next()` to allow or `next(error)` to reject
55
+ */
56
+ function enforceGeneratedTokenMetadata(storage, logger) {
57
+ return async function(req, _res, next) {
58
+ const remoteUser = req.remote_user;
59
+ const tokenKey = remoteUser?.token?.key;
60
+ if (!tokenKey) return next();
61
+ const user = remoteUser?.name;
62
+ if (typeof user !== "string") return next(_verdaccio_core.errorUtils.getForbidden(_verdaccio_core.API_ERROR.UNAUTHORIZED_ACCESS));
63
+ try {
64
+ const token = findToken(await storage.readTokens({ user }), tokenKey);
65
+ if (!token) {
66
+ logger.warn({
67
+ tokenKey,
68
+ user
69
+ }, "generated token @{tokenKey} for user @{user} is missing");
70
+ return next(_verdaccio_core.errorUtils.getForbidden(_verdaccio_core.API_ERROR.UNAUTHORIZED_ACCESS));
71
+ }
72
+ if (!_verdaccio_core.ipUtils.isAddressAllowed(getClientAddress(req), token.cidr)) {
73
+ logger.warn({
74
+ tokenKey,
75
+ user
76
+ }, "generated token @{tokenKey} for user @{user} was used outside its CIDR whitelist");
77
+ return next(_verdaccio_core.errorUtils.getForbidden(_verdaccio_core.API_ERROR.UNAUTHORIZED_ACCESS));
78
+ }
79
+ if (token.readonly && WRITE_METHODS.has(req.method)) {
80
+ logger.warn({
81
+ tokenKey,
82
+ user
83
+ }, "readonly generated token @{tokenKey} for user @{user} was used for a write request");
84
+ return next(_verdaccio_core.errorUtils.getForbidden(_verdaccio_core.API_ERROR.UNAUTHORIZED_ACCESS));
85
+ }
86
+ return next();
87
+ } catch (error) {
88
+ logger.error({ error: error.msg }, "generated token metadata lookup failed: @{error}");
89
+ return next(_verdaccio_core.errorUtils.getInternalError(error.message));
90
+ }
91
+ };
92
+ }
93
+ //#endregion
94
+ exports.enforceGeneratedTokenMetadata = enforceGeneratedTokenMetadata;
95
+
96
+ //# sourceMappingURL=token-auth.js.map
@@ -0,0 +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 * Prefers the `x-forwarded-for` header (taking the first, client-most entry of\n * the comma-separated list) and falls back to `req.ip` and then the raw socket\n * address. The result is normalized (trimmed and IPv4-mapped IPv6 prefixes\n * stripped) via {@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 const forwardedFor = req.headers['x-forwarded-for'];\n\n if (Array.isArray(forwardedFor)) {\n return ipUtils.normalizeAddress(forwardedFor[0]?.split(',')[0]);\n }\n\n if (typeof forwardedFor === 'string') {\n return ipUtils.normalizeAddress(forwardedFor.split(',')[0]);\n }\n\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;;;;;;;;;;;;AA2BhE,SAAS,iBAAiB,KAAkC;CAC1D,MAAM,eAAe,IAAI,QAAQ;CAEjC,IAAI,MAAM,QAAQ,YAAY,GAC5B,OAAO,gBAAA,QAAQ,iBAAiB,aAAa,IAAI,MAAM,GAAG,EAAE,EAAE;CAGhE,IAAI,OAAO,iBAAiB,UAC1B,OAAO,gBAAA,QAAQ,iBAAiB,aAAa,MAAM,GAAG,EAAE,EAAE;CAG5D,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"}
@@ -0,0 +1,96 @@
1
+ import { API_ERROR, errorUtils, ipUtils } from "@verdaccio/core";
2
+ //#region src/middlewares/token-auth.ts
3
+ var WRITE_METHODS = new Set([
4
+ "DELETE",
5
+ "PATCH",
6
+ "POST",
7
+ "PUT"
8
+ ]);
9
+ /**
10
+ * Resolve the client address of an incoming request for CIDR matching.
11
+ *
12
+ * Prefers the `x-forwarded-for` header (taking the first, client-most entry of
13
+ * the comma-separated list) and falls back to `req.ip` and then the raw socket
14
+ * address. The result is normalized (trimmed and IPv4-mapped IPv6 prefixes
15
+ * stripped) via {@link ipUtils.normalizeAddress}.
16
+ *
17
+ * @param req the incoming Express request
18
+ * @returns the normalized client address, or `undefined` when none can be determined
19
+ */
20
+ function getClientAddress(req) {
21
+ const forwardedFor = req.headers["x-forwarded-for"];
22
+ if (Array.isArray(forwardedFor)) return ipUtils.normalizeAddress(forwardedFor[0]?.split(",")[0]);
23
+ if (typeof forwardedFor === "string") return ipUtils.normalizeAddress(forwardedFor.split(",")[0]);
24
+ return ipUtils.normalizeAddress(req.ip || req.socket.remoteAddress);
25
+ }
26
+ /**
27
+ * Find the stored token whose `key` matches the key carried by the request.
28
+ *
29
+ * @param tokens the tokens persisted for the authenticated user
30
+ * @param tokenKey the key extracted from the presented generated token
31
+ * @returns the matching {@link Token}, or `undefined` when it has been revoked / never existed
32
+ */
33
+ function findToken(tokens, tokenKey) {
34
+ return tokens.find(({ key }) => key === tokenKey);
35
+ }
36
+ /**
37
+ * Express middleware that enforces the metadata of npm-style generated tokens
38
+ * (created via `POST /-/npm/v1/tokens`).
39
+ *
40
+ * A generated token embeds a server-issued `key` that is recovered from the
41
+ * verified credentials as `remote_user.token.key`. This middleware looks that
42
+ * key up in storage and rejects the request when the token:
43
+ * - is missing from storage (revoked or never issued);
44
+ * - is used from a client address outside its `cidr` whitelist;
45
+ * - is `readonly` and the request uses a write method (`DELETE`/`PATCH`/`POST`/`PUT`).
46
+ *
47
+ * Requests that do not carry a generated token key (e.g. interactive logins)
48
+ * pass through untouched. It must run after the JWT/auth middleware has
49
+ * populated `remote_user`. Failures fail closed: a storage lookup error yields
50
+ * an internal error rather than allowing the request.
51
+ *
52
+ * @param storage storage exposing {@link TokenReadableStorage.readTokens} to load the user's tokens
53
+ * @param logger logger used to record rejected attempts and lookup failures
54
+ * @returns an Express {@link RequestHandler} that calls `next()` to allow or `next(error)` to reject
55
+ */
56
+ function enforceGeneratedTokenMetadata(storage, logger) {
57
+ return async function(req, _res, next) {
58
+ const remoteUser = req.remote_user;
59
+ const tokenKey = remoteUser?.token?.key;
60
+ if (!tokenKey) return next();
61
+ const user = remoteUser?.name;
62
+ if (typeof user !== "string") return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));
63
+ try {
64
+ const token = findToken(await storage.readTokens({ user }), tokenKey);
65
+ if (!token) {
66
+ logger.warn({
67
+ tokenKey,
68
+ user
69
+ }, "generated token @{tokenKey} for user @{user} is missing");
70
+ return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));
71
+ }
72
+ if (!ipUtils.isAddressAllowed(getClientAddress(req), token.cidr)) {
73
+ logger.warn({
74
+ tokenKey,
75
+ user
76
+ }, "generated token @{tokenKey} for user @{user} was used outside its CIDR whitelist");
77
+ return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));
78
+ }
79
+ if (token.readonly && WRITE_METHODS.has(req.method)) {
80
+ logger.warn({
81
+ tokenKey,
82
+ user
83
+ }, "readonly generated token @{tokenKey} for user @{user} was used for a write request");
84
+ return next(errorUtils.getForbidden(API_ERROR.UNAUTHORIZED_ACCESS));
85
+ }
86
+ return next();
87
+ } catch (error) {
88
+ logger.error({ error: error.msg }, "generated token metadata lookup failed: @{error}");
89
+ return next(errorUtils.getInternalError(error.message));
90
+ }
91
+ };
92
+ }
93
+ //#endregion
94
+ export { enforceGeneratedTokenMetadata };
95
+
96
+ //# sourceMappingURL=token-auth.mjs.map
@@ -0,0 +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 * Prefers the `x-forwarded-for` header (taking the first, client-most entry of\n * the comma-separated list) and falls back to `req.ip` and then the raw socket\n * address. The result is normalized (trimmed and IPv4-mapped IPv6 prefixes\n * stripped) via {@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 const forwardedFor = req.headers['x-forwarded-for'];\n\n if (Array.isArray(forwardedFor)) {\n return ipUtils.normalizeAddress(forwardedFor[0]?.split(',')[0]);\n }\n\n if (typeof forwardedFor === 'string') {\n return ipUtils.normalizeAddress(forwardedFor.split(',')[0]);\n }\n\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;;;;;;;;;;;;AA2BhE,SAAS,iBAAiB,KAAkC;CAC1D,MAAM,eAAe,IAAI,QAAQ;CAEjC,IAAI,MAAM,QAAQ,YAAY,GAC5B,OAAO,QAAQ,iBAAiB,aAAa,IAAI,MAAM,GAAG,EAAE,EAAE;CAGhE,IAAI,OAAO,iBAAiB,UAC1B,OAAO,QAAQ,iBAAiB,aAAa,MAAM,GAAG,EAAE,EAAE;CAG5D,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,4 +1,3 @@
1
- require("../_virtual/_rolldown/runtime.js");
2
1
  let _verdaccio_core = require("@verdaccio/core");
3
2
  let _verdaccio_config = require("@verdaccio/config");
4
3
  //#region src/middlewares/user-agent.ts
@@ -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;AAChC,QAAO,SAAU,MAAsB,KAAsB,MAA8B;AACzF,MAAI,UAAU,gBAAA,QAAQ,aAAA,GAAA,kBAAA,cAAyB,QAAQ,WAAW,CAAC;AACnE,QAAM"}
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 +1 @@
1
- {"version":3,"file":"user-agent.mjs","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;AAChC,QAAO,SAAU,MAAsB,KAAsB,MAA8B;AACzF,MAAI,UAAU,QAAQ,YAAY,aAAa,QAAQ,WAAW,CAAC;AACnE,QAAM"}
1
+ {"version":3,"file":"user-agent.mjs","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,QAAQ,YAAY,aAAa,QAAQ,UAAU,CAAC;EAClE,KAAK;CACP;AACF"}
@@ -1,4 +1,3 @@
1
- require("../_virtual/_rolldown/runtime.js");
2
1
  let _verdaccio_core = require("@verdaccio/core");
3
2
  //#region src/middlewares/validation.ts
4
3
  function validateName(_req, _res, next, value, name) {
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","names":[],"sources":["../../src/middlewares/validation.ts"],"sourcesContent":["import type { NextFunction, Request, Response } from 'express';\n\nimport { errorUtils, validationUtils } from '@verdaccio/core';\n\nexport function validateName(\n _req: Request,\n _res: Response,\n next: NextFunction,\n value: string,\n name: string\n) {\n if (validationUtils.validateName(value)) {\n next();\n } else {\n next(errorUtils.getBadRequest('invalid ' + name));\n }\n}\n\nexport function validatePackage(\n _req: Request,\n _res,\n next: NextFunction,\n value: string,\n name: string\n) {\n if (validationUtils.validatePackage(value)) {\n next();\n } else {\n next(errorUtils.getBadRequest('invalid ' + name));\n }\n}\n"],"mappings":";;;AAIA,SAAgB,aACd,MACA,MACA,MACA,OACA,MACA;AACA,KAAI,gBAAA,gBAAgB,aAAa,MAAM,CACrC,OAAM;KAEN,MAAK,gBAAA,WAAW,cAAc,aAAa,KAAK,CAAC;;AAIrD,SAAgB,gBACd,MACA,MACA,MACA,OACA,MACA;AACA,KAAI,gBAAA,gBAAgB,gBAAgB,MAAM,CACxC,OAAM;KAEN,MAAK,gBAAA,WAAW,cAAc,aAAa,KAAK,CAAC"}
1
+ {"version":3,"file":"validation.js","names":[],"sources":["../../src/middlewares/validation.ts"],"sourcesContent":["import type { NextFunction, Request, Response } from 'express';\n\nimport { errorUtils, validationUtils } from '@verdaccio/core';\n\nexport function validateName(\n _req: Request,\n _res: Response,\n next: NextFunction,\n value: string,\n name: string\n) {\n if (validationUtils.validateName(value)) {\n next();\n } else {\n next(errorUtils.getBadRequest('invalid ' + name));\n }\n}\n\nexport function validatePackage(\n _req: Request,\n _res,\n next: NextFunction,\n value: string,\n name: string\n) {\n if (validationUtils.validatePackage(value)) {\n next();\n } else {\n next(errorUtils.getBadRequest('invalid ' + name));\n }\n}\n"],"mappings":";;AAIA,SAAgB,aACd,MACA,MACA,MACA,OACA,MACA;CACA,IAAI,gBAAA,gBAAgB,aAAa,KAAK,GACpC,KAAK;MAEL,KAAK,gBAAA,WAAW,cAAc,aAAa,IAAI,CAAC;AAEpD;AAEA,SAAgB,gBACd,MACA,MACA,MACA,OACA,MACA;CACA,IAAI,gBAAA,gBAAgB,gBAAgB,KAAK,GACvC,KAAK;MAEL,KAAK,gBAAA,WAAW,cAAc,aAAa,IAAI,CAAC;AAEpD"}
@@ -1 +1 @@
1
- {"version":3,"file":"validation.mjs","names":[],"sources":["../../src/middlewares/validation.ts"],"sourcesContent":["import type { NextFunction, Request, Response } from 'express';\n\nimport { errorUtils, validationUtils } from '@verdaccio/core';\n\nexport function validateName(\n _req: Request,\n _res: Response,\n next: NextFunction,\n value: string,\n name: string\n) {\n if (validationUtils.validateName(value)) {\n next();\n } else {\n next(errorUtils.getBadRequest('invalid ' + name));\n }\n}\n\nexport function validatePackage(\n _req: Request,\n _res,\n next: NextFunction,\n value: string,\n name: string\n) {\n if (validationUtils.validatePackage(value)) {\n next();\n } else {\n next(errorUtils.getBadRequest('invalid ' + name));\n }\n}\n"],"mappings":";;AAIA,SAAgB,aACd,MACA,MACA,MACA,OACA,MACA;AACA,KAAI,gBAAgB,aAAa,MAAM,CACrC,OAAM;KAEN,MAAK,WAAW,cAAc,aAAa,KAAK,CAAC;;AAIrD,SAAgB,gBACd,MACA,MACA,MACA,OACA,MACA;AACA,KAAI,gBAAgB,gBAAgB,MAAM,CACxC,OAAM;KAEN,MAAK,WAAW,cAAc,aAAa,KAAK,CAAC"}
1
+ {"version":3,"file":"validation.mjs","names":[],"sources":["../../src/middlewares/validation.ts"],"sourcesContent":["import type { NextFunction, Request, Response } from 'express';\n\nimport { errorUtils, validationUtils } from '@verdaccio/core';\n\nexport function validateName(\n _req: Request,\n _res: Response,\n next: NextFunction,\n value: string,\n name: string\n) {\n if (validationUtils.validateName(value)) {\n next();\n } else {\n next(errorUtils.getBadRequest('invalid ' + name));\n }\n}\n\nexport function validatePackage(\n _req: Request,\n _res,\n next: NextFunction,\n value: string,\n name: string\n) {\n if (validationUtils.validatePackage(value)) {\n next();\n } else {\n next(errorUtils.getBadRequest('invalid ' + name));\n }\n}\n"],"mappings":";;AAIA,SAAgB,aACd,MACA,MACA,MACA,OACA,MACA;CACA,IAAI,gBAAgB,aAAa,KAAK,GACpC,KAAK;MAEL,KAAK,WAAW,cAAc,aAAa,IAAI,CAAC;AAEpD;AAEA,SAAgB,gBACd,MACA,MACA,MACA,OACA,MACA;CACA,IAAI,gBAAgB,gBAAgB,KAAK,GACvC,KAAK;MAEL,KAAK,WAAW,cAAc,aAAa,IAAI,CAAC;AAEpD"}
@@ -15,7 +15,7 @@ let node_path = require("node:path");
15
15
  node_path = require_runtime.__toESM(node_path);
16
16
  let _verdaccio_url = require("@verdaccio/url");
17
17
  //#region src/middlewares/web/render-web.ts
18
- var debug$1 = (0, debug.default)("verdaccio:web:render");
18
+ var debug$1 = (0, debug.default)("verdaccio:middleware:web:render");
19
19
  function renderWebMiddleware(config, tokenMiddleware, pluginOptions) {
20
20
  const { staticPath, manifest, manifestFiles } = pluginOptions;
21
21
  debug$1("static path %o", staticPath);
@@ -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: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 res.sendFile(file, 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) },\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 res.sendFile(file, 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) },\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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"}
@@ -10,7 +10,7 @@ import fs from "node:fs";
10
10
  import path from "node:path";
11
11
  import { isURLhasValidProtocol } from "@verdaccio/url";
12
12
  //#region src/middlewares/web/render-web.ts
13
- var debug = buildDebug("verdaccio:web:render");
13
+ var debug = buildDebug("verdaccio:middleware:web:render");
14
14
  function renderWebMiddleware(config, tokenMiddleware, pluginOptions) {
15
15
  const { staticPath, manifest, manifestFiles } = pluginOptions;
16
16
  debug("static path %o", staticPath);
@@ -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: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 res.sendFile(file, 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) },\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 res.sendFile(file, 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) },\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,oCAAoC;AAE7D,SAAgB,sBAAsB,MAAM,KAAK,MAAY;CAG3D,MAAM,gBAAgB,IAAI,UAAU,gBAAA,QAAQ,eAAe;AAC3D,KAAI,CAAC,iBAAiB,EAAE,kBAAkB,UAAU,kBAAkB,eAAe;AACnF,UAAM,iEAA+D;AACrE,MAAI,OAAO,gBAAA,QAAQ,gBAAgB,OAAO;;AAK5C,KAAI,CAAC,IAAI,UAAU,gBAAA,QAAQ,IAAI,EAAE;AAC/B,UAAM,4EAA4E;AAClF,MAAI,OAAO,gBAAA,QAAQ,KAAK,qBAAqB;;CAK/C,MAAM,MAAM,IAAI,UAAU,gBAAA,QAAQ,IAAI;AACtC,KAAI,CAAC,OAAO,QAAQ,WAAW;AAC7B,UAAM,2EAAyE;AAC/E,MAAI,OAAO,gBAAA,QAAQ,KAAK,UAAU;;CAKpC,MAAM,MAAM,IAAI,UAAU,gBAAA,QAAQ,IAAI;AACtC,KAAI,CAAC,OAAO,EAAE,QAAQ,OAAO,IAAI,WAAW,IAAI,GAAG;AACjD,UAAM,2EAAyE;AAC/E,MAAI,OAAO,gBAAA,QAAQ,KAAK,gBAAgB;;AAE1C,OAAM"}
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 +1 @@
1
- {"version":3,"file":"security.mjs","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,QAAQ,WAAW,oCAAoC;AAE7D,SAAgB,sBAAsB,MAAM,KAAK,MAAY;CAG3D,MAAM,gBAAgB,IAAI,UAAU,QAAQ,eAAe;AAC3D,KAAI,CAAC,iBAAiB,EAAE,kBAAkB,UAAU,kBAAkB,eAAe;AACnF,QAAM,iEAA+D;AACrE,MAAI,OAAO,QAAQ,gBAAgB,OAAO;;AAK5C,KAAI,CAAC,IAAI,UAAU,QAAQ,IAAI,EAAE;AAC/B,QAAM,4EAA4E;AAClF,MAAI,OAAO,QAAQ,KAAK,qBAAqB;;CAK/C,MAAM,MAAM,IAAI,UAAU,QAAQ,IAAI;AACtC,KAAI,CAAC,OAAO,QAAQ,WAAW;AAC7B,QAAM,2EAAyE;AAC/E,MAAI,OAAO,QAAQ,KAAK,UAAU;;CAKpC,MAAM,MAAM,IAAI,UAAU,QAAQ,IAAI;AACtC,KAAI,CAAC,OAAO,EAAE,QAAQ,OAAO,IAAI,WAAW,IAAI,GAAG;AACjD,QAAM,2EAAyE;AAC/E,MAAI,OAAO,QAAQ,KAAK,gBAAgB;;AAE1C,OAAM"}
1
+ {"version":3,"file":"security.mjs","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,QAAQ,WAAW,mCAAmC;AAE5D,SAAgB,sBAAsB,MAAM,KAAK,MAAY;CAG3D,MAAM,gBAAgB,IAAI,UAAU,QAAQ,cAAc;CAC1D,IAAI,CAAC,iBAAiB,EAAE,kBAAkB,UAAU,kBAAkB,eAAe;EACnF,MAAM,gEAA8D;EACpE,IAAI,OAAO,QAAQ,gBAAgB,MAAM;CAC3C;CAIA,IAAI,CAAC,IAAI,UAAU,QAAQ,GAAG,GAAG;EAC/B,MAAM,2EAA2E;EACjF,IAAI,OAAO,QAAQ,KAAK,oBAAoB;CAC9C;CAIA,MAAM,MAAM,IAAI,UAAU,QAAQ,GAAG;CACrC,IAAI,CAAC,OAAO,QAAQ,WAAW;EAC7B,MAAM,0EAAwE;EAC9E,IAAI,OAAO,QAAQ,KAAK,SAAS;CACnC;CAIA,MAAM,MAAM,IAAI,UAAU,QAAQ,GAAG;CACrC,IAAI,CAAC,OAAO,EAAE,QAAQ,OAAO,IAAI,WAAW,GAAG,IAAI;EACjD,MAAM,0EAAwE;EAC9E,IAAI,OAAO,QAAQ,KAAK,eAAe;CACzC;CACA,KAAK;AACP"}
@@ -19,6 +19,10 @@ var sendFileCallback = (next) => (err) => {
19
19
  * @param next Express next function
20
20
  */
21
21
  function sendFileSafe(baseDir, filename, res, next) {
22
+ if (!filename) {
23
+ debug$1("empty filename requested");
24
+ return next();
25
+ }
22
26
  const safeFilename = _verdaccio_core.fileUtils.sanitizeFilename(filename);
23
27
  const safe = _verdaccio_core.fileUtils.resolveSafePath(baseDir, safeFilename);
24
28
  if (!safe) {
@@ -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 // 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 res.sendFile(safe, sendFileCallback(next));\n}\n"],"mappings":";;;;;;;AAKA,IAAM,WAAA,GAAA,MAAA,SAAmB,iCAAiC;AAE1D,IAAa,oBAAoB,UAAU,QAAQ;AACjD,KAAI,CAAC,IACH;AAEF,KAAI,IAAI,WAAW,gBAAA,YAAY,UAC7B,OAAM;KAEN,MAAK,IAAI;;;;;;;;;AAWb,SAAgB,aAAa,SAAiB,UAAkB,KAAK,MAAM;CAEzE,MAAM,eAAe,gBAAA,UAAU,iBAAiB,SAAS;CAGzD,MAAM,OAAO,gBAAA,UAAU,gBAAgB,SAAS,aAAa;AAC7D,KAAI,CAAC,MAAM;AACT,UAAM,gCAAgC,aAAa;AACnD,SAAO,MAAM;;AAKf,KAAI,SADoB,UAAA,QAAK,QAAQ,QAAQ,EACf;AAC5B,UAAM,0CAA0C,KAAK;AACrD,SAAO,MAAM;;AAGf,SAAM,iBAAiB,KAAK;AAC5B,KAAI,SAAS,MAAM,iBAAiB,KAAK,CAAC"}
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 res.sendFile(safe, 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;CAC3B,IAAI,SAAS,MAAM,iBAAiB,IAAI,CAAC;AAC3C"}
@@ -16,6 +16,10 @@ var sendFileCallback = (next) => (err) => {
16
16
  * @param next Express next function
17
17
  */
18
18
  function sendFileSafe(baseDir, filename, res, next) {
19
+ if (!filename) {
20
+ debug("empty filename requested");
21
+ return next();
22
+ }
19
23
  const safeFilename = fileUtils.sanitizeFilename(filename);
20
24
  const safe = fileUtils.resolveSafePath(baseDir, safeFilename);
21
25
  if (!safe) {
@@ -1 +1 @@
1
- {"version":3,"file":"file-utils.mjs","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 // 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 res.sendFile(safe, sendFileCallback(next));\n}\n"],"mappings":";;;;AAKA,IAAM,QAAQ,WAAW,iCAAiC;AAE1D,IAAa,oBAAoB,UAAU,QAAQ;AACjD,KAAI,CAAC,IACH;AAEF,KAAI,IAAI,WAAW,YAAY,UAC7B,OAAM;KAEN,MAAK,IAAI;;;;;;;;;AAWb,SAAgB,aAAa,SAAiB,UAAkB,KAAK,MAAM;CAEzE,MAAM,eAAe,UAAU,iBAAiB,SAAS;CAGzD,MAAM,OAAO,UAAU,gBAAgB,SAAS,aAAa;AAC7D,KAAI,CAAC,MAAM;AACT,QAAM,gCAAgC,aAAa;AACnD,SAAO,MAAM;;AAKf,KAAI,SADoB,KAAK,QAAQ,QAAQ,EACf;AAC5B,QAAM,0CAA0C,KAAK;AACrD,SAAO,MAAM;;AAGf,OAAM,iBAAiB,KAAK;AAC5B,KAAI,SAAS,MAAM,iBAAiB,KAAK,CAAC"}
1
+ {"version":3,"file":"file-utils.mjs","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 res.sendFile(safe, sendFileCallback(next));\n}\n"],"mappings":";;;;AAKA,IAAM,QAAQ,WAAW,gCAAgC;AAEzD,IAAa,oBAAoB,UAAU,QAAQ;CACjD,IAAI,CAAC,KACH;CAEF,IAAI,IAAI,WAAW,YAAY,WAC7B,KAAK;MAEL,KAAK,GAAG;AAEZ;;;;;;;;AASA,SAAgB,aAAa,SAAiB,UAAkB,KAAK,MAAM;CAGzE,IAAI,CAAC,UAAU;EACb,MAAM,0BAA0B;EAChC,OAAO,KAAK;CACd;CAGA,MAAM,eAAe,UAAU,iBAAiB,QAAQ;CAGxD,MAAM,OAAO,UAAU,gBAAgB,SAAS,YAAY;CAC5D,IAAI,CAAC,MAAM;EACT,MAAM,gCAAgC,YAAY;EAClD,OAAO,KAAK;CACd;CAIA,IAAI,SADoB,KAAK,QAAQ,OACxB,GAAiB;EAC5B,MAAM,0CAA0C,IAAI;EACpD,OAAO,KAAK;CACd;CAEA,MAAM,iBAAiB,IAAI;CAC3B,IAAI,SAAS,MAAM,iBAAiB,IAAI,CAAC;AAC3C"}
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","names":[],"sources":["../../../../src/middlewares/web/utils/manifest.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nexport type Manifest = {\n // goes on first place at the header\n ico: string;\n css: string[];\n js: string[];\n};\n\nconst debug = buildDebug('verdaccio:middleware:web:render:manifest');\n\nexport function getManifestValue(\n manifestItems: string[],\n manifest,\n basePath: string = ''\n): string[] {\n return manifestItems?.map((item) => {\n debug('resolve item %o', item);\n const resolvedItem = `${stripTrailingSlash(basePath)}/${stripLeadingSlash(manifest[item])}`;\n debug('resolved item %o', resolvedItem);\n return resolvedItem;\n });\n}\n\nfunction stripTrailingSlash(path: string): string {\n return path.replace(/\\/$/, '');\n}\n\nfunction stripLeadingSlash(path: string): string {\n return path.replace(/^\\//, '');\n}\n"],"mappings":";;;;AASA,IAAM,WAAA,GAAA,MAAA,SAAmB,2CAA2C;AAEpE,SAAgB,iBACd,eACA,UACA,WAAmB,IACT;AACV,QAAO,eAAe,KAAK,SAAS;AAClC,UAAM,mBAAmB,KAAK;EAC9B,MAAM,eAAe,GAAG,mBAAmB,SAAS,CAAC,GAAG,kBAAkB,SAAS,MAAM;AACzF,UAAM,oBAAoB,aAAa;AACvC,SAAO;GACP;;AAGJ,SAAS,mBAAmB,MAAsB;AAChD,QAAO,KAAK,QAAQ,OAAO,GAAG;;AAGhC,SAAS,kBAAkB,MAAsB;AAC/C,QAAO,KAAK,QAAQ,OAAO,GAAG"}
1
+ {"version":3,"file":"manifest.js","names":[],"sources":["../../../../src/middlewares/web/utils/manifest.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nexport type Manifest = {\n // goes on first place at the header\n ico: string;\n css: string[];\n js: string[];\n};\n\nconst debug = buildDebug('verdaccio:middleware:web:render:manifest');\n\nexport function getManifestValue(\n manifestItems: string[],\n manifest,\n basePath: string = ''\n): string[] {\n return manifestItems?.map((item) => {\n debug('resolve item %o', item);\n const resolvedItem = `${stripTrailingSlash(basePath)}/${stripLeadingSlash(manifest[item])}`;\n debug('resolved item %o', resolvedItem);\n return resolvedItem;\n });\n}\n\nfunction stripTrailingSlash(path: string): string {\n return path.replace(/\\/$/, '');\n}\n\nfunction stripLeadingSlash(path: string): string {\n return path.replace(/^\\//, '');\n}\n"],"mappings":";;;;AASA,IAAM,WAAA,GAAA,MAAA,SAAmB,0CAA0C;AAEnE,SAAgB,iBACd,eACA,UACA,WAAmB,IACT;CACV,OAAO,eAAe,KAAK,SAAS;EAClC,QAAM,mBAAmB,IAAI;EAC7B,MAAM,eAAe,GAAG,mBAAmB,QAAQ,EAAE,GAAG,kBAAkB,SAAS,KAAK;EACxF,QAAM,oBAAoB,YAAY;EACtC,OAAO;CACT,CAAC;AACH;AAEA,SAAS,mBAAmB,MAAsB;CAChD,OAAO,KAAK,QAAQ,OAAO,EAAE;AAC/B;AAEA,SAAS,kBAAkB,MAAsB;CAC/C,OAAO,KAAK,QAAQ,OAAO,EAAE;AAC/B"}
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.mjs","names":[],"sources":["../../../../src/middlewares/web/utils/manifest.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nexport type Manifest = {\n // goes on first place at the header\n ico: string;\n css: string[];\n js: string[];\n};\n\nconst debug = buildDebug('verdaccio:middleware:web:render:manifest');\n\nexport function getManifestValue(\n manifestItems: string[],\n manifest,\n basePath: string = ''\n): string[] {\n return manifestItems?.map((item) => {\n debug('resolve item %o', item);\n const resolvedItem = `${stripTrailingSlash(basePath)}/${stripLeadingSlash(manifest[item])}`;\n debug('resolved item %o', resolvedItem);\n return resolvedItem;\n });\n}\n\nfunction stripTrailingSlash(path: string): string {\n return path.replace(/\\/$/, '');\n}\n\nfunction stripLeadingSlash(path: string): string {\n return path.replace(/^\\//, '');\n}\n"],"mappings":";;AASA,IAAM,QAAQ,WAAW,2CAA2C;AAEpE,SAAgB,iBACd,eACA,UACA,WAAmB,IACT;AACV,QAAO,eAAe,KAAK,SAAS;AAClC,QAAM,mBAAmB,KAAK;EAC9B,MAAM,eAAe,GAAG,mBAAmB,SAAS,CAAC,GAAG,kBAAkB,SAAS,MAAM;AACzF,QAAM,oBAAoB,aAAa;AACvC,SAAO;GACP;;AAGJ,SAAS,mBAAmB,MAAsB;AAChD,QAAO,KAAK,QAAQ,OAAO,GAAG;;AAGhC,SAAS,kBAAkB,MAAsB;AAC/C,QAAO,KAAK,QAAQ,OAAO,GAAG"}
1
+ {"version":3,"file":"manifest.mjs","names":[],"sources":["../../../../src/middlewares/web/utils/manifest.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nexport type Manifest = {\n // goes on first place at the header\n ico: string;\n css: string[];\n js: string[];\n};\n\nconst debug = buildDebug('verdaccio:middleware:web:render:manifest');\n\nexport function getManifestValue(\n manifestItems: string[],\n manifest,\n basePath: string = ''\n): string[] {\n return manifestItems?.map((item) => {\n debug('resolve item %o', item);\n const resolvedItem = `${stripTrailingSlash(basePath)}/${stripLeadingSlash(manifest[item])}`;\n debug('resolved item %o', resolvedItem);\n return resolvedItem;\n });\n}\n\nfunction stripTrailingSlash(path: string): string {\n return path.replace(/\\/$/, '');\n}\n\nfunction stripLeadingSlash(path: string): string {\n return path.replace(/^\\//, '');\n}\n"],"mappings":";;AASA,IAAM,QAAQ,WAAW,0CAA0C;AAEnE,SAAgB,iBACd,eACA,UACA,WAAmB,IACT;CACV,OAAO,eAAe,KAAK,SAAS;EAClC,MAAM,mBAAmB,IAAI;EAC7B,MAAM,eAAe,GAAG,mBAAmB,QAAQ,EAAE,GAAG,kBAAkB,SAAS,KAAK;EACxF,MAAM,oBAAoB,YAAY;EACtC,OAAO;CACT,CAAC;AACH;AAEA,SAAS,mBAAmB,MAAsB;CAChD,OAAO,KAAK,QAAQ,OAAO,EAAE;AAC/B;AAEA,SAAS,kBAAkB,MAAsB;CAC/C,OAAO,KAAK,QAAQ,OAAO,EAAE;AAC/B"}
@@ -8,7 +8,7 @@ var cache = new (require("lru-cache")).LRUCache({
8
8
  max: 500,
9
9
  ttl: 1e3 * 60 * 60
10
10
  });
11
- var debug$1 = (0, debug.default)("verdaccio:web:render");
11
+ var debug$1 = (0, debug.default)("verdaccio:middleware:web:render:html");
12
12
  var defaultManifestFiles = {
13
13
  js: ["vendors.js", "main.js"],
14
14
  ico: "favicon.ico",
@@ -1 +1 @@
1
- {"version":3,"file":"renderHTML.js","names":[],"sources":["../../../../src/middlewares/web/utils/renderHTML.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport type { Response } from 'express';\nimport { LRUCache } from 'lru-cache';\n\nimport { HEADERS } from '@verdaccio/core';\nimport type { ConfigYaml, TemplateUIOptions } from '@verdaccio/types';\n\nimport type { Manifest } from './manifest';\nimport renderTemplate from './template';\nimport type { AssetManifest } from './template';\n\n// Cache for rendered HTML templates: max 500 entries, 1 hour TTL\nconst cache = new LRUCache({ max: 500, ttl: 1000 * 60 * 60 });\n\nconst debug = buildDebug('verdaccio:web:render');\n\nconst defaultManifestFiles: Manifest = {\n js: ['vendors.js', 'main.js'],\n ico: 'favicon.ico',\n css: [],\n};\n\nexport default function renderHTML(\n config: ConfigYaml,\n manifest: AssetManifest,\n manifestFiles: Manifest | null | undefined,\n options: TemplateUIOptions,\n res: Response\n) {\n // @ts-ignore\n const needHtmlCache = [undefined, null].includes(config?.web?.html_cache)\n ? true\n : config?.web?.html_cache;\n\n const scriptsBodyBefore = config?.web?.scriptsBodyBefore || config?.web?.scriptsbodyBefore || [];\n const scriptsBodyAfter = config?.web?.scriptsBodyAfter || [];\n const metaScripts = config?.web?.metaScripts || [];\n\n let webPage;\n\n const cacheKey = `template:${JSON.stringify(options)}`;\n\n try {\n webPage = cache.get(cacheKey);\n if (!webPage) {\n webPage = renderTemplate(\n {\n manifest: manifestFiles ?? defaultManifestFiles,\n options,\n scriptsBodyAfter,\n metaScripts,\n scriptsBodyBefore,\n },\n manifest\n );\n\n if (needHtmlCache) {\n cache.set(cacheKey, webPage);\n debug('set template cache');\n }\n } else {\n debug('reuse template cache');\n }\n } catch (error: any) {\n throw new Error('theme could not be loaded', { cause: error });\n }\n res.setHeader('Content-Type', HEADERS.TEXT_HTML);\n res.send(webPage);\n debug('web rendered');\n}\n"],"mappings":";;;;;;AAYA,IAAM,QAAQ,2BAAI,SAAS;CAAE,KAAK;CAAK,KAAK,MAAO,KAAK;CAAI,CAAC;AAE7D,IAAM,WAAA,GAAA,MAAA,SAAmB,uBAAuB;AAEhD,IAAM,uBAAiC;CACrC,IAAI,CAAC,cAAc,UAAU;CAC7B,KAAK;CACL,KAAK,EAAE;CACR;AAED,SAAwB,WACtB,QACA,UACA,eACA,SACA,KACA;CAEA,MAAM,gBAAgB,CAAC,KAAA,GAAW,KAAK,CAAC,SAAS,QAAQ,KAAK,WAAW,GACrE,OACA,QAAQ,KAAK;CAEjB,MAAM,oBAAoB,QAAQ,KAAK,qBAAqB,QAAQ,KAAK,qBAAqB,EAAE;CAChG,MAAM,mBAAmB,QAAQ,KAAK,oBAAoB,EAAE;CAC5D,MAAM,cAAc,QAAQ,KAAK,eAAe,EAAE;CAElD,IAAI;CAEJ,MAAM,WAAW,YAAY,KAAK,UAAU,QAAQ;AAEpD,KAAI;AACF,YAAU,MAAM,IAAI,SAAS;AAC7B,MAAI,CAAC,SAAS;AACZ,aAAU,iBAAA,QACR;IACE,UAAU,iBAAiB;IAC3B;IACA;IACA;IACA;IACD,EACD,SACD;AAED,OAAI,eAAe;AACjB,UAAM,IAAI,UAAU,QAAQ;AAC5B,YAAM,qBAAqB;;QAG7B,SAAM,uBAAuB;UAExB,OAAY;AACnB,QAAM,IAAI,MAAM,6BAA6B,EAAE,OAAO,OAAO,CAAC;;AAEhE,KAAI,UAAU,gBAAgB,gBAAA,QAAQ,UAAU;AAChD,KAAI,KAAK,QAAQ;AACjB,SAAM,eAAe"}
1
+ {"version":3,"file":"renderHTML.js","names":[],"sources":["../../../../src/middlewares/web/utils/renderHTML.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport type { Response } from 'express';\nimport { LRUCache } from 'lru-cache';\n\nimport { HEADERS } from '@verdaccio/core';\nimport type { ConfigYaml, TemplateUIOptions } from '@verdaccio/types';\n\nimport type { Manifest } from './manifest';\nimport renderTemplate from './template';\nimport type { AssetManifest } from './template';\n\n// Cache for rendered HTML templates: max 500 entries, 1 hour TTL\nconst cache = new LRUCache({ max: 500, ttl: 1000 * 60 * 60 });\n\nconst debug = buildDebug('verdaccio:middleware:web:render:html');\n\nconst defaultManifestFiles: Manifest = {\n js: ['vendors.js', 'main.js'],\n ico: 'favicon.ico',\n css: [],\n};\n\nexport default function renderHTML(\n config: ConfigYaml,\n manifest: AssetManifest,\n manifestFiles: Manifest | null | undefined,\n options: TemplateUIOptions,\n res: Response\n) {\n // @ts-ignore\n const needHtmlCache = [undefined, null].includes(config?.web?.html_cache)\n ? true\n : config?.web?.html_cache;\n\n const scriptsBodyBefore = config?.web?.scriptsBodyBefore || config?.web?.scriptsbodyBefore || [];\n const scriptsBodyAfter = config?.web?.scriptsBodyAfter || [];\n const metaScripts = config?.web?.metaScripts || [];\n\n let webPage;\n\n const cacheKey = `template:${JSON.stringify(options)}`;\n\n try {\n webPage = cache.get(cacheKey);\n if (!webPage) {\n webPage = renderTemplate(\n {\n manifest: manifestFiles ?? defaultManifestFiles,\n options,\n scriptsBodyAfter,\n metaScripts,\n scriptsBodyBefore,\n },\n manifest\n );\n\n if (needHtmlCache) {\n cache.set(cacheKey, webPage);\n debug('set template cache');\n }\n } else {\n debug('reuse template cache');\n }\n } catch (error: any) {\n throw new Error('theme could not be loaded', { cause: error });\n }\n res.setHeader('Content-Type', HEADERS.TEXT_HTML);\n res.send(webPage);\n debug('web rendered');\n}\n"],"mappings":";;;;;;AAYA,IAAM,QAAQ,wBAAI,GAAA,SAAS;CAAE,KAAK;CAAK,KAAK,MAAO,KAAK;AAAG,CAAC;AAE5D,IAAM,WAAA,GAAA,MAAA,SAAmB,sCAAsC;AAE/D,IAAM,uBAAiC;CACrC,IAAI,CAAC,cAAc,SAAS;CAC5B,KAAK;CACL,KAAK,CAAC;AACR;AAEA,SAAwB,WACtB,QACA,UACA,eACA,SACA,KACA;CAEA,MAAM,gBAAgB,CAAC,KAAA,GAAW,IAAI,EAAE,SAAS,QAAQ,KAAK,UAAU,IACpE,OACA,QAAQ,KAAK;CAEjB,MAAM,oBAAoB,QAAQ,KAAK,qBAAqB,QAAQ,KAAK,qBAAqB,CAAC;CAC/F,MAAM,mBAAmB,QAAQ,KAAK,oBAAoB,CAAC;CAC3D,MAAM,cAAc,QAAQ,KAAK,eAAe,CAAC;CAEjD,IAAI;CAEJ,MAAM,WAAW,YAAY,KAAK,UAAU,OAAO;CAEnD,IAAI;EACF,UAAU,MAAM,IAAI,QAAQ;EAC5B,IAAI,CAAC,SAAS;GACZ,UAAU,iBAAA,QACR;IACE,UAAU,iBAAiB;IAC3B;IACA;IACA;IACA;GACF,GACA,QACF;GAEA,IAAI,eAAe;IACjB,MAAM,IAAI,UAAU,OAAO;IAC3B,QAAM,oBAAoB;GAC5B;EACF,OACE,QAAM,sBAAsB;CAEhC,SAAS,OAAY;EACnB,MAAM,IAAI,MAAM,6BAA6B,EAAE,OAAO,MAAM,CAAC;CAC/D;CACA,IAAI,UAAU,gBAAgB,gBAAA,QAAQ,SAAS;CAC/C,IAAI,KAAK,OAAO;CAChB,QAAM,cAAc;AACtB"}
@@ -7,7 +7,7 @@ var cache = new LRUCache({
7
7
  max: 500,
8
8
  ttl: 1e3 * 60 * 60
9
9
  });
10
- var debug = buildDebug("verdaccio:web:render");
10
+ var debug = buildDebug("verdaccio:middleware:web:render:html");
11
11
  var defaultManifestFiles = {
12
12
  js: ["vendors.js", "main.js"],
13
13
  ico: "favicon.ico",