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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/build/dist-tags.d.ts +4 -4
  2. package/build/dist-tags.js +10 -6
  3. package/build/dist-tags.js.map +1 -1
  4. package/build/dist-tags.mjs +11 -7
  5. package/build/dist-tags.mjs.map +1 -1
  6. package/build/index.d.ts +4 -4
  7. package/build/index.js +1 -1
  8. package/build/index.js.map +1 -1
  9. package/build/index.mjs +1 -1
  10. package/build/index.mjs.map +1 -1
  11. package/build/package.d.ts +3 -3
  12. package/build/package.js +4 -3
  13. package/build/package.js.map +1 -1
  14. package/build/package.mjs +5 -4
  15. package/build/package.mjs.map +1 -1
  16. package/build/ping.d.ts +1 -1
  17. package/build/publish.d.ts +4 -4
  18. package/build/publish.js +8 -6
  19. package/build/publish.js.map +1 -1
  20. package/build/publish.mjs +9 -8
  21. package/build/publish.mjs.map +1 -1
  22. package/build/search.d.ts +1 -1
  23. package/build/user.d.ts +3 -3
  24. package/build/user.js +4 -3
  25. package/build/user.js.map +1 -1
  26. package/build/user.mjs +5 -4
  27. package/build/user.mjs.map +1 -1
  28. package/build/v1/login.d.ts +4 -4
  29. package/build/v1/login.js +6 -6
  30. package/build/v1/login.js.map +1 -1
  31. package/build/v1/login.mjs +7 -7
  32. package/build/v1/login.mjs.map +1 -1
  33. package/build/v1/profile.d.ts +3 -3
  34. package/build/v1/profile.js.map +1 -1
  35. package/build/v1/search.d.ts +4 -4
  36. package/build/v1/search.js +33 -13
  37. package/build/v1/search.js.map +1 -1
  38. package/build/v1/search.mjs +34 -14
  39. package/build/v1/search.mjs.map +1 -1
  40. package/build/v1/token.d.ts +4 -4
  41. package/build/v1/token.js +5 -3
  42. package/build/v1/token.js.map +1 -1
  43. package/build/v1/token.mjs +6 -4
  44. package/build/v1/token.mjs.map +1 -1
  45. package/build/whoami.d.ts +1 -1
  46. package/build/whoami.js.map +1 -1
  47. package/package.json +11 -11
package/build/user.js CHANGED
@@ -15,12 +15,12 @@ function user_default(route, auth, config, logger) {
15
15
  res.status(_verdaccio_core.HTTP_STATUS.OK);
16
16
  return next({ ok: false });
17
17
  }
18
- const username = req.params.org_couchdb_user.split(":")[1];
18
+ const userName = _verdaccio_core.reqUtils.paramToString(req.params.org_couchdb_user)?.split(":")[1] ?? "";
19
19
  const message = _verdaccio_core.authUtils.getAuthenticatedMessage(req.remote_user.name);
20
20
  debug$1("user authenticated message %o", message);
21
21
  res.status(_verdaccio_core.HTTP_STATUS.OK);
22
22
  next({
23
- name: username,
23
+ name: userName,
24
24
  email: "",
25
25
  ok: message
26
26
  });
@@ -46,7 +46,8 @@ function user_default(route, auth, config, logger) {
46
46
  const { name, password } = req.body;
47
47
  debug$1("login or adduser");
48
48
  const remoteName = req?.remote_user?.name;
49
- if (!_verdaccio_core.validationUtils.validateUserName(req.params.org_couchdb_user, name)) return next(_verdaccio_core.errorUtils.getBadRequest(_verdaccio_core.API_ERROR.USERNAME_MISMATCH));
49
+ const userName = _verdaccio_core.reqUtils.paramToString(req.params.org_couchdb_user);
50
+ if (!_verdaccio_core.validationUtils.validateUserName(userName, name)) return next(_verdaccio_core.errorUtils.getBadRequest(_verdaccio_core.API_ERROR.USERNAME_MISMATCH));
50
51
  if (typeof remoteName !== "undefined" && typeof name === "string" && remoteName === name) {
51
52
  debug$1("login: no remote user detected");
52
53
  auth.authenticate(name, password, async function callbackAuthenticate(err, user) {
package/build/user.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"user.js","names":[],"sources":["../src/user.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport type { Response, Router } from 'express';\n\nimport type { Auth } from '@verdaccio/auth';\nimport { getApiToken } from '@verdaccio/auth';\nimport { createRemoteUser } from '@verdaccio/config';\nimport {\n API_ERROR,\n API_MESSAGE,\n HEADERS,\n HTTP_STATUS,\n authUtils,\n cryptoUtils,\n errorUtils,\n validationUtils,\n} from '@verdaccio/core';\nimport { USER_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Config, Logger, RemoteUser } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../types/custom';\n\nconst debug = buildDebug('verdaccio:api:user');\n\nexport default function (route: Router, auth: Auth, config: Config, logger: Logger): void {\n route.get(\n USER_API_ENDPOINTS.get_user,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n debug('verifying user');\n\n if (\n !req.remote_user ||\n typeof req.remote_user.name !== 'string' ||\n req.remote_user.name === ''\n ) {\n debug('user not logged in');\n res.status(HTTP_STATUS.OK);\n return next({ ok: false });\n }\n\n const username = req.params.org_couchdb_user.split(':')[1];\n const message = authUtils.getAuthenticatedMessage(req.remote_user.name);\n debug('user authenticated message %o', message);\n res.status(HTTP_STATUS.OK);\n next({\n // 'npm owner' requires user info\n // TODO: we don't have the email\n name: username,\n email: '',\n ok: message,\n });\n }\n );\n\n /**\n * \n * body example\n * req.body = {\n _id: \"org.couchdb.user:jjjj\",\n name: \"jjjj\",\n password: \"jjjj\",\n type: \"user\",\n roles: [],\n date: \"2022-07-08T15:51:04.002Z\",\n }\n * \n * @export\n * @param {Router} route\n * @param {Auth} auth\n * @param {Config} config\n */\n route.put(\n USER_API_ENDPOINTS.add_user,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n const { name, password } = req.body;\n debug('login or adduser');\n const remoteName = req?.remote_user?.name;\n\n if (!validationUtils.validateUserName(req.params.org_couchdb_user, name)) {\n return next(errorUtils.getBadRequest(API_ERROR.USERNAME_MISMATCH));\n }\n\n if (typeof remoteName !== 'undefined' && typeof name === 'string' && remoteName === name) {\n debug('login: no remote user detected');\n auth.authenticate(\n name,\n password,\n async function callbackAuthenticate(err, user): Promise<void> {\n if (err) {\n logger.trace(\n { name, err },\n 'authenticating for user @{username} failed. Error: @{err.message}'\n );\n return next(\n errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.BAD_USERNAME_PASSWORD)\n );\n }\n\n const restoredRemoteUser: RemoteUser = createRemoteUser(name, user?.groups || []);\n const token = await getApiToken(auth, config, restoredRemoteUser, password);\n debug('login: new token');\n if (!token) {\n return next(errorUtils.getUnauthorized());\n }\n\n res.status(HTTP_STATUS.CREATED);\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n\n const message = authUtils.getAuthenticatedMessage(req.remote_user.name);\n debug('login: created user message %o', message);\n\n return next({\n ok: message,\n token,\n });\n }\n );\n } else {\n debug('adduser: %o', name);\n if (\n validationUtils.validatePassword(password, config?.server?.passwordValidationRegex) ===\n false\n ) {\n debug('adduser: invalid password');\n\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.PASSWORD_SHORT));\n }\n\n auth.add_user(name, password, async function (err, user): Promise<void> {\n if (err) {\n if (err.status >= HTTP_STATUS.BAD_REQUEST && err.status < HTTP_STATUS.INTERNAL_ERROR) {\n debug('adduser: error on create user');\n // With npm registering is the same as logging in,\n // and npm accepts only an 409 error.\n // So, changing status code here.\n return next(\n errorUtils.getCode(err.status, err.message) || errorUtils.getConflict(err.message)\n );\n }\n return next(err);\n }\n\n const token =\n name && password\n ? await getApiToken(auth, config, user as RemoteUser, password)\n : undefined;\n if (token) {\n debug('adduser: new token %o', cryptoUtils.mask(token as string, 4));\n }\n if (!token) {\n return next(errorUtils.getUnauthorized());\n }\n\n req.remote_user = user;\n res.status(HTTP_STATUS.CREATED);\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n debug('adduser: user has been created');\n return next({\n ok: `user '${req.body.name}' created`,\n token,\n });\n });\n }\n }\n );\n\n route.delete(\n USER_API_ENDPOINTS.user_token,\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n res.status(HTTP_STATUS.OK);\n next({\n ok: API_MESSAGE.LOGGED_OUT,\n });\n }\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"user.js","names":[],"sources":["../src/user.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport type { Response, Router } from 'express';\n\nimport type { Auth } from '@verdaccio/auth';\nimport { getApiToken } from '@verdaccio/auth';\nimport { createRemoteUser } from '@verdaccio/config';\nimport {\n API_ERROR,\n API_MESSAGE,\n HEADERS,\n HTTP_STATUS,\n authUtils,\n cryptoUtils,\n errorUtils,\n reqUtils,\n validationUtils,\n} from '@verdaccio/core';\nimport { USER_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Config, Logger, RemoteUser } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../types/custom';\n\nconst debug = buildDebug('verdaccio:api:user');\n\nexport default function (route: Router, auth: Auth, config: Config, logger: Logger): void {\n route.get(\n USER_API_ENDPOINTS.get_user,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n debug('verifying user');\n\n if (\n !req.remote_user ||\n typeof req.remote_user.name !== 'string' ||\n req.remote_user.name === ''\n ) {\n debug('user not logged in');\n res.status(HTTP_STATUS.OK);\n return next({ ok: false });\n }\n\n const orgCouchdbUser = reqUtils.paramToString(req.params.org_couchdb_user);\n const userName = orgCouchdbUser?.split(':')[1] ?? '';\n const message = authUtils.getAuthenticatedMessage(req.remote_user.name);\n debug('user authenticated message %o', message);\n res.status(HTTP_STATUS.OK);\n next({\n // 'npm owner' requires user info\n // TODO: we don't have the email\n name: userName,\n email: '',\n ok: message,\n });\n }\n );\n\n /**\n * \n * body example\n * req.body = {\n _id: \"org.couchdb.user:jjjj\",\n name: \"jjjj\",\n password: \"jjjj\",\n type: \"user\",\n roles: [],\n date: \"2022-07-08T15:51:04.002Z\",\n }\n * \n * @export\n * @param {Router} route\n * @param {Auth} auth\n * @param {Config} config\n */\n route.put(\n USER_API_ENDPOINTS.add_user,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n const { name, password } = req.body;\n debug('login or adduser');\n const remoteName = req?.remote_user?.name;\n\n const userName = reqUtils.paramToString(req.params.org_couchdb_user);\n if (!validationUtils.validateUserName(userName, name)) {\n return next(errorUtils.getBadRequest(API_ERROR.USERNAME_MISMATCH));\n }\n\n if (typeof remoteName !== 'undefined' && typeof name === 'string' && remoteName === name) {\n debug('login: no remote user detected');\n auth.authenticate(\n name,\n password,\n async function callbackAuthenticate(err, user): Promise<void> {\n if (err) {\n logger.trace(\n { name, err },\n 'authenticating for user @{username} failed. Error: @{err.message}'\n );\n return next(\n errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.BAD_USERNAME_PASSWORD)\n );\n }\n\n const restoredRemoteUser: RemoteUser = createRemoteUser(name, user?.groups || []);\n const token = await getApiToken(auth, config, restoredRemoteUser, password);\n debug('login: new token');\n if (!token) {\n return next(errorUtils.getUnauthorized());\n }\n\n res.status(HTTP_STATUS.CREATED);\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n\n const message = authUtils.getAuthenticatedMessage(req.remote_user.name);\n debug('login: created user message %o', message);\n\n return next({\n ok: message,\n token,\n });\n }\n );\n } else {\n debug('adduser: %o', name);\n if (\n validationUtils.validatePassword(password, config?.server?.passwordValidationRegex) ===\n false\n ) {\n debug('adduser: invalid password');\n\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.PASSWORD_SHORT));\n }\n\n auth.add_user(name, password, async function (err, user): Promise<void> {\n if (err) {\n if (err.status >= HTTP_STATUS.BAD_REQUEST && err.status < HTTP_STATUS.INTERNAL_ERROR) {\n debug('adduser: error on create user');\n // With npm registering is the same as logging in,\n // and npm accepts only an 409 error.\n // So, changing status code here.\n return next(\n errorUtils.getCode(err.status, err.message) || errorUtils.getConflict(err.message)\n );\n }\n return next(err);\n }\n\n const token =\n name && password\n ? await getApiToken(auth, config, user as RemoteUser, password)\n : undefined;\n if (token) {\n debug('adduser: new token %o', cryptoUtils.mask(token as string, 4));\n }\n if (!token) {\n return next(errorUtils.getUnauthorized());\n }\n\n req.remote_user = user;\n res.status(HTTP_STATUS.CREATED);\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n debug('adduser: user has been created');\n return next({\n ok: `user '${req.body.name}' created`,\n token,\n });\n });\n }\n }\n );\n\n route.delete(\n USER_API_ENDPOINTS.user_token,\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n res.status(HTTP_STATUS.OK);\n next({\n ok: API_MESSAGE.LOGGED_OUT,\n });\n }\n );\n}\n"],"mappings":"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"}
package/build/user.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { USER_API_ENDPOINTS, rateLimit } from "@verdaccio/middleware";
2
- import { API_ERROR, API_MESSAGE, HEADERS, HTTP_STATUS, authUtils, cryptoUtils, errorUtils, validationUtils } from "@verdaccio/core";
2
+ import { API_ERROR, API_MESSAGE, HEADERS, HTTP_STATUS, authUtils, cryptoUtils, errorUtils, reqUtils, validationUtils } from "@verdaccio/core";
3
3
  import buildDebug from "debug";
4
4
  import { getApiToken } from "@verdaccio/auth";
5
5
  import { createRemoteUser } from "@verdaccio/config";
@@ -13,12 +13,12 @@ function user_default(route, auth, config, logger) {
13
13
  res.status(HTTP_STATUS.OK);
14
14
  return next({ ok: false });
15
15
  }
16
- const username = req.params.org_couchdb_user.split(":")[1];
16
+ const userName = reqUtils.paramToString(req.params.org_couchdb_user)?.split(":")[1] ?? "";
17
17
  const message = authUtils.getAuthenticatedMessage(req.remote_user.name);
18
18
  debug("user authenticated message %o", message);
19
19
  res.status(HTTP_STATUS.OK);
20
20
  next({
21
- name: username,
21
+ name: userName,
22
22
  email: "",
23
23
  ok: message
24
24
  });
@@ -44,7 +44,8 @@ function user_default(route, auth, config, logger) {
44
44
  const { name, password } = req.body;
45
45
  debug("login or adduser");
46
46
  const remoteName = req?.remote_user?.name;
47
- if (!validationUtils.validateUserName(req.params.org_couchdb_user, name)) return next(errorUtils.getBadRequest(API_ERROR.USERNAME_MISMATCH));
47
+ const userName = reqUtils.paramToString(req.params.org_couchdb_user);
48
+ if (!validationUtils.validateUserName(userName, name)) return next(errorUtils.getBadRequest(API_ERROR.USERNAME_MISMATCH));
48
49
  if (typeof remoteName !== "undefined" && typeof name === "string" && remoteName === name) {
49
50
  debug("login: no remote user detected");
50
51
  auth.authenticate(name, password, async function callbackAuthenticate(err, user) {
@@ -1 +1 @@
1
- {"version":3,"file":"user.mjs","names":[],"sources":["../src/user.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport type { Response, Router } from 'express';\n\nimport type { Auth } from '@verdaccio/auth';\nimport { getApiToken } from '@verdaccio/auth';\nimport { createRemoteUser } from '@verdaccio/config';\nimport {\n API_ERROR,\n API_MESSAGE,\n HEADERS,\n HTTP_STATUS,\n authUtils,\n cryptoUtils,\n errorUtils,\n validationUtils,\n} from '@verdaccio/core';\nimport { USER_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Config, Logger, RemoteUser } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../types/custom';\n\nconst debug = buildDebug('verdaccio:api:user');\n\nexport default function (route: Router, auth: Auth, config: Config, logger: Logger): void {\n route.get(\n USER_API_ENDPOINTS.get_user,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n debug('verifying user');\n\n if (\n !req.remote_user ||\n typeof req.remote_user.name !== 'string' ||\n req.remote_user.name === ''\n ) {\n debug('user not logged in');\n res.status(HTTP_STATUS.OK);\n return next({ ok: false });\n }\n\n const username = req.params.org_couchdb_user.split(':')[1];\n const message = authUtils.getAuthenticatedMessage(req.remote_user.name);\n debug('user authenticated message %o', message);\n res.status(HTTP_STATUS.OK);\n next({\n // 'npm owner' requires user info\n // TODO: we don't have the email\n name: username,\n email: '',\n ok: message,\n });\n }\n );\n\n /**\n * \n * body example\n * req.body = {\n _id: \"org.couchdb.user:jjjj\",\n name: \"jjjj\",\n password: \"jjjj\",\n type: \"user\",\n roles: [],\n date: \"2022-07-08T15:51:04.002Z\",\n }\n * \n * @export\n * @param {Router} route\n * @param {Auth} auth\n * @param {Config} config\n */\n route.put(\n USER_API_ENDPOINTS.add_user,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n const { name, password } = req.body;\n debug('login or adduser');\n const remoteName = req?.remote_user?.name;\n\n if (!validationUtils.validateUserName(req.params.org_couchdb_user, name)) {\n return next(errorUtils.getBadRequest(API_ERROR.USERNAME_MISMATCH));\n }\n\n if (typeof remoteName !== 'undefined' && typeof name === 'string' && remoteName === name) {\n debug('login: no remote user detected');\n auth.authenticate(\n name,\n password,\n async function callbackAuthenticate(err, user): Promise<void> {\n if (err) {\n logger.trace(\n { name, err },\n 'authenticating for user @{username} failed. Error: @{err.message}'\n );\n return next(\n errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.BAD_USERNAME_PASSWORD)\n );\n }\n\n const restoredRemoteUser: RemoteUser = createRemoteUser(name, user?.groups || []);\n const token = await getApiToken(auth, config, restoredRemoteUser, password);\n debug('login: new token');\n if (!token) {\n return next(errorUtils.getUnauthorized());\n }\n\n res.status(HTTP_STATUS.CREATED);\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n\n const message = authUtils.getAuthenticatedMessage(req.remote_user.name);\n debug('login: created user message %o', message);\n\n return next({\n ok: message,\n token,\n });\n }\n );\n } else {\n debug('adduser: %o', name);\n if (\n validationUtils.validatePassword(password, config?.server?.passwordValidationRegex) ===\n false\n ) {\n debug('adduser: invalid password');\n\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.PASSWORD_SHORT));\n }\n\n auth.add_user(name, password, async function (err, user): Promise<void> {\n if (err) {\n if (err.status >= HTTP_STATUS.BAD_REQUEST && err.status < HTTP_STATUS.INTERNAL_ERROR) {\n debug('adduser: error on create user');\n // With npm registering is the same as logging in,\n // and npm accepts only an 409 error.\n // So, changing status code here.\n return next(\n errorUtils.getCode(err.status, err.message) || errorUtils.getConflict(err.message)\n );\n }\n return next(err);\n }\n\n const token =\n name && password\n ? await getApiToken(auth, config, user as RemoteUser, password)\n : undefined;\n if (token) {\n debug('adduser: new token %o', cryptoUtils.mask(token as string, 4));\n }\n if (!token) {\n return next(errorUtils.getUnauthorized());\n }\n\n req.remote_user = user;\n res.status(HTTP_STATUS.CREATED);\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n debug('adduser: user has been created');\n return next({\n ok: `user '${req.body.name}' created`,\n token,\n });\n });\n }\n }\n );\n\n route.delete(\n USER_API_ENDPOINTS.user_token,\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n res.status(HTTP_STATUS.OK);\n next({\n ok: API_MESSAGE.LOGGED_OUT,\n });\n }\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"user.mjs","names":[],"sources":["../src/user.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport type { Response, Router } from 'express';\n\nimport type { Auth } from '@verdaccio/auth';\nimport { getApiToken } from '@verdaccio/auth';\nimport { createRemoteUser } from '@verdaccio/config';\nimport {\n API_ERROR,\n API_MESSAGE,\n HEADERS,\n HTTP_STATUS,\n authUtils,\n cryptoUtils,\n errorUtils,\n reqUtils,\n validationUtils,\n} from '@verdaccio/core';\nimport { USER_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Config, Logger, RemoteUser } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../types/custom';\n\nconst debug = buildDebug('verdaccio:api:user');\n\nexport default function (route: Router, auth: Auth, config: Config, logger: Logger): void {\n route.get(\n USER_API_ENDPOINTS.get_user,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n debug('verifying user');\n\n if (\n !req.remote_user ||\n typeof req.remote_user.name !== 'string' ||\n req.remote_user.name === ''\n ) {\n debug('user not logged in');\n res.status(HTTP_STATUS.OK);\n return next({ ok: false });\n }\n\n const orgCouchdbUser = reqUtils.paramToString(req.params.org_couchdb_user);\n const userName = orgCouchdbUser?.split(':')[1] ?? '';\n const message = authUtils.getAuthenticatedMessage(req.remote_user.name);\n debug('user authenticated message %o', message);\n res.status(HTTP_STATUS.OK);\n next({\n // 'npm owner' requires user info\n // TODO: we don't have the email\n name: userName,\n email: '',\n ok: message,\n });\n }\n );\n\n /**\n * \n * body example\n * req.body = {\n _id: \"org.couchdb.user:jjjj\",\n name: \"jjjj\",\n password: \"jjjj\",\n type: \"user\",\n roles: [],\n date: \"2022-07-08T15:51:04.002Z\",\n }\n * \n * @export\n * @param {Router} route\n * @param {Auth} auth\n * @param {Config} config\n */\n route.put(\n USER_API_ENDPOINTS.add_user,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n const { name, password } = req.body;\n debug('login or adduser');\n const remoteName = req?.remote_user?.name;\n\n const userName = reqUtils.paramToString(req.params.org_couchdb_user);\n if (!validationUtils.validateUserName(userName, name)) {\n return next(errorUtils.getBadRequest(API_ERROR.USERNAME_MISMATCH));\n }\n\n if (typeof remoteName !== 'undefined' && typeof name === 'string' && remoteName === name) {\n debug('login: no remote user detected');\n auth.authenticate(\n name,\n password,\n async function callbackAuthenticate(err, user): Promise<void> {\n if (err) {\n logger.trace(\n { name, err },\n 'authenticating for user @{username} failed. Error: @{err.message}'\n );\n return next(\n errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.BAD_USERNAME_PASSWORD)\n );\n }\n\n const restoredRemoteUser: RemoteUser = createRemoteUser(name, user?.groups || []);\n const token = await getApiToken(auth, config, restoredRemoteUser, password);\n debug('login: new token');\n if (!token) {\n return next(errorUtils.getUnauthorized());\n }\n\n res.status(HTTP_STATUS.CREATED);\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n\n const message = authUtils.getAuthenticatedMessage(req.remote_user.name);\n debug('login: created user message %o', message);\n\n return next({\n ok: message,\n token,\n });\n }\n );\n } else {\n debug('adduser: %o', name);\n if (\n validationUtils.validatePassword(password, config?.server?.passwordValidationRegex) ===\n false\n ) {\n debug('adduser: invalid password');\n\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.PASSWORD_SHORT));\n }\n\n auth.add_user(name, password, async function (err, user): Promise<void> {\n if (err) {\n if (err.status >= HTTP_STATUS.BAD_REQUEST && err.status < HTTP_STATUS.INTERNAL_ERROR) {\n debug('adduser: error on create user');\n // With npm registering is the same as logging in,\n // and npm accepts only an 409 error.\n // So, changing status code here.\n return next(\n errorUtils.getCode(err.status, err.message) || errorUtils.getConflict(err.message)\n );\n }\n return next(err);\n }\n\n const token =\n name && password\n ? await getApiToken(auth, config, user as RemoteUser, password)\n : undefined;\n if (token) {\n debug('adduser: new token %o', cryptoUtils.mask(token as string, 4));\n }\n if (!token) {\n return next(errorUtils.getUnauthorized());\n }\n\n req.remote_user = user;\n res.status(HTTP_STATUS.CREATED);\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n debug('adduser: user has been created');\n return next({\n ok: `user '${req.body.name}' created`,\n token,\n });\n });\n }\n }\n );\n\n route.delete(\n USER_API_ENDPOINTS.user_token,\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n res.status(HTTP_STATUS.OK);\n next({\n ok: API_MESSAGE.LOGGED_OUT,\n });\n }\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { Router } from 'express';
2
- import { Auth } from '@verdaccio/auth';
3
- import { Storage } from '@verdaccio/store';
4
- import { Config, Logger } from '@verdaccio/types';
1
+ import type { Router } from 'express';
2
+ import type { Auth } from '@verdaccio/auth';
3
+ import type { Storage } from '@verdaccio/store';
4
+ import type { Config, Logger } from '@verdaccio/types';
5
5
  export default function (route: Router, auth: Auth, storage: Storage, config: Config, logger: Logger): void;
package/build/v1/login.js CHANGED
@@ -34,12 +34,12 @@ function login_default(route, auth, storage, config, logger) {
34
34
  });
35
35
  });
36
36
  route.get(_verdaccio_middleware.LOGIN_API_ENDPOINTS.login_done_session, (0, _verdaccio_middleware.rateLimit)(config?.userRateLimit), async function(req, res, next) {
37
- debug$1("polling login session %o", req.params.sessionId);
38
- if (!req.params.sessionId) {
37
+ const sessionId = _verdaccio_core.reqUtils.paramToString(req.params.sessionId);
38
+ debug$1("polling login session %o", sessionId);
39
+ if (!sessionId) {
39
40
  debug$1("sessionId is missing");
40
41
  return next(_verdaccio_core.errorUtils.getCode(_verdaccio_core.HTTP_STATUS.BAD_REQUEST, _verdaccio_core.API_ERROR.SESSION_ID_REQUIRED));
41
42
  }
42
- const sessionId = req.params.sessionId;
43
43
  if (sessionId.length !== 36) {
44
44
  debug$1("sessionId is invalid length: %o", sessionId.length);
45
45
  return next(_verdaccio_core.errorUtils.getCode(_verdaccio_core.HTTP_STATUS.BAD_REQUEST, _verdaccio_core.API_ERROR.SESSION_ID_INVALID));
@@ -71,12 +71,12 @@ function login_default(route, auth, storage, config, logger) {
71
71
  });
72
72
  route.post(_verdaccio_middleware.LOGIN_API_ENDPOINTS.login_cli_session, (0, _verdaccio_middleware.rateLimit)(config?.userRateLimit), async function(req, res, next) {
73
73
  const { username, password } = req.body;
74
- debug$1("authenticating login session %o for user %o", req.params.sessionId, username);
75
- if (!req.params.sessionId) {
74
+ const sessionId = _verdaccio_core.reqUtils.paramToString(req.params.sessionId);
75
+ debug$1("authenticating login session %o for user %o", sessionId, username);
76
+ if (!sessionId) {
76
77
  debug$1("sessionId is missing");
77
78
  return next(_verdaccio_core.errorUtils.getCode(_verdaccio_core.HTTP_STATUS.BAD_REQUEST, _verdaccio_core.API_ERROR.SESSION_ID_REQUIRED));
78
79
  }
79
- const sessionId = req.params.sessionId;
80
80
  if (sessionId.length !== 36) {
81
81
  debug$1("sessionId is invalid length: %o", sessionId.length);
82
82
  return next(_verdaccio_core.errorUtils.getCode(_verdaccio_core.HTTP_STATUS.BAD_REQUEST, _verdaccio_core.API_ERROR.SESSION_ID_INVALID));
@@ -1 +1 @@
1
- {"version":3,"file":"login.js","names":[],"sources":["../../src/v1/login.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport type { Response, Router } from 'express';\nimport { randomUUID } from 'node:crypto';\n\nimport type { Auth } from '@verdaccio/auth';\nimport { getApiToken } from '@verdaccio/auth';\nimport { createRemoteUser } from '@verdaccio/config';\nimport { API_ERROR, HEADERS, HTTP_STATUS, authUtils, errorUtils } from '@verdaccio/core';\nimport { LOGIN_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Storage } from '@verdaccio/store';\nimport type { Config, Logger } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\nconst debug = buildDebug('verdaccio:api:login');\n\nconst WEB_LOGIN_SESSION_ID = 'web-login-sessionId';\n\n// https://github.com/npm/npm-profile/blob/main/lib/index.js\nexport default function (\n route: Router,\n auth: Auth,\n storage: Storage,\n config: Config,\n logger: Logger\n): void {\n route.post(\n LOGIN_API_ENDPOINTS.login,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response): Promise<void> {\n // create new login session (without token)\n const sessionId = randomUUID();\n debug('creating login session %o', sessionId);\n\n await storage.saveToken({\n user: sessionId,\n token: '',\n key: WEB_LOGIN_SESSION_ID,\n readonly: false,\n created: new Date().getTime(),\n });\n\n res.status(HTTP_STATUS.OK);\n\n // Assuming Web UI is running on the same address as API\n const protocol = req.protocol;\n const host = req.hostname;\n const port = req.socket.localPort;\n\n const loginUrl = `${protocol}://${host}:${port}/-/web/login?next=${LOGIN_API_ENDPOINTS.login_cli}/${sessionId}`;\n const doneUrl = `${protocol}://${host}:${port}${LOGIN_API_ENDPOINTS.login_done}/${sessionId}`;\n debug('loginUrl: %o', loginUrl);\n debug('doneUrl: %o', doneUrl);\n\n res.json({\n loginUrl,\n doneUrl,\n });\n }\n );\n\n route.get(\n LOGIN_API_ENDPOINTS.login_done_session,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer): Promise<void> {\n debug('polling login session %o', req.params.sessionId);\n\n if (!req.params.sessionId) {\n debug('sessionId is missing');\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_REQUIRED));\n }\n const sessionId = req.params.sessionId;\n if (sessionId.length !== 36) {\n debug('sessionId is invalid length: %o', sessionId.length);\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));\n }\n\n try {\n const tokens = await storage.readTokens({ user: sessionId });\n debug('tokens found for sessionId %o: %o', sessionId, tokens.length);\n if (tokens && tokens.length === 1 && tokens[0].key === WEB_LOGIN_SESSION_ID) {\n if (tokens[0].token.length === 0) {\n debug('waiting for authentication');\n // Poll again after short delay\n // TODO: make this configurable (default 5 seconds)\n res.status(HTTP_STATUS.ACCEPTED);\n res.set(HEADERS.RETRY_AFTER, '5');\n res.json({});\n } else {\n // session token can only be used once\n debug('deleting session token');\n await storage.deleteToken(sessionId, tokens[0].key);\n\n // Check if token has expired\n // TODO: make this configurable (default 2 minutes)\n const tokenCreatedDate = new Date(tokens[0].created);\n const minutesAgo = new Date(Date.now() - 2 * 60 * 1000);\n if (tokenCreatedDate < minutesAgo) {\n debug('session token expired');\n return next(errorUtils.getUnauthorized(API_ERROR.SESSION_TOKEN_EXPIRED));\n }\n\n debug('session token is valid, login successful');\n\n res.status(HTTP_STATUS.OK);\n res.json({ token: tokens[0].token });\n }\n } else {\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));\n }\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token list has failed: @{error}');\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));\n }\n }\n );\n\n route.post(\n LOGIN_API_ENDPOINTS.login_cli_session,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer): Promise<void> {\n const { username, password } = req.body;\n debug('authenticating login session %o for user %o', req.params.sessionId, username);\n\n if (!req.params.sessionId) {\n debug('sessionId is missing');\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_REQUIRED));\n }\n const sessionId = req.params.sessionId;\n if (sessionId.length !== 36) {\n debug('sessionId is invalid length: %o', sessionId.length);\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));\n }\n\n auth.authenticate(\n username,\n password,\n async function callbackAuthenticate(err, user): Promise<void> {\n if (err) {\n logger.trace(\n { username, err },\n 'authenticating for user @{username} failed. Error: @{err.message}'\n );\n return next(errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, err.message));\n }\n\n const remoteUser = createRemoteUser(username, user?.groups || []);\n const token = await getApiToken(auth, config, remoteUser, password);\n\n if (!token) {\n return next(errorUtils.getUnauthorized());\n }\n\n // Replace login session with token (to be picked up by the \"done\" endpoint)\n debug('saving token for sessionId %o', sessionId);\n await storage.deleteToken(sessionId, WEB_LOGIN_SESSION_ID);\n await storage.saveToken({\n user: sessionId,\n token,\n key: WEB_LOGIN_SESSION_ID,\n readonly: false,\n created: new Date().getTime(),\n });\n\n const message = authUtils.getAuthenticatedMessage(remoteUser.name ?? '');\n\n res.status(HTTP_STATUS.CREATED);\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n res.json({ ok: message, token });\n }\n );\n }\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"login.js","names":[],"sources":["../../src/v1/login.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport type { Response, Router } from 'express';\nimport { randomUUID } from 'node:crypto';\n\nimport type { Auth } from '@verdaccio/auth';\nimport { getApiToken } from '@verdaccio/auth';\nimport { createRemoteUser } from '@verdaccio/config';\nimport { API_ERROR, HEADERS, HTTP_STATUS, authUtils, errorUtils, reqUtils } from '@verdaccio/core';\nimport { LOGIN_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Storage } from '@verdaccio/store';\nimport type { Config, Logger } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\nconst debug = buildDebug('verdaccio:api:login');\n\nconst WEB_LOGIN_SESSION_ID = 'web-login-sessionId';\n\n// https://github.com/npm/npm-profile/blob/main/lib/index.js\nexport default function (\n route: Router,\n auth: Auth,\n storage: Storage,\n config: Config,\n logger: Logger\n): void {\n route.post(\n LOGIN_API_ENDPOINTS.login,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response): Promise<void> {\n // create new login session (without token)\n const sessionId = randomUUID();\n debug('creating login session %o', sessionId);\n\n await storage.saveToken({\n user: sessionId,\n token: '',\n key: WEB_LOGIN_SESSION_ID,\n readonly: false,\n created: new Date().getTime(),\n });\n\n res.status(HTTP_STATUS.OK);\n\n // Assuming Web UI is running on the same address as API\n const protocol = req.protocol;\n const host = req.hostname;\n const port = req.socket.localPort;\n\n const loginUrl = `${protocol}://${host}:${port}/-/web/login?next=${LOGIN_API_ENDPOINTS.login_cli}/${sessionId}`;\n const doneUrl = `${protocol}://${host}:${port}${LOGIN_API_ENDPOINTS.login_done}/${sessionId}`;\n debug('loginUrl: %o', loginUrl);\n debug('doneUrl: %o', doneUrl);\n\n res.json({\n loginUrl,\n doneUrl,\n });\n }\n );\n\n route.get(\n LOGIN_API_ENDPOINTS.login_done_session,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer): Promise<void> {\n const sessionId = reqUtils.paramToString(req.params.sessionId);\n debug('polling login session %o', sessionId);\n\n if (!sessionId) {\n debug('sessionId is missing');\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_REQUIRED));\n }\n if (sessionId.length !== 36) {\n debug('sessionId is invalid length: %o', sessionId.length);\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));\n }\n\n try {\n const tokens = await storage.readTokens({ user: sessionId });\n debug('tokens found for sessionId %o: %o', sessionId, tokens.length);\n if (tokens && tokens.length === 1 && tokens[0].key === WEB_LOGIN_SESSION_ID) {\n if (tokens[0].token.length === 0) {\n debug('waiting for authentication');\n // Poll again after short delay\n // TODO: make this configurable (default 5 seconds)\n res.status(HTTP_STATUS.ACCEPTED);\n res.set(HEADERS.RETRY_AFTER, '5');\n res.json({});\n } else {\n // session token can only be used once\n debug('deleting session token');\n await storage.deleteToken(sessionId, tokens[0].key);\n\n // Check if token has expired\n // TODO: make this configurable (default 2 minutes)\n const tokenCreatedDate = new Date(tokens[0].created);\n const minutesAgo = new Date(Date.now() - 2 * 60 * 1000);\n if (tokenCreatedDate < minutesAgo) {\n debug('session token expired');\n return next(errorUtils.getUnauthorized(API_ERROR.SESSION_TOKEN_EXPIRED));\n }\n\n debug('session token is valid, login successful');\n\n res.status(HTTP_STATUS.OK);\n res.json({ token: tokens[0].token });\n }\n } else {\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));\n }\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token list has failed: @{error}');\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));\n }\n }\n );\n\n route.post(\n LOGIN_API_ENDPOINTS.login_cli_session,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer): Promise<void> {\n const { username, password } = req.body;\n const sessionId = reqUtils.paramToString(req.params.sessionId);\n debug('authenticating login session %o for user %o', sessionId, username);\n\n if (!sessionId) {\n debug('sessionId is missing');\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_REQUIRED));\n }\n if (sessionId.length !== 36) {\n debug('sessionId is invalid length: %o', sessionId.length);\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));\n }\n\n auth.authenticate(\n username,\n password,\n async function callbackAuthenticate(err, user): Promise<void> {\n if (err) {\n logger.trace(\n { username, err },\n 'authenticating for user @{username} failed. Error: @{err.message}'\n );\n return next(errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, err.message));\n }\n\n const remoteUser = createRemoteUser(username, user?.groups || []);\n const token = await getApiToken(auth, config, remoteUser, password);\n\n if (!token) {\n return next(errorUtils.getUnauthorized());\n }\n\n // Replace login session with token (to be picked up by the \"done\" endpoint)\n debug('saving token for sessionId %o', sessionId);\n await storage.deleteToken(sessionId, WEB_LOGIN_SESSION_ID);\n await storage.saveToken({\n user: sessionId,\n token,\n key: WEB_LOGIN_SESSION_ID,\n readonly: false,\n created: new Date().getTime(),\n });\n\n const message = authUtils.getAuthenticatedMessage(remoteUser.name ?? '');\n\n res.status(HTTP_STATUS.CREATED);\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n res.json({ ok: message, token });\n }\n );\n }\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { LOGIN_API_ENDPOINTS, rateLimit } from "@verdaccio/middleware";
2
- import { API_ERROR, HEADERS, HTTP_STATUS, authUtils, errorUtils } from "@verdaccio/core";
2
+ import { API_ERROR, HEADERS, HTTP_STATUS, authUtils, errorUtils, reqUtils } from "@verdaccio/core";
3
3
  import buildDebug from "debug";
4
4
  import { getApiToken } from "@verdaccio/auth";
5
5
  import { createRemoteUser } from "@verdaccio/config";
@@ -32,12 +32,12 @@ function login_default(route, auth, storage, config, logger) {
32
32
  });
33
33
  });
34
34
  route.get(LOGIN_API_ENDPOINTS.login_done_session, rateLimit(config?.userRateLimit), async function(req, res, next) {
35
- debug("polling login session %o", req.params.sessionId);
36
- if (!req.params.sessionId) {
35
+ const sessionId = reqUtils.paramToString(req.params.sessionId);
36
+ debug("polling login session %o", sessionId);
37
+ if (!sessionId) {
37
38
  debug("sessionId is missing");
38
39
  return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_REQUIRED));
39
40
  }
40
- const sessionId = req.params.sessionId;
41
41
  if (sessionId.length !== 36) {
42
42
  debug("sessionId is invalid length: %o", sessionId.length);
43
43
  return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));
@@ -69,12 +69,12 @@ function login_default(route, auth, storage, config, logger) {
69
69
  });
70
70
  route.post(LOGIN_API_ENDPOINTS.login_cli_session, rateLimit(config?.userRateLimit), async function(req, res, next) {
71
71
  const { username, password } = req.body;
72
- debug("authenticating login session %o for user %o", req.params.sessionId, username);
73
- if (!req.params.sessionId) {
72
+ const sessionId = reqUtils.paramToString(req.params.sessionId);
73
+ debug("authenticating login session %o for user %o", sessionId, username);
74
+ if (!sessionId) {
74
75
  debug("sessionId is missing");
75
76
  return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_REQUIRED));
76
77
  }
77
- const sessionId = req.params.sessionId;
78
78
  if (sessionId.length !== 36) {
79
79
  debug("sessionId is invalid length: %o", sessionId.length);
80
80
  return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));
@@ -1 +1 @@
1
- {"version":3,"file":"login.mjs","names":[],"sources":["../../src/v1/login.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport type { Response, Router } from 'express';\nimport { randomUUID } from 'node:crypto';\n\nimport type { Auth } from '@verdaccio/auth';\nimport { getApiToken } from '@verdaccio/auth';\nimport { createRemoteUser } from '@verdaccio/config';\nimport { API_ERROR, HEADERS, HTTP_STATUS, authUtils, errorUtils } from '@verdaccio/core';\nimport { LOGIN_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Storage } from '@verdaccio/store';\nimport type { Config, Logger } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\nconst debug = buildDebug('verdaccio:api:login');\n\nconst WEB_LOGIN_SESSION_ID = 'web-login-sessionId';\n\n// https://github.com/npm/npm-profile/blob/main/lib/index.js\nexport default function (\n route: Router,\n auth: Auth,\n storage: Storage,\n config: Config,\n logger: Logger\n): void {\n route.post(\n LOGIN_API_ENDPOINTS.login,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response): Promise<void> {\n // create new login session (without token)\n const sessionId = randomUUID();\n debug('creating login session %o', sessionId);\n\n await storage.saveToken({\n user: sessionId,\n token: '',\n key: WEB_LOGIN_SESSION_ID,\n readonly: false,\n created: new Date().getTime(),\n });\n\n res.status(HTTP_STATUS.OK);\n\n // Assuming Web UI is running on the same address as API\n const protocol = req.protocol;\n const host = req.hostname;\n const port = req.socket.localPort;\n\n const loginUrl = `${protocol}://${host}:${port}/-/web/login?next=${LOGIN_API_ENDPOINTS.login_cli}/${sessionId}`;\n const doneUrl = `${protocol}://${host}:${port}${LOGIN_API_ENDPOINTS.login_done}/${sessionId}`;\n debug('loginUrl: %o', loginUrl);\n debug('doneUrl: %o', doneUrl);\n\n res.json({\n loginUrl,\n doneUrl,\n });\n }\n );\n\n route.get(\n LOGIN_API_ENDPOINTS.login_done_session,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer): Promise<void> {\n debug('polling login session %o', req.params.sessionId);\n\n if (!req.params.sessionId) {\n debug('sessionId is missing');\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_REQUIRED));\n }\n const sessionId = req.params.sessionId;\n if (sessionId.length !== 36) {\n debug('sessionId is invalid length: %o', sessionId.length);\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));\n }\n\n try {\n const tokens = await storage.readTokens({ user: sessionId });\n debug('tokens found for sessionId %o: %o', sessionId, tokens.length);\n if (tokens && tokens.length === 1 && tokens[0].key === WEB_LOGIN_SESSION_ID) {\n if (tokens[0].token.length === 0) {\n debug('waiting for authentication');\n // Poll again after short delay\n // TODO: make this configurable (default 5 seconds)\n res.status(HTTP_STATUS.ACCEPTED);\n res.set(HEADERS.RETRY_AFTER, '5');\n res.json({});\n } else {\n // session token can only be used once\n debug('deleting session token');\n await storage.deleteToken(sessionId, tokens[0].key);\n\n // Check if token has expired\n // TODO: make this configurable (default 2 minutes)\n const tokenCreatedDate = new Date(tokens[0].created);\n const minutesAgo = new Date(Date.now() - 2 * 60 * 1000);\n if (tokenCreatedDate < minutesAgo) {\n debug('session token expired');\n return next(errorUtils.getUnauthorized(API_ERROR.SESSION_TOKEN_EXPIRED));\n }\n\n debug('session token is valid, login successful');\n\n res.status(HTTP_STATUS.OK);\n res.json({ token: tokens[0].token });\n }\n } else {\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));\n }\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token list has failed: @{error}');\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));\n }\n }\n );\n\n route.post(\n LOGIN_API_ENDPOINTS.login_cli_session,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer): Promise<void> {\n const { username, password } = req.body;\n debug('authenticating login session %o for user %o', req.params.sessionId, username);\n\n if (!req.params.sessionId) {\n debug('sessionId is missing');\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_REQUIRED));\n }\n const sessionId = req.params.sessionId;\n if (sessionId.length !== 36) {\n debug('sessionId is invalid length: %o', sessionId.length);\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));\n }\n\n auth.authenticate(\n username,\n password,\n async function callbackAuthenticate(err, user): Promise<void> {\n if (err) {\n logger.trace(\n { username, err },\n 'authenticating for user @{username} failed. Error: @{err.message}'\n );\n return next(errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, err.message));\n }\n\n const remoteUser = createRemoteUser(username, user?.groups || []);\n const token = await getApiToken(auth, config, remoteUser, password);\n\n if (!token) {\n return next(errorUtils.getUnauthorized());\n }\n\n // Replace login session with token (to be picked up by the \"done\" endpoint)\n debug('saving token for sessionId %o', sessionId);\n await storage.deleteToken(sessionId, WEB_LOGIN_SESSION_ID);\n await storage.saveToken({\n user: sessionId,\n token,\n key: WEB_LOGIN_SESSION_ID,\n readonly: false,\n created: new Date().getTime(),\n });\n\n const message = authUtils.getAuthenticatedMessage(remoteUser.name ?? '');\n\n res.status(HTTP_STATUS.CREATED);\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n res.json({ ok: message, token });\n }\n );\n }\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"login.mjs","names":[],"sources":["../../src/v1/login.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport type { Response, Router } from 'express';\nimport { randomUUID } from 'node:crypto';\n\nimport type { Auth } from '@verdaccio/auth';\nimport { getApiToken } from '@verdaccio/auth';\nimport { createRemoteUser } from '@verdaccio/config';\nimport { API_ERROR, HEADERS, HTTP_STATUS, authUtils, errorUtils, reqUtils } from '@verdaccio/core';\nimport { LOGIN_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Storage } from '@verdaccio/store';\nimport type { Config, Logger } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\nconst debug = buildDebug('verdaccio:api:login');\n\nconst WEB_LOGIN_SESSION_ID = 'web-login-sessionId';\n\n// https://github.com/npm/npm-profile/blob/main/lib/index.js\nexport default function (\n route: Router,\n auth: Auth,\n storage: Storage,\n config: Config,\n logger: Logger\n): void {\n route.post(\n LOGIN_API_ENDPOINTS.login,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response): Promise<void> {\n // create new login session (without token)\n const sessionId = randomUUID();\n debug('creating login session %o', sessionId);\n\n await storage.saveToken({\n user: sessionId,\n token: '',\n key: WEB_LOGIN_SESSION_ID,\n readonly: false,\n created: new Date().getTime(),\n });\n\n res.status(HTTP_STATUS.OK);\n\n // Assuming Web UI is running on the same address as API\n const protocol = req.protocol;\n const host = req.hostname;\n const port = req.socket.localPort;\n\n const loginUrl = `${protocol}://${host}:${port}/-/web/login?next=${LOGIN_API_ENDPOINTS.login_cli}/${sessionId}`;\n const doneUrl = `${protocol}://${host}:${port}${LOGIN_API_ENDPOINTS.login_done}/${sessionId}`;\n debug('loginUrl: %o', loginUrl);\n debug('doneUrl: %o', doneUrl);\n\n res.json({\n loginUrl,\n doneUrl,\n });\n }\n );\n\n route.get(\n LOGIN_API_ENDPOINTS.login_done_session,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer): Promise<void> {\n const sessionId = reqUtils.paramToString(req.params.sessionId);\n debug('polling login session %o', sessionId);\n\n if (!sessionId) {\n debug('sessionId is missing');\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_REQUIRED));\n }\n if (sessionId.length !== 36) {\n debug('sessionId is invalid length: %o', sessionId.length);\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));\n }\n\n try {\n const tokens = await storage.readTokens({ user: sessionId });\n debug('tokens found for sessionId %o: %o', sessionId, tokens.length);\n if (tokens && tokens.length === 1 && tokens[0].key === WEB_LOGIN_SESSION_ID) {\n if (tokens[0].token.length === 0) {\n debug('waiting for authentication');\n // Poll again after short delay\n // TODO: make this configurable (default 5 seconds)\n res.status(HTTP_STATUS.ACCEPTED);\n res.set(HEADERS.RETRY_AFTER, '5');\n res.json({});\n } else {\n // session token can only be used once\n debug('deleting session token');\n await storage.deleteToken(sessionId, tokens[0].key);\n\n // Check if token has expired\n // TODO: make this configurable (default 2 minutes)\n const tokenCreatedDate = new Date(tokens[0].created);\n const minutesAgo = new Date(Date.now() - 2 * 60 * 1000);\n if (tokenCreatedDate < minutesAgo) {\n debug('session token expired');\n return next(errorUtils.getUnauthorized(API_ERROR.SESSION_TOKEN_EXPIRED));\n }\n\n debug('session token is valid, login successful');\n\n res.status(HTTP_STATUS.OK);\n res.json({ token: tokens[0].token });\n }\n } else {\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));\n }\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token list has failed: @{error}');\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));\n }\n }\n );\n\n route.post(\n LOGIN_API_ENDPOINTS.login_cli_session,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer): Promise<void> {\n const { username, password } = req.body;\n const sessionId = reqUtils.paramToString(req.params.sessionId);\n debug('authenticating login session %o for user %o', sessionId, username);\n\n if (!sessionId) {\n debug('sessionId is missing');\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_REQUIRED));\n }\n if (sessionId.length !== 36) {\n debug('sessionId is invalid length: %o', sessionId.length);\n return next(errorUtils.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.SESSION_ID_INVALID));\n }\n\n auth.authenticate(\n username,\n password,\n async function callbackAuthenticate(err, user): Promise<void> {\n if (err) {\n logger.trace(\n { username, err },\n 'authenticating for user @{username} failed. Error: @{err.message}'\n );\n return next(errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, err.message));\n }\n\n const remoteUser = createRemoteUser(username, user?.groups || []);\n const token = await getApiToken(auth, config, remoteUser, password);\n\n if (!token) {\n return next(errorUtils.getUnauthorized());\n }\n\n // Replace login session with token (to be picked up by the \"done\" endpoint)\n debug('saving token for sessionId %o', sessionId);\n await storage.deleteToken(sessionId, WEB_LOGIN_SESSION_ID);\n await storage.saveToken({\n user: sessionId,\n token,\n key: WEB_LOGIN_SESSION_ID,\n readonly: false,\n created: new Date().getTime(),\n });\n\n const message = authUtils.getAuthenticatedMessage(remoteUser.name ?? '');\n\n res.status(HTTP_STATUS.CREATED);\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n res.json({ ok: message, token });\n }\n );\n }\n );\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { Router } from 'express';
2
- import { Auth } from '@verdaccio/auth';
3
- import { Config } from '@verdaccio/types';
1
+ import type { Router } from 'express';
2
+ import type { Auth } from '@verdaccio/auth';
3
+ import type { Config } from '@verdaccio/types';
4
4
  export interface Profile {
5
5
  tfa: boolean;
6
6
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"profile.js","names":[],"sources":["../../src/v1/profile.ts"],"sourcesContent":["import type { Response, Router } from 'express';\nimport { isEmpty, isNil } from 'lodash-es';\n\nimport type { Auth } from '@verdaccio/auth';\nimport {\n API_ERROR,\n APP_ERROR,\n HTTP_STATUS,\n SUPPORT_ERRORS,\n errorUtils,\n validationUtils,\n} from '@verdaccio/core';\nimport { PROFILE_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Config } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\nexport interface Profile {\n tfa: boolean;\n name: string;\n email: string;\n email_verified: boolean;\n created: string;\n updated: string;\n cidr_whitelist: string[] | null;\n fullname: string;\n}\n\nexport default function (route: Router, auth: Auth, config: Config): void {\n function buildProfile(name: string): Profile {\n return {\n tfa: false,\n name,\n email: '',\n email_verified: false,\n created: '',\n updated: '',\n cidr_whitelist: null,\n fullname: '',\n };\n }\n\n route.get(\n PROFILE_API_ENDPOINTS.get_profile,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n if (isNil(req.remote_user.name) === false) {\n return next(buildProfile(req.remote_user.name));\n }\n\n res.status(HTTP_STATUS.UNAUTHORIZED);\n return next({\n message: API_ERROR.MUST_BE_LOGGED,\n });\n }\n );\n\n route.post(\n PROFILE_API_ENDPOINTS.get_profile,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n if (isNil(req.remote_user.name)) {\n res.status(HTTP_STATUS.UNAUTHORIZED);\n return next({\n message: API_ERROR.MUST_BE_LOGGED,\n });\n }\n\n const { password, tfa } = req.body;\n const { name } = req.remote_user;\n\n if (isNil(password) === false) {\n if (\n validationUtils.validatePassword(\n password.new,\n config?.server?.passwordValidationRegex\n ) === false\n ) {\n /* eslint new-cap:off */\n return next(errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.PASSWORD_SHORT));\n }\n\n if (isEmpty(password.old)) {\n return next(errorUtils.getBadRequest('old password is required'));\n }\n\n auth.changePassword(\n name,\n password.old,\n password.new,\n (err, isUpdated): $NextFunctionVer => {\n if (err !== null) {\n return next(errorUtils.getForbidden(err.message));\n }\n\n if (isUpdated) {\n return next(buildProfile(req.remote_user.name));\n }\n return next(errorUtils.getInternalError(API_ERROR.INTERNAL_SERVER_ERROR));\n }\n );\n } else if (isNil(tfa) === false) {\n return next(\n errorUtils.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.TFA_DISABLED)\n );\n } else {\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, APP_ERROR.PROFILE_ERROR));\n }\n }\n );\n}\n"],"mappings":";;;;AA4BA,SAAA,gBAAyB,OAAe,MAAY,QAAsB;CACxE,SAAS,aAAa,MAAuB;EAC3C,OAAO;GACL,KAAK;GACL;GACA,OAAO;GACP,gBAAgB;GAChB,SAAS;GACT,SAAS;GACT,gBAAgB;GAChB,UAAU;EACZ;CACF;CAEA,MAAM,IACJ,sBAAA,sBAAsB,cAAA,GAAA,sBAAA,WACZ,QAAQ,aAAa,GAC/B,SAAU,KAAqB,KAAe,MAA8B;EAC1E,KAAA,GAAA,UAAA,OAAU,IAAI,YAAY,IAAI,MAAM,OAClC,OAAO,KAAK,aAAa,IAAI,YAAY,IAAI,CAAC;EAGhD,IAAI,OAAO,gBAAA,YAAY,YAAY;EACnC,OAAO,KAAK,EACV,SAAS,gBAAA,UAAU,eACrB,CAAC;CACH,CACF;CAEA,MAAM,KACJ,sBAAA,sBAAsB,cAAA,GAAA,sBAAA,WACZ,QAAQ,aAAa,GAC/B,SAAU,KAAqB,KAAe,MAA8B;EAC1E,KAAA,GAAA,UAAA,OAAU,IAAI,YAAY,IAAI,GAAG;GAC/B,IAAI,OAAO,gBAAA,YAAY,YAAY;GACnC,OAAO,KAAK,EACV,SAAS,gBAAA,UAAU,eACrB,CAAC;EACH;EAEA,MAAM,EAAE,UAAU,QAAQ,IAAI;EAC9B,MAAM,EAAE,SAAS,IAAI;EAErB,KAAA,GAAA,UAAA,OAAU,QAAQ,MAAM,OAAO;GAC7B,IACE,gBAAA,gBAAgB,iBACd,SAAS,KACT,QAAQ,QAAQ,uBAClB,MAAM,OAGN,OAAO,KAAK,gBAAA,WAAW,QAAQ,gBAAA,YAAY,cAAc,gBAAA,UAAU,cAAc,CAAC;GAGpF,KAAA,GAAA,UAAA,SAAY,SAAS,GAAG,GACtB,OAAO,KAAK,gBAAA,WAAW,cAAc,0BAA0B,CAAC;GAGlE,KAAK,eACH,MACA,SAAS,KACT,SAAS,MACR,KAAK,cAAgC;IACpC,IAAI,QAAQ,MACV,OAAO,KAAK,gBAAA,WAAW,aAAa,IAAI,OAAO,CAAC;IAGlD,IAAI,WACF,OAAO,KAAK,aAAa,IAAI,YAAY,IAAI,CAAC;IAEhD,OAAO,KAAK,gBAAA,WAAW,iBAAiB,gBAAA,UAAU,qBAAqB,CAAC;GAC1E,CACF;EACF,OAAO,KAAA,GAAA,UAAA,OAAU,GAAG,MAAM,OACxB,OAAO,KACL,gBAAA,WAAW,QAAQ,gBAAA,YAAY,qBAAqB,gBAAA,eAAe,YAAY,CACjF;OAEA,OAAO,KAAK,gBAAA,WAAW,QAAQ,gBAAA,YAAY,gBAAgB,gBAAA,UAAU,aAAa,CAAC;CAEvF,CACF;AACF"}
1
+ {"version":3,"file":"profile.js","names":[],"sources":["../../src/v1/profile.ts"],"sourcesContent":["import type { Response, Router } from 'express';\nimport { isEmpty, isNil } from 'lodash-es';\n\nimport type { Auth } from '@verdaccio/auth';\nimport {\n API_ERROR,\n APP_ERROR,\n HTTP_STATUS,\n SUPPORT_ERRORS,\n errorUtils,\n validationUtils,\n} from '@verdaccio/core';\nimport { PROFILE_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Config } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\nexport interface Profile {\n tfa: boolean;\n name: string;\n email: string;\n email_verified: boolean;\n created: string;\n updated: string;\n cidr_whitelist: string[] | null;\n fullname: string;\n}\n\nexport default function (route: Router, auth: Auth, config: Config): void {\n function buildProfile(name: string): Profile {\n return {\n tfa: false,\n name,\n email: '',\n email_verified: false,\n created: '',\n updated: '',\n cidr_whitelist: null,\n fullname: '',\n };\n }\n\n route.get(\n PROFILE_API_ENDPOINTS.get_profile,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n if (isNil(req.remote_user.name) === false) {\n return next(buildProfile(req.remote_user.name));\n }\n\n res.status(HTTP_STATUS.UNAUTHORIZED);\n return next({\n message: API_ERROR.MUST_BE_LOGGED,\n });\n }\n );\n\n route.post(\n PROFILE_API_ENDPOINTS.get_profile,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n if (isNil(req.remote_user.name)) {\n res.status(HTTP_STATUS.UNAUTHORIZED);\n return next({\n message: API_ERROR.MUST_BE_LOGGED,\n });\n }\n\n const { password, tfa } = req.body;\n const { name } = req.remote_user;\n\n if (isNil(password) === false) {\n if (\n validationUtils.validatePassword(\n password.new,\n config?.server?.passwordValidationRegex\n ) === false\n ) {\n /* eslint new-cap:off */\n return next(errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.PASSWORD_SHORT));\n }\n\n if (isEmpty(password.old)) {\n return next(errorUtils.getBadRequest('old password is required'));\n }\n\n auth.changePassword(\n name,\n password.old,\n password.new,\n (err, isUpdated): $NextFunctionVer => {\n if (err !== null) {\n return next(errorUtils.getForbidden(err.message));\n }\n\n if (isUpdated) {\n return next(buildProfile(req.remote_user.name));\n }\n return next(errorUtils.getInternalError(API_ERROR.INTERNAL_SERVER_ERROR));\n }\n );\n } else if (isNil(tfa) === false) {\n return next(\n errorUtils.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.TFA_DISABLED)\n );\n } else {\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, APP_ERROR.PROFILE_ERROR));\n }\n }\n );\n}\n"],"mappings":";;;;AA4BA,SAAA,gBAAyB,OAAe,MAAY,QAAsB;CACxE,SAAS,aAAa,MAAuB;EAC3C,OAAO;GACL,KAAK;GACL;GACA,OAAO;GACP,gBAAgB;GAChB,SAAS;GACT,SAAS;GACT,gBAAgB;GAChB,UAAU;EACZ;CACF;CAEA,MAAM,IACJ,sBAAA,sBAAsB,cAAA,GAAA,sBAAA,UAAA,CACZ,QAAQ,aAAa,GAC/B,SAAU,KAAqB,KAAe,MAA8B;EAC1E,KAAA,GAAA,UAAA,MAAA,CAAU,IAAI,YAAY,IAAI,MAAM,OAClC,OAAO,KAAK,aAAa,IAAI,YAAY,IAAI,CAAC;EAGhD,IAAI,OAAO,gBAAA,YAAY,YAAY;EACnC,OAAO,KAAK,EACV,SAAS,gBAAA,UAAU,eACrB,CAAC;CACH,CACF;CAEA,MAAM,KACJ,sBAAA,sBAAsB,cAAA,GAAA,sBAAA,UAAA,CACZ,QAAQ,aAAa,GAC/B,SAAU,KAAqB,KAAe,MAA8B;EAC1E,KAAA,GAAA,UAAA,MAAA,CAAU,IAAI,YAAY,IAAI,GAAG;GAC/B,IAAI,OAAO,gBAAA,YAAY,YAAY;GACnC,OAAO,KAAK,EACV,SAAS,gBAAA,UAAU,eACrB,CAAC;EACH;EAEA,MAAM,EAAE,UAAU,QAAQ,IAAI;EAC9B,MAAM,EAAE,SAAS,IAAI;EAErB,KAAA,GAAA,UAAA,MAAA,CAAU,QAAQ,MAAM,OAAO;GAC7B,IACE,gBAAA,gBAAgB,iBACd,SAAS,KACT,QAAQ,QAAQ,uBAClB,MAAM,OAGN,OAAO,KAAK,gBAAA,WAAW,QAAQ,gBAAA,YAAY,cAAc,gBAAA,UAAU,cAAc,CAAC;GAGpF,KAAA,GAAA,UAAA,QAAA,CAAY,SAAS,GAAG,GACtB,OAAO,KAAK,gBAAA,WAAW,cAAc,0BAA0B,CAAC;GAGlE,KAAK,eACH,MACA,SAAS,KACT,SAAS,MACR,KAAK,cAAgC;IACpC,IAAI,QAAQ,MACV,OAAO,KAAK,gBAAA,WAAW,aAAa,IAAI,OAAO,CAAC;IAGlD,IAAI,WACF,OAAO,KAAK,aAAa,IAAI,YAAY,IAAI,CAAC;IAEhD,OAAO,KAAK,gBAAA,WAAW,iBAAiB,gBAAA,UAAU,qBAAqB,CAAC;GAC1E,CACF;EACF,OAAO,KAAA,GAAA,UAAA,MAAA,CAAU,GAAG,MAAM,OACxB,OAAO,KACL,gBAAA,WAAW,QAAQ,gBAAA,YAAY,qBAAqB,gBAAA,eAAe,YAAY,CACjF;OAEA,OAAO,KAAK,gBAAA,WAAW,QAAQ,gBAAA,YAAY,gBAAgB,gBAAA,UAAU,aAAa,CAAC;CAEvF,CACF;AACF"}
@@ -1,10 +1,10 @@
1
- import { Auth } from '@verdaccio/auth';
2
- import { Storage } from '@verdaccio/store';
3
- import { Logger } from '@verdaccio/types';
1
+ import type { Auth } from '@verdaccio/auth';
2
+ import type { Storage } from '@verdaccio/store';
3
+ import type { Config, Logger } from '@verdaccio/types';
4
4
  /**
5
5
  * Endpoint for npm search v1
6
6
  * Empty value
7
7
  * - {"objects":[],"total":0,"time":"Sun Jul 25 2021 14:09:11 GMT+0000 (Coordinated Universal Time)"}
8
8
  * req: 'GET /-/v1/search?text=react&size=20&frpom=0&quality=0.65&popularity=0.98&maintenance=0.5'
9
9
  */
10
- export default function (route: any, auth: Auth, storage: Storage, logger: Logger): void;
10
+ export default function (route: any, auth: Auth, storage: Storage, config: Config, logger: Logger): void;
@@ -5,13 +5,22 @@ let debug = require("debug");
5
5
  debug = require_runtime.__toESM(debug);
6
6
  //#region src/v1/search.ts
7
7
  var debug$1 = (0, debug.default)("verdaccio:api:search");
8
+ var DEFAULT_SIZE = 20;
9
+ var MAX_SIZE = 250;
10
+ var MAX_FROM = 1e4;
11
+ var CHECK_ACCESS_BATCH_SIZE = 50;
12
+ function parseQueryInt(value, defaultValue, max) {
13
+ const parsed = Number.parseInt(String(value), 10);
14
+ if (Number.isNaN(parsed) || parsed < 0) return defaultValue;
15
+ return Math.min(parsed, max);
16
+ }
8
17
  /**
9
18
  * Endpoint for npm search v1
10
19
  * Empty value
11
20
  * - {"objects":[],"total":0,"time":"Sun Jul 25 2021 14:09:11 GMT+0000 (Coordinated Universal Time)"}
12
21
  * req: 'GET /-/v1/search?text=react&size=20&frpom=0&quality=0.65&popularity=0.98&maintenance=0.5'
13
22
  */
14
- function search_default(route, auth, storage, logger) {
23
+ function search_default(route, auth, storage, config, logger) {
15
24
  function checkAccess(pkg, auth, remoteUser) {
16
25
  return new Promise((resolve, reject) => {
17
26
  auth.allow_access({ packageName: pkg?.package?.name }, remoteUser, function(err, allowed) {
@@ -21,10 +30,19 @@ function search_default(route, auth, storage, logger) {
21
30
  });
22
31
  });
23
32
  }
24
- route.get(_verdaccio_middleware.SEARCH_API_ENDPOINTS.search, async (req, res, next) => {
33
+ route.get(_verdaccio_middleware.SEARCH_API_ENDPOINTS.search, (0, _verdaccio_middleware.rateLimit)(config?.userRateLimit), async (req, res, next) => {
25
34
  const { query, url } = req;
26
- let [size, from] = ["size", "from"].map((k) => query[k]);
27
- let data;
35
+ const size = parseQueryInt(query.size, DEFAULT_SIZE, MAX_SIZE);
36
+ const from = parseQueryInt(query.from, 0, MAX_FROM);
37
+ const safeQuery = {
38
+ ...query,
39
+ size,
40
+ from
41
+ };
42
+ const searchParams = new URLSearchParams(query);
43
+ searchParams.set("size", String(size));
44
+ searchParams.set("from", String(from));
45
+ const safeUrl = `${url.split("?")[0]}?${searchParams.toString()}`;
28
46
  const abort = new AbortController();
29
47
  const onClientClose = () => {
30
48
  debug$1("search web aborted");
@@ -32,18 +50,20 @@ function search_default(route, auth, storage, logger) {
32
50
  };
33
51
  req.socket.on("close", onClientClose);
34
52
  try {
35
- size = parseInt(Number.isFinite(size) ? size : "20", 10);
36
- from = parseInt(Number.isFinite(from) ? from : "0", 10);
37
53
  debug$1("storage search initiated");
38
- data = await storage.search({
39
- query,
40
- url,
54
+ const data = await storage.search({
55
+ query: safeQuery,
56
+ url: safeUrl,
41
57
  abort
42
58
  });
43
- debug$1("storage items tota: %o", data.length);
44
- const final = (await Promise.all(data.map((pkgItem) => {
45
- return checkAccess(pkgItem, auth, req.remote_user);
46
- }))).filter((i) => i !== null).slice(from, from + size);
59
+ debug$1("storage items total: %o", data.length);
60
+ const requested = from + size;
61
+ const allowed = [];
62
+ for (let i = 0; i < data.length && allowed.length < requested && !abort.signal.aborted; i += CHECK_ACCESS_BATCH_SIZE) {
63
+ const batch = await Promise.all(data.slice(i, i + CHECK_ACCESS_BATCH_SIZE).map((pkgItem) => checkAccess(pkgItem, auth, req.remote_user)));
64
+ for (const item of batch) if (item !== null) allowed.push(item);
65
+ }
66
+ const final = allowed.slice(from, requested);
47
67
  logger.debug(`search results ${final?.length}`);
48
68
  const response = {
49
69
  objects: final,
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","names":[],"sources":["../../src/v1/search.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport type { Auth } from '@verdaccio/auth';\nimport type { searchUtils } from '@verdaccio/core';\nimport { HTTP_STATUS } from '@verdaccio/core';\nimport { SEARCH_API_ENDPOINTS } from '@verdaccio/middleware';\nimport type { Storage } from '@verdaccio/store';\nimport type { Logger, Manifest } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:api:search');\n\n/**\n * Endpoint for npm search v1\n * Empty value\n * - {\"objects\":[],\"total\":0,\"time\":\"Sun Jul 25 2021 14:09:11 GMT+0000 (Coordinated Universal Time)\"}\n * req: 'GET /-/v1/search?text=react&size=20&frpom=0&quality=0.65&popularity=0.98&maintenance=0.5'\n */\nexport default function (route, auth: Auth, storage: Storage, logger: Logger): void {\n function checkAccess(pkg: any, auth: any, remoteUser): Promise<Manifest | null> {\n return new Promise((resolve, reject) => {\n auth.allow_access({ packageName: pkg?.package?.name }, remoteUser, function (err, allowed) {\n if (err) {\n if (err.status && String(err.status).match(/^4\\d\\d$/)) {\n // auth plugin returns 4xx user error,\n // that's equivalent of !allowed basically\n return resolve(null);\n } else {\n reject(err);\n }\n } else {\n return resolve(allowed ? pkg : null);\n }\n });\n });\n }\n\n route.get(SEARCH_API_ENDPOINTS.search, async (req, res, next) => {\n const { query, url } = req;\n let [size, from] = ['size', 'from'].map((k) => query[k]);\n let data;\n const abort = new AbortController();\n const onClientClose = (): void => {\n debug('search web aborted');\n abort.abort();\n };\n req.socket.on('close', onClientClose);\n\n try {\n size = parseInt(Number.isFinite(size) ? size : '20', 10);\n from = parseInt(Number.isFinite(from) ? from : '0', 10);\n\n debug('storage search initiated');\n data = await storage.search({\n query,\n url,\n abort,\n });\n debug('storage items tota: %o', data.length);\n const checkAccessPromises: searchUtils.SearchItemPkg[] = await Promise.all(\n data.map((pkgItem) => {\n return checkAccess(pkgItem, auth, req.remote_user);\n })\n );\n\n const final: searchUtils.SearchItemPkg[] = checkAccessPromises\n .filter((i) => i !== null)\n .slice(from, from + size);\n logger.debug(`search results ${final?.length}`);\n\n const response: searchUtils.SearchResults = {\n objects: final,\n total: final.length,\n time: new Date().toUTCString(),\n };\n\n res.status(HTTP_STATUS.OK).json(response);\n } catch (error) {\n logger.error({ error }, 'search endpoint has failed @{error.message}');\n next(error);\n return;\n } finally {\n req.socket.off('close', onClientClose);\n }\n });\n}\n"],"mappings":";;;;;;AASA,IAAM,WAAA,GAAA,MAAA,SAAmB,sBAAsB;;;;;;;AAQ/C,SAAA,eAAyB,OAAO,MAAY,SAAkB,QAAsB;CAClF,SAAS,YAAY,KAAU,MAAW,YAAsC;EAC9E,OAAO,IAAI,SAAS,SAAS,WAAW;GACtC,KAAK,aAAa,EAAE,aAAa,KAAK,SAAS,KAAK,GAAG,YAAY,SAAU,KAAK,SAAS;IACzF,IAAI,KACF,IAAI,IAAI,UAAU,OAAO,IAAI,MAAM,EAAE,MAAM,SAAS,GAGlD,OAAO,QAAQ,IAAI;SAEnB,OAAO,GAAG;SAGZ,OAAO,QAAQ,UAAU,MAAM,IAAI;GAEvC,CAAC;EACH,CAAC;CACH;CAEA,MAAM,IAAI,sBAAA,qBAAqB,QAAQ,OAAO,KAAK,KAAK,SAAS;EAC/D,MAAM,EAAE,OAAO,QAAQ;EACvB,IAAI,CAAC,MAAM,QAAQ,CAAC,QAAQ,MAAM,EAAE,KAAK,MAAM,MAAM,EAAE;EACvD,IAAI;EACJ,MAAM,QAAQ,IAAI,gBAAgB;EAClC,MAAM,sBAA4B;GAChC,QAAM,oBAAoB;GAC1B,MAAM,MAAM;EACd;EACA,IAAI,OAAO,GAAG,SAAS,aAAa;EAEpC,IAAI;GACF,OAAO,SAAS,OAAO,SAAS,IAAI,IAAI,OAAO,MAAM,EAAE;GACvD,OAAO,SAAS,OAAO,SAAS,IAAI,IAAI,OAAO,KAAK,EAAE;GAEtD,QAAM,0BAA0B;GAChC,OAAO,MAAM,QAAQ,OAAO;IAC1B;IACA;IACA;GACF,CAAC;GACD,QAAM,0BAA0B,KAAK,MAAM;GAO3C,MAAM,SAAqC,MANoB,QAAQ,IACrE,KAAK,KAAK,YAAY;IACpB,OAAO,YAAY,SAAS,MAAM,IAAI,WAAW;GACnD,CAAC,CACH,GAGG,QAAQ,MAAM,MAAM,IAAI,EACxB,MAAM,MAAM,OAAO,IAAI;GAC1B,OAAO,MAAM,kBAAkB,OAAO,QAAQ;GAE9C,MAAM,WAAsC;IAC1C,SAAS;IACT,OAAO,MAAM;IACb,uBAAM,IAAI,KAAK,GAAE,YAAY;GAC/B;GAEA,IAAI,OAAO,gBAAA,YAAY,EAAE,EAAE,KAAK,QAAQ;EAC1C,SAAS,OAAO;GACd,OAAO,MAAM,EAAE,MAAM,GAAG,6CAA6C;GACrE,KAAK,KAAK;GACV;EACF,UAAU;GACR,IAAI,OAAO,IAAI,SAAS,aAAa;EACvC;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"search.js","names":[],"sources":["../../src/v1/search.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport type { Auth } from '@verdaccio/auth';\nimport type { searchUtils } from '@verdaccio/core';\nimport { HTTP_STATUS } from '@verdaccio/core';\nimport { SEARCH_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Storage } from '@verdaccio/store';\nimport type { Config, Logger } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:api:search');\n\nconst DEFAULT_SIZE = 20;\n// the public npm registry caps page size at 250 as well\nconst MAX_SIZE = 250;\n// upper bound for the pagination offset so a single request cannot force\n// an access check on an arbitrarily large slice of the catalog\nconst MAX_FROM = 10_000;\n// access checks run in batches so the scan can stop early once the\n// requested page is filled\nconst CHECK_ACCESS_BATCH_SIZE = 50;\n\nfunction parseQueryInt(value: unknown, defaultValue: number, max: number): number {\n const parsed = Number.parseInt(String(value), 10);\n if (Number.isNaN(parsed) || parsed < 0) {\n return defaultValue;\n }\n return Math.min(parsed, max);\n}\n\n/**\n * Endpoint for npm search v1\n * Empty value\n * - {\"objects\":[],\"total\":0,\"time\":\"Sun Jul 25 2021 14:09:11 GMT+0000 (Coordinated Universal Time)\"}\n * req: 'GET /-/v1/search?text=react&size=20&frpom=0&quality=0.65&popularity=0.98&maintenance=0.5'\n */\nexport default function (\n route,\n auth: Auth,\n storage: Storage,\n config: Config,\n logger: Logger\n): void {\n function checkAccess(pkg: any, auth: any, remoteUser): Promise<searchUtils.SearchItemPkg | null> {\n return new Promise((resolve, reject) => {\n auth.allow_access({ packageName: pkg?.package?.name }, remoteUser, function (err, allowed) {\n if (err) {\n if (err.status && String(err.status).match(/^4\\d\\d$/)) {\n // auth plugin returns 4xx user error,\n // that's equivalent of !allowed basically\n return resolve(null);\n } else {\n reject(err);\n }\n } else {\n return resolve(allowed ? pkg : null);\n }\n });\n });\n }\n\n route.get(\n SEARCH_API_ENDPOINTS.search,\n rateLimit(config?.userRateLimit),\n async (req, res, next) => {\n const { query, url } = req;\n // `size` and `from` are attacker-controlled: clamp them so a single\n // request cannot demand unbounded work\n const size = parseQueryInt(query.size, DEFAULT_SIZE, MAX_SIZE);\n const from = parseQueryInt(query.from, 0, MAX_FROM);\n // rebuild the query and relative url from the clamped values so the\n // bounds hold end-to-end: storage plugins read `query.size`/`query.from`\n // and the uplink proxy forwards `url` verbatim to the remote registry\n const safeQuery = { ...query, size, from };\n const searchParams = new URLSearchParams(query);\n searchParams.set('size', String(size));\n searchParams.set('from', String(from));\n const safeUrl = `${url.split('?')[0]}?${searchParams.toString()}`;\n const abort = new AbortController();\n const onClientClose = (): void => {\n debug('search web aborted');\n abort.abort();\n };\n req.socket.on('close', onClientClose);\n\n try {\n debug('storage search initiated');\n const data = await storage.search({\n query: safeQuery,\n url: safeUrl,\n abort,\n });\n debug('storage items total: %o', data.length);\n\n // evaluate access in batches and stop as soon as the requested page\n // is filled, instead of running an auth check on every result\n const requested = from + size;\n const allowed: searchUtils.SearchItemPkg[] = [];\n for (\n let i = 0;\n i < data.length && allowed.length < requested && !abort.signal.aborted;\n i += CHECK_ACCESS_BATCH_SIZE\n ) {\n const batch = await Promise.all(\n data\n .slice(i, i + CHECK_ACCESS_BATCH_SIZE)\n .map((pkgItem) => checkAccess(pkgItem, auth, req.remote_user))\n );\n for (const item of batch) {\n if (item !== null) {\n allowed.push(item);\n }\n }\n }\n\n const final: searchUtils.SearchItemPkg[] = allowed.slice(from, requested);\n logger.debug(`search results ${final?.length}`);\n\n const response: searchUtils.SearchResults = {\n objects: final,\n total: final.length,\n time: new Date().toUTCString(),\n };\n\n res.status(HTTP_STATUS.OK).json(response);\n } catch (error) {\n logger.error({ error }, 'search endpoint has failed @{error.message}');\n next(error);\n return;\n } finally {\n req.socket.off('close', onClientClose);\n }\n }\n );\n}\n"],"mappings":";;;;;;AASA,IAAM,WAAA,GAAA,MAAA,QAAA,CAAmB,sBAAsB;AAE/C,IAAM,eAAe;AAErB,IAAM,WAAW;AAGjB,IAAM,WAAW;AAGjB,IAAM,0BAA0B;AAEhC,SAAS,cAAc,OAAgB,cAAsB,KAAqB;CAChF,MAAM,SAAS,OAAO,SAAS,OAAO,KAAK,GAAG,EAAE;CAChD,IAAI,OAAO,MAAM,MAAM,KAAK,SAAS,GACnC,OAAO;CAET,OAAO,KAAK,IAAI,QAAQ,GAAG;AAC7B;;;;;;;AAQA,SAAA,eACE,OACA,MACA,SACA,QACA,QACM;CACN,SAAS,YAAY,KAAU,MAAW,YAAuD;EAC/F,OAAO,IAAI,SAAS,SAAS,WAAW;GACtC,KAAK,aAAa,EAAE,aAAa,KAAK,SAAS,KAAK,GAAG,YAAY,SAAU,KAAK,SAAS;IACzF,IAAI,KACF,IAAI,IAAI,UAAU,OAAO,IAAI,MAAM,CAAC,CAAC,MAAM,SAAS,GAGlD,OAAO,QAAQ,IAAI;SAEnB,OAAO,GAAG;SAGZ,OAAO,QAAQ,UAAU,MAAM,IAAI;GAEvC,CAAC;EACH,CAAC;CACH;CAEA,MAAM,IACJ,sBAAA,qBAAqB,SAAA,GAAA,sBAAA,UAAA,CACX,QAAQ,aAAa,GAC/B,OAAO,KAAK,KAAK,SAAS;EACxB,MAAM,EAAE,OAAO,QAAQ;EAGvB,MAAM,OAAO,cAAc,MAAM,MAAM,cAAc,QAAQ;EAC7D,MAAM,OAAO,cAAc,MAAM,MAAM,GAAG,QAAQ;EAIlD,MAAM,YAAY;GAAE,GAAG;GAAO;GAAM;EAAK;EACzC,MAAM,eAAe,IAAI,gBAAgB,KAAK;EAC9C,aAAa,IAAI,QAAQ,OAAO,IAAI,CAAC;EACrC,aAAa,IAAI,QAAQ,OAAO,IAAI,CAAC;EACrC,MAAM,UAAU,GAAG,IAAI,MAAM,GAAG,CAAC,CAAC,GAAG,GAAG,aAAa,SAAS;EAC9D,MAAM,QAAQ,IAAI,gBAAgB;EAClC,MAAM,sBAA4B;GAChC,QAAM,oBAAoB;GAC1B,MAAM,MAAM;EACd;EACA,IAAI,OAAO,GAAG,SAAS,aAAa;EAEpC,IAAI;GACF,QAAM,0BAA0B;GAChC,MAAM,OAAO,MAAM,QAAQ,OAAO;IAChC,OAAO;IACP,KAAK;IACL;GACF,CAAC;GACD,QAAM,2BAA2B,KAAK,MAAM;GAI5C,MAAM,YAAY,OAAO;GACzB,MAAM,UAAuC,CAAC;GAC9C,KACE,IAAI,IAAI,GACR,IAAI,KAAK,UAAU,QAAQ,SAAS,aAAa,CAAC,MAAM,OAAO,SAC/D,KAAK,yBACL;IACA,MAAM,QAAQ,MAAM,QAAQ,IAC1B,KACG,MAAM,GAAG,IAAI,uBAAuB,CAAC,CACrC,KAAK,YAAY,YAAY,SAAS,MAAM,IAAI,WAAW,CAAC,CACjE;IACA,KAAK,MAAM,QAAQ,OACjB,IAAI,SAAS,MACX,QAAQ,KAAK,IAAI;GAGvB;GAEA,MAAM,QAAqC,QAAQ,MAAM,MAAM,SAAS;GACxE,OAAO,MAAM,kBAAkB,OAAO,QAAQ;GAE9C,MAAM,WAAsC;IAC1C,SAAS;IACT,OAAO,MAAM;IACb,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;GAC/B;GAEA,IAAI,OAAO,gBAAA,YAAY,EAAE,CAAC,CAAC,KAAK,QAAQ;EAC1C,SAAS,OAAO;GACd,OAAO,MAAM,EAAE,MAAM,GAAG,6CAA6C;GACrE,KAAK,KAAK;GACV;EACF,UAAU;GACR,IAAI,OAAO,IAAI,SAAS,aAAa;EACvC;CACF,CACF;AACF"}
@@ -1,15 +1,24 @@
1
- import { SEARCH_API_ENDPOINTS } from "@verdaccio/middleware";
1
+ import { SEARCH_API_ENDPOINTS, rateLimit } from "@verdaccio/middleware";
2
2
  import { HTTP_STATUS } from "@verdaccio/core";
3
3
  import buildDebug from "debug";
4
4
  //#region src/v1/search.ts
5
5
  var debug = buildDebug("verdaccio:api:search");
6
+ var DEFAULT_SIZE = 20;
7
+ var MAX_SIZE = 250;
8
+ var MAX_FROM = 1e4;
9
+ var CHECK_ACCESS_BATCH_SIZE = 50;
10
+ function parseQueryInt(value, defaultValue, max) {
11
+ const parsed = Number.parseInt(String(value), 10);
12
+ if (Number.isNaN(parsed) || parsed < 0) return defaultValue;
13
+ return Math.min(parsed, max);
14
+ }
6
15
  /**
7
16
  * Endpoint for npm search v1
8
17
  * Empty value
9
18
  * - {"objects":[],"total":0,"time":"Sun Jul 25 2021 14:09:11 GMT+0000 (Coordinated Universal Time)"}
10
19
  * req: 'GET /-/v1/search?text=react&size=20&frpom=0&quality=0.65&popularity=0.98&maintenance=0.5'
11
20
  */
12
- function search_default(route, auth, storage, logger) {
21
+ function search_default(route, auth, storage, config, logger) {
13
22
  function checkAccess(pkg, auth, remoteUser) {
14
23
  return new Promise((resolve, reject) => {
15
24
  auth.allow_access({ packageName: pkg?.package?.name }, remoteUser, function(err, allowed) {
@@ -19,10 +28,19 @@ function search_default(route, auth, storage, logger) {
19
28
  });
20
29
  });
21
30
  }
22
- route.get(SEARCH_API_ENDPOINTS.search, async (req, res, next) => {
31
+ route.get(SEARCH_API_ENDPOINTS.search, rateLimit(config?.userRateLimit), async (req, res, next) => {
23
32
  const { query, url } = req;
24
- let [size, from] = ["size", "from"].map((k) => query[k]);
25
- let data;
33
+ const size = parseQueryInt(query.size, DEFAULT_SIZE, MAX_SIZE);
34
+ const from = parseQueryInt(query.from, 0, MAX_FROM);
35
+ const safeQuery = {
36
+ ...query,
37
+ size,
38
+ from
39
+ };
40
+ const searchParams = new URLSearchParams(query);
41
+ searchParams.set("size", String(size));
42
+ searchParams.set("from", String(from));
43
+ const safeUrl = `${url.split("?")[0]}?${searchParams.toString()}`;
26
44
  const abort = new AbortController();
27
45
  const onClientClose = () => {
28
46
  debug("search web aborted");
@@ -30,18 +48,20 @@ function search_default(route, auth, storage, logger) {
30
48
  };
31
49
  req.socket.on("close", onClientClose);
32
50
  try {
33
- size = parseInt(Number.isFinite(size) ? size : "20", 10);
34
- from = parseInt(Number.isFinite(from) ? from : "0", 10);
35
51
  debug("storage search initiated");
36
- data = await storage.search({
37
- query,
38
- url,
52
+ const data = await storage.search({
53
+ query: safeQuery,
54
+ url: safeUrl,
39
55
  abort
40
56
  });
41
- debug("storage items tota: %o", data.length);
42
- const final = (await Promise.all(data.map((pkgItem) => {
43
- return checkAccess(pkgItem, auth, req.remote_user);
44
- }))).filter((i) => i !== null).slice(from, from + size);
57
+ debug("storage items total: %o", data.length);
58
+ const requested = from + size;
59
+ const allowed = [];
60
+ for (let i = 0; i < data.length && allowed.length < requested && !abort.signal.aborted; i += CHECK_ACCESS_BATCH_SIZE) {
61
+ const batch = await Promise.all(data.slice(i, i + CHECK_ACCESS_BATCH_SIZE).map((pkgItem) => checkAccess(pkgItem, auth, req.remote_user)));
62
+ for (const item of batch) if (item !== null) allowed.push(item);
63
+ }
64
+ const final = allowed.slice(from, requested);
45
65
  logger.debug(`search results ${final?.length}`);
46
66
  const response = {
47
67
  objects: final,