@verdaccio/middleware 6.0.0-6-next.38 → 6.0.0-6-next.40

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 (68) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/build/index.d.ts +3 -0
  3. package/build/index.js +24 -0
  4. package/build/index.js.map +1 -1
  5. package/build/middlewares/rate-limit.d.ts +2 -0
  6. package/build/middlewares/rate-limit.js +13 -0
  7. package/build/middlewares/rate-limit.js.map +1 -0
  8. package/build/middlewares/user-agent.d.ts +2 -0
  9. package/build/middlewares/user-agent.js +14 -0
  10. package/build/middlewares/user-agent.js.map +1 -0
  11. package/build/middlewares/validation.d.ts +2 -3
  12. package/build/middlewares/validation.js +2 -2
  13. package/build/middlewares/validation.js.map +1 -1
  14. package/build/middlewares/web/index.d.ts +1 -0
  15. package/build/middlewares/web/index.js +14 -0
  16. package/build/middlewares/web/index.js.map +1 -0
  17. package/build/middlewares/web/render-web.d.ts +1 -0
  18. package/build/middlewares/web/render-web.js +103 -0
  19. package/build/middlewares/web/render-web.js.map +1 -0
  20. package/build/middlewares/web/security.d.ts +1 -0
  21. package/build/middlewares/web/security.js +19 -0
  22. package/build/middlewares/web/security.js.map +1 -0
  23. package/build/middlewares/web/utils/manifest.d.ts +6 -0
  24. package/build/middlewares/web/utils/manifest.js +18 -0
  25. package/build/middlewares/web/utils/manifest.js.map +1 -0
  26. package/build/middlewares/web/utils/renderHTML.d.ts +7 -0
  27. package/build/middlewares/web/utils/renderHTML.js +133 -0
  28. package/build/middlewares/web/utils/renderHTML.js.map +1 -0
  29. package/build/middlewares/web/utils/template.d.ts +13 -0
  30. package/build/middlewares/web/utils/template.js +38 -0
  31. package/build/middlewares/web/utils/template.js.map +1 -0
  32. package/build/middlewares/web/utils/web-utils.d.ts +2 -0
  33. package/build/middlewares/web/utils/web-utils.js +24 -0
  34. package/build/middlewares/web/utils/web-utils.js.map +1 -0
  35. package/build/middlewares/web/web-api.d.ts +1 -0
  36. package/build/middlewares/web/web-api.js +32 -0
  37. package/build/middlewares/web/web-api.js.map +1 -0
  38. package/build/middlewares/web/web-middleware.d.ts +2 -0
  39. package/build/middlewares/web/web-middleware.js +25 -0
  40. package/build/middlewares/web/web-middleware.js.map +1 -0
  41. package/package.json +10 -5
  42. package/src/index.ts +3 -0
  43. package/src/middlewares/rate-limit.ts +8 -0
  44. package/src/middlewares/user-agent.ts +10 -0
  45. package/src/middlewares/validation.ts +2 -16
  46. package/src/middlewares/web/index.ts +1 -0
  47. package/src/middlewares/web/render-web.ts +99 -0
  48. package/src/middlewares/web/security.ts +13 -0
  49. package/src/middlewares/web/utils/manifest.ts +23 -0
  50. package/src/middlewares/web/utils/renderHTML.ts +134 -0
  51. package/src/middlewares/web/utils/template.ts +50 -0
  52. package/src/middlewares/web/utils/web-utils.ts +18 -0
  53. package/src/middlewares/web/web-api.ts +27 -0
  54. package/src/middlewares/web/web-middleware.ts +15 -0
  55. package/test/__snapshots__/template.test.ts.snap +151 -0
  56. package/test/_helper.ts +8 -0
  57. package/test/config/default-test.yaml +28 -0
  58. package/test/config/login-disabled.yaml +29 -0
  59. package/test/config/web.yaml +23 -0
  60. package/test/manifest.test.ts +11 -0
  61. package/test/partials/htmlParser.ts +1 -0
  62. package/test/partials/manifest/manifest.json +64 -0
  63. package/test/render.spec.ts +106 -0
  64. package/test/static/main.js +3 -0
  65. package/test/static/vendor.js +3 -0
  66. package/test/template.test.ts +74 -0
  67. package/test/utils.spec.ts +13 -0
  68. package/tsconfig.json +6 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","names":["debug","buildDebug","renderTemplate","template","manifest","options","base","title","JSON","stringify","metaScripts","join","scriptsbodyBefore","getManifestValue","js","map","item","scriptsBodyAfter"],"sources":["../../../../src/middlewares/web/utils/template.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport { TemplateUIOptions } from '@verdaccio/types';\n\nimport { Manifest, getManifestValue } from './manifest';\n\nconst debug = buildDebug('verdaccio:web:render:template');\n\nexport type Template = {\n manifest: Manifest;\n options: TemplateUIOptions;\n metaScripts?: string[];\n scriptsBodyAfter?: string[];\n scriptsbodyBefore?: string[];\n};\n\n// the outcome of the Webpack Manifest Plugin\nexport interface WebpackManifest {\n [key: string]: string;\n}\n\nexport default function renderTemplate(template: Template, manifest: WebpackManifest) {\n debug('template %o', template);\n debug('manifest %o', manifest);\n\n return `\n <!DOCTYPE html>\n <html lang=\"en-us\"> \n <head>\n <meta charset=\"utf-8\">\n <base href=\"${template?.options.base}\">\n <title>${template?.options?.title ?? ''}</title> \n <link rel=\"icon\" href=\"${template?.options.base}-/static/favicon.ico\"/>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /> \n <script>\n window.__VERDACCIO_BASENAME_UI_OPTIONS=${JSON.stringify(template.options)}\n </script>\n ${template?.metaScripts ? template.metaScripts.join('') : ''}\n </head> \n <body class=\"body\">\n ${template?.scriptsbodyBefore ? template.scriptsbodyBefore.join('') : ''}\n <div id=\"root\"></div>\n ${getManifestValue(template.manifest.js, manifest, template?.options.base)\n .map((item) => `<script defer=\"defer\" src=\"${item}\"></script>`)\n .join('')}\n ${template?.scriptsBodyAfter ? template.scriptsBodyAfter.join('') : ''}\n </body>\n </html>\n `;\n}\n"],"mappings":";;;;;;AAAA;AAIA;AAAwD;AAExD,MAAMA,KAAK,GAAG,IAAAC,cAAU,EAAC,+BAA+B,CAAC;AAe1C,SAASC,cAAc,CAACC,QAAkB,EAAEC,QAAyB,EAAE;EAAA;EACpFJ,KAAK,CAAC,aAAa,EAAEG,QAAQ,CAAC;EAC9BH,KAAK,CAAC,aAAa,EAAEI,QAAQ,CAAC;EAE9B,OAAQ;AACV;AACA;AACA;AACA;AACA,sBAAsBD,QAAQ,aAARA,QAAQ,uBAARA,QAAQ,CAAEE,OAAO,CAACC,IAAK;AAC7C,iBAAe,yBAAEH,QAAQ,aAARA,QAAQ,4CAARA,QAAQ,CAAEE,OAAO,sDAAjB,kBAAmBE,KAAK,yEAAI,EAAG;AAChD,iCAAiCJ,QAAQ,aAARA,QAAQ,uBAARA,QAAQ,CAAEE,OAAO,CAACC,IAAK;AACxD;AACA;AACA,qDAAqDE,IAAI,CAACC,SAAS,CAACN,QAAQ,CAACE,OAAO,CAAE;AACtF;AACA,UAAUF,QAAQ,aAARA,QAAQ,eAARA,QAAQ,CAAEO,WAAW,GAAGP,QAAQ,CAACO,WAAW,CAACC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAG;AACrE;AACA;AACA,QAAQR,QAAQ,aAARA,QAAQ,eAARA,QAAQ,CAAES,iBAAiB,GAAGT,QAAQ,CAACS,iBAAiB,CAACD,IAAI,CAAC,EAAE,CAAC,GAAG,EAAG;AAC/E;AACA,UAAU,IAAAE,0BAAgB,EAACV,QAAQ,CAACC,QAAQ,CAACU,EAAE,EAAEV,QAAQ,EAAED,QAAQ,aAARA,QAAQ,uBAARA,QAAQ,CAAEE,OAAO,CAACC,IAAI,CAAC,CACvES,GAAG,CAAEC,IAAI,IAAM,8BAA6BA,IAAK,aAAY,CAAC,CAC9DL,IAAI,CAAC,EAAE,CAAE;AACpB,UAAUR,QAAQ,aAARA,QAAQ,eAARA,QAAQ,CAAEc,gBAAgB,GAAGd,QAAQ,CAACc,gBAAgB,CAACN,IAAI,CAAC,EAAE,CAAC,GAAG,EAAG;AAC/E;AACA;AACA,GAAG;AACH"}
@@ -0,0 +1,2 @@
1
+ export declare function validatePrimaryColor(primaryColor: any): any;
2
+ export declare function hasLogin(config: any): boolean;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.hasLogin = hasLogin;
7
+ exports.validatePrimaryColor = validatePrimaryColor;
8
+ var _debug = _interopRequireDefault(require("debug"));
9
+ var _lodash = _interopRequireDefault(require("lodash"));
10
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
+ const debug = (0, _debug.default)('verdaccio:web:middlwares');
12
+ function validatePrimaryColor(primaryColor) {
13
+ const isHex = /^#([0-9A-F]{3}){1,2}$/i.test(primaryColor);
14
+ if (!isHex) {
15
+ debug('invalid primary color %o', primaryColor);
16
+ return;
17
+ }
18
+ return primaryColor;
19
+ }
20
+ function hasLogin(config) {
21
+ var _config$web, _config$web2;
22
+ return _lodash.default.isNil(config === null || config === void 0 ? void 0 : (_config$web = config.web) === null || _config$web === void 0 ? void 0 : _config$web.login) || (config === null || config === void 0 ? void 0 : (_config$web2 = config.web) === null || _config$web2 === void 0 ? void 0 : _config$web2.login) === true;
23
+ }
24
+ //# sourceMappingURL=web-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-utils.js","names":["debug","buildDebug","validatePrimaryColor","primaryColor","isHex","test","hasLogin","config","_","isNil","web","login"],"sources":["../../../../src/middlewares/web/utils/web-utils.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport _ from 'lodash';\n\nconst debug = buildDebug('verdaccio:web:middlwares');\n\nexport function validatePrimaryColor(primaryColor) {\n const isHex = /^#([0-9A-F]{3}){1,2}$/i.test(primaryColor);\n if (!isHex) {\n debug('invalid primary color %o', primaryColor);\n return;\n }\n\n return primaryColor;\n}\n\nexport function hasLogin(config: any) {\n return _.isNil(config?.web?.login) || config?.web?.login === true;\n}\n"],"mappings":";;;;;;;AAAA;AACA;AAAuB;AAEvB,MAAMA,KAAK,GAAG,IAAAC,cAAU,EAAC,0BAA0B,CAAC;AAE7C,SAASC,oBAAoB,CAACC,YAAY,EAAE;EACjD,MAAMC,KAAK,GAAG,wBAAwB,CAACC,IAAI,CAACF,YAAY,CAAC;EACzD,IAAI,CAACC,KAAK,EAAE;IACVJ,KAAK,CAAC,0BAA0B,EAAEG,YAAY,CAAC;IAC/C;EACF;EAEA,OAAOA,YAAY;AACrB;AAEO,SAASG,QAAQ,CAACC,MAAW,EAAE;EAAA;EACpC,OAAOC,eAAC,CAACC,KAAK,CAACF,MAAM,aAANA,MAAM,sCAANA,MAAM,CAAEG,GAAG,gDAAX,YAAaC,KAAK,CAAC,IAAI,CAAAJ,MAAM,aAANA,MAAM,uCAANA,MAAM,CAAEG,GAAG,iDAAX,aAAaC,KAAK,MAAK,IAAI;AACnE"}
@@ -0,0 +1 @@
1
+ export declare function webMiddleware(tokenMiddleware: any, webEndpointsApi: any): import("express-serve-static-core").Router;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.webMiddleware = webMiddleware;
7
+ var _express = _interopRequireWildcard(require("express"));
8
+ var _validation = require("../validation");
9
+ var _security = require("./security");
10
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
11
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
12
+ function webMiddleware(tokenMiddleware, webEndpointsApi) {
13
+ // eslint-disable-next-line new-cap
14
+ const route = (0, _express.Router)();
15
+ // validate all of these params as a package name
16
+ // this might be too harsh, so ask if it causes trouble=
17
+ route.param('package', _validation.validatePackage);
18
+ route.param('filename', _validation.validateName);
19
+ route.param('version', _validation.validateName);
20
+ route.use(_express.default.urlencoded({
21
+ extended: false
22
+ }));
23
+ if (typeof tokenMiddleware === 'function') {
24
+ route.use(tokenMiddleware);
25
+ }
26
+ route.use(_security.setSecurityWebHeaders);
27
+ if (webEndpointsApi) {
28
+ route.use(webEndpointsApi);
29
+ }
30
+ return route;
31
+ }
32
+ //# sourceMappingURL=web-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-api.js","names":["webMiddleware","tokenMiddleware","webEndpointsApi","route","Router","param","validatePackage","validateName","use","express","urlencoded","extended","setSecurityWebHeaders"],"sources":["../../../src/middlewares/web/web-api.ts"],"sourcesContent":["import express from 'express';\nimport { Router } from 'express';\n\nimport { validateName, validatePackage } from '../validation';\nimport { setSecurityWebHeaders } from './security';\n\nexport function webMiddleware(tokenMiddleware, webEndpointsApi) {\n // eslint-disable-next-line new-cap\n const route = Router();\n // validate all of these params as a package name\n // this might be too harsh, so ask if it causes trouble=\n route.param('package', validatePackage);\n route.param('filename', validateName);\n route.param('version', validateName);\n route.use(express.urlencoded({ extended: false }));\n\n if (typeof tokenMiddleware === 'function') {\n route.use(tokenMiddleware);\n }\n\n route.use(setSecurityWebHeaders);\n\n if (webEndpointsApi) {\n route.use(webEndpointsApi);\n }\n return route;\n}\n"],"mappings":";;;;;;AAAA;AAGA;AACA;AAAmD;AAAA;AAE5C,SAASA,aAAa,CAACC,eAAe,EAAEC,eAAe,EAAE;EAC9D;EACA,MAAMC,KAAK,GAAG,IAAAC,eAAM,GAAE;EACtB;EACA;EACAD,KAAK,CAACE,KAAK,CAAC,SAAS,EAAEC,2BAAe,CAAC;EACvCH,KAAK,CAACE,KAAK,CAAC,UAAU,EAAEE,wBAAY,CAAC;EACrCJ,KAAK,CAACE,KAAK,CAAC,SAAS,EAAEE,wBAAY,CAAC;EACpCJ,KAAK,CAACK,GAAG,CAACC,gBAAO,CAACC,UAAU,CAAC;IAAEC,QAAQ,EAAE;EAAM,CAAC,CAAC,CAAC;EAElD,IAAI,OAAOV,eAAe,KAAK,UAAU,EAAE;IACzCE,KAAK,CAACK,GAAG,CAACP,eAAe,CAAC;EAC5B;EAEAE,KAAK,CAACK,GAAG,CAACI,+BAAqB,CAAC;EAEhC,IAAIV,eAAe,EAAE;IACnBC,KAAK,CAACK,GAAG,CAACN,eAAe,CAAC;EAC5B;EACA,OAAOC,KAAK;AACd"}
@@ -0,0 +1,2 @@
1
+ declare const _default: (config: any, middlewares: any, pluginOptions: any) => any;
2
+ export default _default;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _express = _interopRequireDefault(require("express"));
8
+ var _renderWeb = require("./render-web");
9
+ var _webApi = require("./web-api");
10
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
+ var _default = (config, middlewares, pluginOptions) => {
12
+ // eslint-disable-next-line new-cap
13
+ const router = _express.default.Router();
14
+ const {
15
+ tokenMiddleware,
16
+ webEndpointsApi
17
+ } = middlewares;
18
+ // render web
19
+ router.use('/', (0, _renderWeb.renderWebMiddleware)(config, tokenMiddleware, pluginOptions));
20
+ // web endpoints, search, packages, etc
21
+ router.use('/-/verdaccio/', (0, _webApi.webMiddleware)(tokenMiddleware, webEndpointsApi));
22
+ return router;
23
+ };
24
+ exports.default = _default;
25
+ //# sourceMappingURL=web-middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-middleware.js","names":["config","middlewares","pluginOptions","router","express","Router","tokenMiddleware","webEndpointsApi","use","renderWebMiddleware","webMiddleware"],"sources":["../../../src/middlewares/web/web-middleware.ts"],"sourcesContent":["import express from 'express';\n\nimport { renderWebMiddleware } from './render-web';\nimport { webMiddleware } from './web-api';\n\nexport default (config, middlewares, pluginOptions): any => {\n // eslint-disable-next-line new-cap\n const router = express.Router();\n const { tokenMiddleware, webEndpointsApi } = middlewares;\n // render web\n router.use('/', renderWebMiddleware(config, tokenMiddleware, pluginOptions));\n // web endpoints, search, packages, etc\n router.use('/-/verdaccio/', webMiddleware(tokenMiddleware, webEndpointsApi));\n return router;\n};\n"],"mappings":";;;;;;AAAA;AAEA;AACA;AAA0C;AAAA,eAE3B,CAACA,MAAM,EAAEC,WAAW,EAAEC,aAAa,KAAU;EAC1D;EACA,MAAMC,MAAM,GAAGC,gBAAO,CAACC,MAAM,EAAE;EAC/B,MAAM;IAAEC,eAAe;IAAEC;EAAgB,CAAC,GAAGN,WAAW;EACxD;EACAE,MAAM,CAACK,GAAG,CAAC,GAAG,EAAE,IAAAC,8BAAmB,EAACT,MAAM,EAAEM,eAAe,EAAEJ,aAAa,CAAC,CAAC;EAC5E;EACAC,MAAM,CAACK,GAAG,CAAC,eAAe,EAAE,IAAAE,qBAAa,EAACJ,eAAe,EAAEC,eAAe,CAAC,CAAC;EAC5E,OAAOJ,MAAM;AACf,CAAC;AAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verdaccio/middleware",
3
- "version": "6.0.0-6-next.38",
3
+ "version": "6.0.0-6-next.40",
4
4
  "description": "express middleware utils",
5
5
  "main": "./build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -29,18 +29,23 @@
29
29
  "node": ">=12"
30
30
  },
31
31
  "dependencies": {
32
- "@verdaccio/core": "6.0.0-6-next.59",
33
- "@verdaccio/utils": "6.0.0-6-next.27",
32
+ "@verdaccio/core": "6.0.0-6-next.61",
33
+ "@verdaccio/utils": "6.0.0-6-next.29",
34
+ "@verdaccio/config": "6.0.0-6-next.61",
35
+ "@verdaccio/url": "11.0.0-6-next.27",
34
36
  "debug": "4.3.4",
37
+ "lru-cache": "7.14.1",
38
+ "express": "4.18.2",
35
39
  "lodash": "4.17.21",
36
- "mime": "2.6.0"
40
+ "mime": "2.6.0",
41
+ "express-rate-limit": "5.5.1"
37
42
  },
38
43
  "funding": {
39
44
  "type": "opencollective",
40
45
  "url": "https://opencollective.com/verdaccio"
41
46
  },
42
47
  "devDependencies": {
43
- "@verdaccio/logger": "6.0.0-6-next.27",
48
+ "@verdaccio/logger": "6.0.0-6-next.29",
44
49
  "body-parser": "1.20.1",
45
50
  "supertest": "6.3.3"
46
51
  },
package/src/index.ts CHANGED
@@ -7,6 +7,9 @@ export { expectJson } from './middlewares/json';
7
7
  export { antiLoop } from './middlewares/antiLoop';
8
8
  export { final } from './middlewares/final';
9
9
  export { allow } from './middlewares/allow';
10
+ export { rateLimit } from './middlewares/rate-limit';
11
+ export { userAgent } from './middlewares/user-agent';
12
+ export { webMiddleware } from './middlewares/web';
10
13
  export { errorReportingMiddleware, handleError } from './middlewares/error';
11
14
  export {
12
15
  log,
@@ -0,0 +1,8 @@
1
+ import RateLimit from 'express-rate-limit';
2
+
3
+ import { RateLimit as RateLimitType } from '@verdaccio/types';
4
+
5
+ export function rateLimit(rateLimitOptions?: RateLimitType) {
6
+ const limiter = new RateLimit(rateLimitOptions);
7
+ return limiter;
8
+ }
@@ -0,0 +1,10 @@
1
+ import { getUserAgent } from '@verdaccio/config';
2
+
3
+ import { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';
4
+
5
+ export function userAgent(config) {
6
+ return function (_req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
7
+ res.setHeader('x-powered-by', getUserAgent(config?.user_agent));
8
+ next();
9
+ };
10
+ }
@@ -4,15 +4,7 @@ import {
4
4
  validatePackage as utilValidatePackage,
5
5
  } from '@verdaccio/utils';
6
6
 
7
- import { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types';
8
-
9
- export function validateName(
10
- req: $RequestExtend,
11
- res: $ResponseExtend,
12
- next: $NextFunctionVer,
13
- value: string,
14
- name: string
15
- ): void {
7
+ export function validateName(_req, _res, next, value: string, name: string) {
16
8
  if (value === '-') {
17
9
  // special case in couchdb usually
18
10
  next('route');
@@ -23,13 +15,7 @@ export function validateName(
23
15
  }
24
16
  }
25
17
 
26
- export function validatePackage(
27
- req: $RequestExtend,
28
- res: $ResponseExtend,
29
- next: $NextFunctionVer,
30
- value: string,
31
- name: string
32
- ): void {
18
+ export function validatePackage(_req, _res, next, value: string, name: string) {
33
19
  if (value === '-') {
34
20
  // special case in couchdb usually
35
21
  next('route');
@@ -0,0 +1 @@
1
+ export { default as webMiddleware } from './web-middleware';
@@ -0,0 +1,99 @@
1
+ import buildDebug from 'debug';
2
+ import express from 'express';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+
6
+ import { HTTP_STATUS } from '@verdaccio/core';
7
+ import { isURLhasValidProtocol } from '@verdaccio/url';
8
+
9
+ import { setSecurityWebHeaders } from './security';
10
+ import renderHTML, { isHTTPProtocol } from './utils/renderHTML';
11
+
12
+ const debug = buildDebug('verdaccio:web:render');
13
+
14
+ const sendFileCallback = (next) => (err) => {
15
+ if (!err) {
16
+ return;
17
+ }
18
+ if (err.status === HTTP_STATUS.NOT_FOUND) {
19
+ next();
20
+ } else {
21
+ next(err);
22
+ }
23
+ };
24
+
25
+ export function renderWebMiddleware(config, tokenMiddleware, pluginOptions) {
26
+ const { staticPath, manifest, manifestFiles } = pluginOptions;
27
+ debug('static path %o', staticPath);
28
+
29
+ /* eslint new-cap:off */
30
+ const router = express.Router();
31
+ if (typeof tokenMiddleware === 'function') {
32
+ router.use(tokenMiddleware);
33
+ }
34
+ router.use(setSecurityWebHeaders);
35
+
36
+ // Logo
37
+ let logoURI = config?.web?.logo ?? '';
38
+ if (logoURI && !isURLhasValidProtocol(logoURI)) {
39
+ // URI related to a local file
40
+
41
+ // Note: `path.join` will break on Windows, because it transforms `/` to `\`
42
+ // Use POSIX version `path.posix.join` instead.
43
+ logoURI = path.posix.join('/-/static/', path.basename(logoURI));
44
+ router.get(logoURI, function (req, res, next) {
45
+ res.sendFile(path.resolve(config.web.logo), sendFileCallback(next));
46
+ debug('render static');
47
+ });
48
+ }
49
+
50
+ // Static
51
+ router.get('/-/static/*', function (req, res, next) {
52
+ const filename = req.params[0];
53
+ const file = `${staticPath}/${filename}`;
54
+ debug('render static file %o', file);
55
+ res.sendFile(file, sendFileCallback(next));
56
+ });
57
+
58
+ // logo
59
+ if (config?.web?.logo && !isHTTPProtocol(config?.web?.logo)) {
60
+ // URI related to a local file
61
+ const absoluteLocalFile = path.posix.resolve(config.web.logo);
62
+ debug('serve local logo %s', absoluteLocalFile);
63
+ try {
64
+ // TODO: remove existsSync by async alternative
65
+ if (
66
+ fs.existsSync(absoluteLocalFile) &&
67
+ typeof fs.accessSync(absoluteLocalFile, fs.constants.R_OK) === 'undefined'
68
+ ) {
69
+ // Note: `path.join` will break on Windows, because it transforms `/` to `\`
70
+ // Use POSIX version `path.posix.join` instead.
71
+ config.web.logo = path.posix.join('/-/static/', path.basename(config.web.logo));
72
+ router.get(config.web.logo, function (_req, res, next) {
73
+ // @ts-ignore
74
+ debug('serve custom logo web:%s - local:%s', config.web.logo, absoluteLocalFile);
75
+ res.sendFile(absoluteLocalFile, sendFileCallback(next));
76
+ });
77
+ debug('enabled custom logo %s', config.web.logo);
78
+ } else {
79
+ config.web.logo = undefined;
80
+ debug(`web logo is wrong, path ${absoluteLocalFile} does not exist or is not readable`);
81
+ }
82
+ } catch {
83
+ config.web.logo = undefined;
84
+ debug(`web logo is wrong, path ${absoluteLocalFile} does not exist or is not readable`);
85
+ }
86
+ }
87
+
88
+ router.get('/-/web/:section/*', function (req, res) {
89
+ renderHTML(config, manifest, manifestFiles, req, res);
90
+ debug('render html section');
91
+ });
92
+
93
+ router.get('/', function (req, res) {
94
+ renderHTML(config, manifest, manifestFiles, req, res);
95
+ debug('render root');
96
+ });
97
+
98
+ return router;
99
+ }
@@ -0,0 +1,13 @@
1
+ import { HEADERS } from '@verdaccio/core';
2
+
3
+ export function setSecurityWebHeaders(_req, res, next): void {
4
+ // disable loading in frames (clickjacking, etc.)
5
+ res.header(HEADERS.FRAMES_OPTIONS, 'deny');
6
+ // avoid stablish connections outside of domain
7
+ res.header(HEADERS.CSP, "connect-src 'self'");
8
+ // https://stackoverflow.com/questions/18337630/what-is-x-content-type-options-nosniff
9
+ res.header(HEADERS.CTO, 'nosniff');
10
+ // https://stackoverflow.com/questions/9090577/what-is-the-http-header-x-xss-protection
11
+ res.header(HEADERS.XSS, '1; mode=block');
12
+ next();
13
+ }
@@ -0,0 +1,23 @@
1
+ import buildDebug from 'debug';
2
+
3
+ export type Manifest = {
4
+ // goes on first place at the header
5
+ ico: string;
6
+ css: string[];
7
+ js: string[];
8
+ };
9
+
10
+ const debug = buildDebug('verdaccio:web:render:manifest');
11
+
12
+ export function getManifestValue(
13
+ manifestItems: string[],
14
+ manifest,
15
+ basePath: string = ''
16
+ ): string[] {
17
+ return manifestItems?.map((item) => {
18
+ debug('resolve item %o', item);
19
+ const resolvedItem = `${basePath}${manifest[item]}`;
20
+ debug('resolved item %o', resolvedItem);
21
+ return resolvedItem;
22
+ });
23
+ }
@@ -0,0 +1,134 @@
1
+ import buildDebug from 'debug';
2
+ import LRU from 'lru-cache';
3
+ import path from 'path';
4
+ import { URL } from 'url';
5
+
6
+ import { WEB_TITLE } from '@verdaccio/config';
7
+ import { HEADERS } from '@verdaccio/core';
8
+ import { TemplateUIOptions } from '@verdaccio/types';
9
+ import { getPublicUrl } from '@verdaccio/url';
10
+
11
+ import renderTemplate from './template';
12
+ import { hasLogin, validatePrimaryColor } from './web-utils';
13
+
14
+ const DEFAULT_LANGUAGE = 'es-US';
15
+ const cache = new LRU({ max: 500, ttl: 1000 * 60 * 60 });
16
+
17
+ const debug = buildDebug('verdaccio:web:render');
18
+
19
+ const defaultManifestFiles = {
20
+ js: ['runtime.js', 'vendors.js', 'main.js'],
21
+ ico: 'favicon.ico',
22
+ };
23
+
24
+ /**
25
+ * Check if URI is starting with "http://", "https://" or "//"
26
+ * @param {string} uri
27
+ */
28
+ export function isHTTPProtocol(uri: string): boolean {
29
+ return /^(https?:)?\/\//.test(uri);
30
+ }
31
+
32
+ export function resolveLogo(config, req) {
33
+ const isLocalFile = config?.web?.logo && !isHTTPProtocol(config?.web?.logo);
34
+
35
+ if (isLocalFile) {
36
+ return `${getPublicUrl(config?.url_prefix, req)}-/static/${path.basename(config?.web?.logo)}`;
37
+ } else if (isHTTPProtocol(config?.web?.logo)) {
38
+ return config?.web?.logo;
39
+ } else {
40
+ return '';
41
+ }
42
+ }
43
+
44
+ export default function renderHTML(config, manifest, manifestFiles, req, res) {
45
+ const { url_prefix } = config;
46
+ const base = getPublicUrl(config?.url_prefix, req);
47
+ const basename = new URL(base).pathname;
48
+ const language = config?.i18n?.web ?? DEFAULT_LANGUAGE;
49
+ const needHtmlCache = [undefined, null].includes(config?.web?.html_cache)
50
+ ? true
51
+ : config.web.html_cache;
52
+ const darkMode = config?.web?.darkMode ?? false;
53
+ const title = config?.web?.title ?? WEB_TITLE;
54
+ const login = hasLogin(config);
55
+ const scope = config?.web?.scope ?? '';
56
+ const logoURI = resolveLogo(config, req);
57
+ const pkgManagers = config?.web?.pkgManagers ?? ['yarn', 'pnpm', 'npm'];
58
+ const version = config?.web?.version;
59
+ const flags = {
60
+ ...config.flags,
61
+ // legacy from 5.x
62
+ ...config.experiments,
63
+ };
64
+ const primaryColor = validatePrimaryColor(config?.web?.primary_color) ?? '#4b5e40';
65
+ const {
66
+ scriptsBodyAfter,
67
+ metaScripts,
68
+ scriptsbodyBefore,
69
+ showInfo,
70
+ showSettings,
71
+ showThemeSwitch,
72
+ showFooter,
73
+ showSearch,
74
+ showDownloadTarball,
75
+ } = Object.assign(
76
+ {},
77
+ {
78
+ scriptsBodyAfter: [],
79
+ bodyBefore: [],
80
+ metaScripts: [],
81
+ },
82
+ config?.web
83
+ );
84
+ const options: TemplateUIOptions = {
85
+ showInfo,
86
+ showSettings,
87
+ showThemeSwitch,
88
+ showFooter,
89
+ showSearch,
90
+ showDownloadTarball,
91
+ darkMode,
92
+ url_prefix,
93
+ basename,
94
+ base,
95
+ primaryColor,
96
+ version,
97
+ logoURI,
98
+ flags,
99
+ login,
100
+ pkgManagers,
101
+ title,
102
+ scope,
103
+ language,
104
+ };
105
+
106
+ let webPage;
107
+
108
+ try {
109
+ webPage = cache.get('template');
110
+ if (!webPage) {
111
+ webPage = renderTemplate(
112
+ {
113
+ manifest: manifestFiles ?? defaultManifestFiles,
114
+ options,
115
+ scriptsBodyAfter,
116
+ metaScripts,
117
+ scriptsbodyBefore,
118
+ },
119
+ manifest
120
+ );
121
+ if (needHtmlCache) {
122
+ cache.set('template', webPage);
123
+ debug('set template cache');
124
+ }
125
+ } else {
126
+ debug('reuse template cache');
127
+ }
128
+ } catch (error: any) {
129
+ throw new Error(`theme could not be load, stack ${error.stack}`);
130
+ }
131
+ res.setHeader('Content-Type', HEADERS.TEXT_HTML);
132
+ res.send(webPage);
133
+ debug('web rendered');
134
+ }
@@ -0,0 +1,50 @@
1
+ import buildDebug from 'debug';
2
+
3
+ import { TemplateUIOptions } from '@verdaccio/types';
4
+
5
+ import { Manifest, getManifestValue } from './manifest';
6
+
7
+ const debug = buildDebug('verdaccio:web:render:template');
8
+
9
+ export type Template = {
10
+ manifest: Manifest;
11
+ options: TemplateUIOptions;
12
+ metaScripts?: string[];
13
+ scriptsBodyAfter?: string[];
14
+ scriptsbodyBefore?: string[];
15
+ };
16
+
17
+ // the outcome of the Webpack Manifest Plugin
18
+ export interface WebpackManifest {
19
+ [key: string]: string;
20
+ }
21
+
22
+ export default function renderTemplate(template: Template, manifest: WebpackManifest) {
23
+ debug('template %o', template);
24
+ debug('manifest %o', manifest);
25
+
26
+ return `
27
+ <!DOCTYPE html>
28
+ <html lang="en-us">
29
+ <head>
30
+ <meta charset="utf-8">
31
+ <base href="${template?.options.base}">
32
+ <title>${template?.options?.title ?? ''}</title>
33
+ <link rel="icon" href="${template?.options.base}-/static/favicon.ico"/>
34
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
35
+ <script>
36
+ window.__VERDACCIO_BASENAME_UI_OPTIONS=${JSON.stringify(template.options)}
37
+ </script>
38
+ ${template?.metaScripts ? template.metaScripts.join('') : ''}
39
+ </head>
40
+ <body class="body">
41
+ ${template?.scriptsbodyBefore ? template.scriptsbodyBefore.join('') : ''}
42
+ <div id="root"></div>
43
+ ${getManifestValue(template.manifest.js, manifest, template?.options.base)
44
+ .map((item) => `<script defer="defer" src="${item}"></script>`)
45
+ .join('')}
46
+ ${template?.scriptsBodyAfter ? template.scriptsBodyAfter.join('') : ''}
47
+ </body>
48
+ </html>
49
+ `;
50
+ }
@@ -0,0 +1,18 @@
1
+ import buildDebug from 'debug';
2
+ import _ from 'lodash';
3
+
4
+ const debug = buildDebug('verdaccio:web:middlwares');
5
+
6
+ export function validatePrimaryColor(primaryColor) {
7
+ const isHex = /^#([0-9A-F]{3}){1,2}$/i.test(primaryColor);
8
+ if (!isHex) {
9
+ debug('invalid primary color %o', primaryColor);
10
+ return;
11
+ }
12
+
13
+ return primaryColor;
14
+ }
15
+
16
+ export function hasLogin(config: any) {
17
+ return _.isNil(config?.web?.login) || config?.web?.login === true;
18
+ }
@@ -0,0 +1,27 @@
1
+ import express from 'express';
2
+ import { Router } from 'express';
3
+
4
+ import { validateName, validatePackage } from '../validation';
5
+ import { setSecurityWebHeaders } from './security';
6
+
7
+ export function webMiddleware(tokenMiddleware, webEndpointsApi) {
8
+ // eslint-disable-next-line new-cap
9
+ const route = Router();
10
+ // validate all of these params as a package name
11
+ // this might be too harsh, so ask if it causes trouble=
12
+ route.param('package', validatePackage);
13
+ route.param('filename', validateName);
14
+ route.param('version', validateName);
15
+ route.use(express.urlencoded({ extended: false }));
16
+
17
+ if (typeof tokenMiddleware === 'function') {
18
+ route.use(tokenMiddleware);
19
+ }
20
+
21
+ route.use(setSecurityWebHeaders);
22
+
23
+ if (webEndpointsApi) {
24
+ route.use(webEndpointsApi);
25
+ }
26
+ return route;
27
+ }
@@ -0,0 +1,15 @@
1
+ import express from 'express';
2
+
3
+ import { renderWebMiddleware } from './render-web';
4
+ import { webMiddleware } from './web-api';
5
+
6
+ export default (config, middlewares, pluginOptions): any => {
7
+ // eslint-disable-next-line new-cap
8
+ const router = express.Router();
9
+ const { tokenMiddleware, webEndpointsApi } = middlewares;
10
+ // render web
11
+ router.use('/', renderWebMiddleware(config, tokenMiddleware, pluginOptions));
12
+ // web endpoints, search, packages, etc
13
+ router.use('/-/verdaccio/', webMiddleware(tokenMiddleware, webEndpointsApi));
14
+ return router;
15
+ };