@verdaccio/core 6.0.0-6-next.8 → 6.0.0-6-next.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/build/constants.d.ts +2 -1
- package/build/constants.js +4 -3
- package/build/constants.js.map +1 -1
- package/build/error-utils.d.ts +1 -1
- package/build/error-utils.js +1 -1
- package/build/error-utils.js.map +1 -1
- package/build/index.d.ts +3 -2
- package/build/index.js +7 -3
- package/build/index.js.map +1 -1
- package/build/string-utils.d.ts +8 -0
- package/build/string-utils.js +44 -0
- package/build/string-utils.js.map +1 -0
- package/build/validation-utils.d.ts +1 -0
- package/build/validation-utils.js +5 -0
- package/build/validation-utils.js.map +1 -1
- package/package.json +3 -3
- package/src/constants.ts +2 -1
- package/src/error-utils.ts +2 -4
- package/src/index.ts +3 -1
- package/src/string-utils.ts +36 -0
- package/src/validation-utils.ts +10 -1
- package/test/string-utils.spec.ts +40 -0
- package/test/validation-utilts.spec.ts +52 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @verdaccio/core
|
|
2
2
|
|
|
3
|
+
## 6.0.0-6-next.48
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 62c24b63: feat: add passwordValidationRegex property
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 43f32687: fix: abbreviated headers handle quality values
|
|
12
|
+
|
|
13
|
+
## 6.0.0-6-next.47
|
|
14
|
+
|
|
3
15
|
## 6.0.0-6-next.8
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/build/constants.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const
|
|
1
|
+
export declare const DEFAULT_PASSWORD_VALIDATION: RegExp;
|
|
2
2
|
export declare const TIME_EXPIRATION_24H = "24h";
|
|
3
3
|
export declare const TIME_EXPIRATION_7D = "7d";
|
|
4
4
|
export declare const DIST_TAGS = "dist-tags";
|
|
@@ -37,6 +37,7 @@ export declare const HEADERS: {
|
|
|
37
37
|
NONE_MATCH: string;
|
|
38
38
|
ETAG: string;
|
|
39
39
|
JSON_CHARSET: string;
|
|
40
|
+
JSON_INSTALL_CHARSET: string;
|
|
40
41
|
OCTET_STREAM: string;
|
|
41
42
|
TEXT_CHARSET: string;
|
|
42
43
|
WWW_AUTH: string;
|
package/build/constants.js
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.USERS = exports.TOKEN_BEARER = exports.TOKEN_BASIC = exports.TIME_EXPIRATION_7D = exports.TIME_EXPIRATION_24H = exports.ROLES = exports.PACKAGE_ACCESS = exports.LOG_VERDACCIO_ERROR = exports.LOG_VERDACCIO_BYTES = exports.LOG_STATUS_MESSAGE = exports.LATEST = exports.HTTP_STATUS = exports.HEADER_TYPE = exports.HEADERS = exports.ERROR_CODE = exports.DIST_TAGS = exports.DEFAULT_USER = exports.
|
|
6
|
+
exports.USERS = exports.TOKEN_BEARER = exports.TOKEN_BASIC = exports.TIME_EXPIRATION_7D = exports.TIME_EXPIRATION_24H = exports.ROLES = exports.PACKAGE_ACCESS = exports.LOG_VERDACCIO_ERROR = exports.LOG_VERDACCIO_BYTES = exports.LOG_STATUS_MESSAGE = exports.LATEST = exports.HTTP_STATUS = exports.HEADER_TYPE = exports.HEADERS = exports.ERROR_CODE = exports.DIST_TAGS = exports.DEFAULT_USER = exports.DEFAULT_PASSWORD_VALIDATION = exports.CHARACTER_ENCODING = exports.API_MESSAGE = void 0;
|
|
7
7
|
|
|
8
8
|
var _httpStatusCodes = _interopRequireDefault(require("http-status-codes"));
|
|
9
9
|
|
|
10
10
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
exports.
|
|
12
|
+
const DEFAULT_PASSWORD_VALIDATION = /.{3}$/;
|
|
13
|
+
exports.DEFAULT_PASSWORD_VALIDATION = DEFAULT_PASSWORD_VALIDATION;
|
|
14
14
|
const TIME_EXPIRATION_24H = '24h';
|
|
15
15
|
exports.TIME_EXPIRATION_24H = TIME_EXPIRATION_24H;
|
|
16
16
|
const TIME_EXPIRATION_7D = '7d';
|
|
@@ -62,6 +62,7 @@ const HEADERS = {
|
|
|
62
62
|
NONE_MATCH: 'If-None-Match',
|
|
63
63
|
ETAG: 'ETag',
|
|
64
64
|
JSON_CHARSET: 'application/json; charset=utf-8',
|
|
65
|
+
JSON_INSTALL_CHARSET: 'application/vnd.npm.install-v1+json; charset=utf-8',
|
|
65
66
|
OCTET_STREAM: 'application/octet-stream; charset=utf-8',
|
|
66
67
|
TEXT_CHARSET: 'text/plain; charset=utf-8',
|
|
67
68
|
WWW_AUTH: 'WWW-Authenticate',
|
package/build/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","names":["
|
|
1
|
+
{"version":3,"file":"constants.js","names":["DEFAULT_PASSWORD_VALIDATION","TIME_EXPIRATION_24H","TIME_EXPIRATION_7D","DIST_TAGS","LATEST","USERS","DEFAULT_USER","HEADER_TYPE","CONTENT_ENCODING","CONTENT_TYPE","CONTENT_LENGTH","ACCEPT_ENCODING","CHARACTER_ENCODING","UTF8","TOKEN_BASIC","TOKEN_BEARER","HEADERS","ACCEPT","USER_AGENT","JSON","TEXT_PLAIN","TEXT_PLAIN_UTF8","TEXT_HTML_UTF8","TEXT_HTML","AUTHORIZATION","FORWARDED_PROTO","FORWARDED_FOR","FRAMES_OPTIONS","CSP","CTO","XSS","NONE_MATCH","ETAG","JSON_CHARSET","JSON_INSTALL_CHARSET","OCTET_STREAM","TEXT_CHARSET","WWW_AUTH","GZIP","HTTP_STATUS","OK","httpCodes","CREATED","MULTIPLE_CHOICES","NOT_MODIFIED","BAD_REQUEST","UNAUTHORIZED","FORBIDDEN","NOT_FOUND","CONFLICT","NOT_IMPLEMENTED","UNSUPPORTED_MEDIA","UNSUPPORTED_MEDIA_TYPE","BAD_DATA","UNPROCESSABLE_ENTITY","INTERNAL_ERROR","INTERNAL_SERVER_ERROR","SERVICE_UNAVAILABLE","LOOP_DETECTED","ERROR_CODE","token_required","API_MESSAGE","PKG_CREATED","PKG_CHANGED","PKG_REMOVED","PKG_PUBLISHED","TARBALL_UPLOADED","TARBALL_REMOVED","TAG_UPDATED","TAG_REMOVED","TAG_ADDED","LOGGED_OUT","LOG_STATUS_MESSAGE","LOG_VERDACCIO_ERROR","LOG_VERDACCIO_BYTES","ROLES","$ALL","ALL","$AUTH","$ANONYMOUS","DEPRECATED_ALL","DEPRECATED_AUTH","DEPRECATED_ANONYMOUS","PACKAGE_ACCESS","SCOPE"],"sources":["../src/constants.ts"],"sourcesContent":["import httpCodes from 'http-status-codes';\n\nexport const DEFAULT_PASSWORD_VALIDATION = /.{3}$/;\nexport const TIME_EXPIRATION_24H = '24h';\nexport const TIME_EXPIRATION_7D = '7d';\nexport const DIST_TAGS = 'dist-tags';\nexport const LATEST = 'latest';\nexport const USERS = 'users';\nexport const DEFAULT_USER = 'Anonymous';\n\nexport const HEADER_TYPE = {\n CONTENT_ENCODING: 'content-encoding',\n CONTENT_TYPE: 'content-type',\n CONTENT_LENGTH: 'content-length',\n ACCEPT_ENCODING: 'accept-encoding',\n};\n\nexport const CHARACTER_ENCODING = {\n UTF8: 'utf8',\n};\n\n// @deprecated use Bearer instead\nexport const TOKEN_BASIC = 'Basic';\nexport const TOKEN_BEARER = 'Bearer';\n\nexport const HEADERS = {\n ACCEPT: 'Accept',\n ACCEPT_ENCODING: 'Accept-Encoding',\n USER_AGENT: 'User-Agent',\n JSON: 'application/json',\n CONTENT_TYPE: 'Content-type',\n CONTENT_LENGTH: 'content-length',\n TEXT_PLAIN: 'text/plain',\n TEXT_PLAIN_UTF8: 'text/plain; charset=utf-8',\n TEXT_HTML_UTF8: 'text/html; charset=utf-8',\n TEXT_HTML: 'text/html',\n AUTHORIZATION: 'authorization',\n // only set with proxy that setup HTTPS\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto\n FORWARDED_PROTO: 'X-Forwarded-Proto',\n FORWARDED_FOR: 'X-Forwarded-For',\n FRAMES_OPTIONS: 'X-Frame-Options',\n CSP: 'Content-Security-Policy',\n CTO: 'X-Content-Type-Options',\n XSS: 'X-XSS-Protection',\n NONE_MATCH: 'If-None-Match',\n ETAG: 'ETag',\n JSON_CHARSET: 'application/json; charset=utf-8',\n JSON_INSTALL_CHARSET: 'application/vnd.npm.install-v1+json; charset=utf-8',\n OCTET_STREAM: 'application/octet-stream; charset=utf-8',\n TEXT_CHARSET: 'text/plain; charset=utf-8',\n WWW_AUTH: 'WWW-Authenticate',\n GZIP: 'gzip',\n};\n\nexport const HTTP_STATUS = {\n OK: httpCodes.OK,\n CREATED: httpCodes.CREATED,\n MULTIPLE_CHOICES: httpCodes.MULTIPLE_CHOICES,\n NOT_MODIFIED: httpCodes.NOT_MODIFIED,\n BAD_REQUEST: httpCodes.BAD_REQUEST,\n UNAUTHORIZED: httpCodes.UNAUTHORIZED,\n FORBIDDEN: httpCodes.FORBIDDEN,\n NOT_FOUND: httpCodes.NOT_FOUND,\n CONFLICT: httpCodes.CONFLICT,\n NOT_IMPLEMENTED: httpCodes.NOT_IMPLEMENTED,\n UNSUPPORTED_MEDIA: httpCodes.UNSUPPORTED_MEDIA_TYPE,\n BAD_DATA: httpCodes.UNPROCESSABLE_ENTITY,\n INTERNAL_ERROR: httpCodes.INTERNAL_SERVER_ERROR,\n SERVICE_UNAVAILABLE: httpCodes.SERVICE_UNAVAILABLE,\n LOOP_DETECTED: 508,\n};\n\nexport const ERROR_CODE = {\n token_required: 'token is required',\n};\n\nexport const API_MESSAGE = {\n PKG_CREATED: 'created new package',\n PKG_CHANGED: 'package changed',\n PKG_REMOVED: 'package removed',\n PKG_PUBLISHED: 'package published',\n TARBALL_UPLOADED: 'tarball uploaded successfully',\n TARBALL_REMOVED: 'tarball removed',\n TAG_UPDATED: 'tags updated',\n TAG_REMOVED: 'tag removed',\n TAG_ADDED: 'package tagged',\n OK: 'ok',\n LOGGED_OUT: 'Logged out',\n};\n\nexport const LOG_STATUS_MESSAGE =\n \"@{status}, user: @{user}(@{remoteIP}), req: '@{request.method} @{request.url}'\";\nexport const LOG_VERDACCIO_ERROR = `${LOG_STATUS_MESSAGE}, error: @{!error}`;\nexport const LOG_VERDACCIO_BYTES = `${LOG_STATUS_MESSAGE}, bytes: @{bytes.in}/@{bytes.out}`;\n\nexport const ROLES = {\n $ALL: '$all',\n ALL: 'all',\n $AUTH: '$authenticated',\n $ANONYMOUS: '$anonymous',\n DEPRECATED_ALL: '@all',\n DEPRECATED_AUTH: '@authenticated',\n DEPRECATED_ANONYMOUS: '@anonymous',\n};\n\nexport const PACKAGE_ACCESS = {\n SCOPE: '@*/*',\n ALL: '**',\n};\n"],"mappings":";;;;;;;AAAA;;;;AAEO,MAAMA,2BAA2B,GAAG,OAApC;;AACA,MAAMC,mBAAmB,GAAG,KAA5B;;AACA,MAAMC,kBAAkB,GAAG,IAA3B;;AACA,MAAMC,SAAS,GAAG,WAAlB;;AACA,MAAMC,MAAM,GAAG,QAAf;;AACA,MAAMC,KAAK,GAAG,OAAd;;AACA,MAAMC,YAAY,GAAG,WAArB;;AAEA,MAAMC,WAAW,GAAG;EACzBC,gBAAgB,EAAE,kBADO;EAEzBC,YAAY,EAAE,cAFW;EAGzBC,cAAc,EAAE,gBAHS;EAIzBC,eAAe,EAAE;AAJQ,CAApB;;AAOA,MAAMC,kBAAkB,GAAG;EAChCC,IAAI,EAAE;AAD0B,CAA3B,C,CAIP;;;AACO,MAAMC,WAAW,GAAG,OAApB;;AACA,MAAMC,YAAY,GAAG,QAArB;;AAEA,MAAMC,OAAO,GAAG;EACrBC,MAAM,EAAE,QADa;EAErBN,eAAe,EAAE,iBAFI;EAGrBO,UAAU,EAAE,YAHS;EAIrBC,IAAI,EAAE,kBAJe;EAKrBV,YAAY,EAAE,cALO;EAMrBC,cAAc,EAAE,gBANK;EAOrBU,UAAU,EAAE,YAPS;EAQrBC,eAAe,EAAE,2BARI;EASrBC,cAAc,EAAE,0BATK;EAUrBC,SAAS,EAAE,WAVU;EAWrBC,aAAa,EAAE,eAXM;EAYrB;EACA;EACAC,eAAe,EAAE,mBAdI;EAerBC,aAAa,EAAE,iBAfM;EAgBrBC,cAAc,EAAE,iBAhBK;EAiBrBC,GAAG,EAAE,yBAjBgB;EAkBrBC,GAAG,EAAE,wBAlBgB;EAmBrBC,GAAG,EAAE,kBAnBgB;EAoBrBC,UAAU,EAAE,eApBS;EAqBrBC,IAAI,EAAE,MArBe;EAsBrBC,YAAY,EAAE,iCAtBO;EAuBrBC,oBAAoB,EAAE,oDAvBD;EAwBrBC,YAAY,EAAE,yCAxBO;EAyBrBC,YAAY,EAAE,2BAzBO;EA0BrBC,QAAQ,EAAE,kBA1BW;EA2BrBC,IAAI,EAAE;AA3Be,CAAhB;;AA8BA,MAAMC,WAAW,GAAG;EACzBC,EAAE,EAAEC,wBAAA,CAAUD,EADW;EAEzBE,OAAO,EAAED,wBAAA,CAAUC,OAFM;EAGzBC,gBAAgB,EAAEF,wBAAA,CAAUE,gBAHH;EAIzBC,YAAY,EAAEH,wBAAA,CAAUG,YAJC;EAKzBC,WAAW,EAAEJ,wBAAA,CAAUI,WALE;EAMzBC,YAAY,EAAEL,wBAAA,CAAUK,YANC;EAOzBC,SAAS,EAAEN,wBAAA,CAAUM,SAPI;EAQzBC,SAAS,EAAEP,wBAAA,CAAUO,SARI;EASzBC,QAAQ,EAAER,wBAAA,CAAUQ,QATK;EAUzBC,eAAe,EAAET,wBAAA,CAAUS,eAVF;EAWzBC,iBAAiB,EAAEV,wBAAA,CAAUW,sBAXJ;EAYzBC,QAAQ,EAAEZ,wBAAA,CAAUa,oBAZK;EAazBC,cAAc,EAAEd,wBAAA,CAAUe,qBAbD;EAczBC,mBAAmB,EAAEhB,wBAAA,CAAUgB,mBAdN;EAezBC,aAAa,EAAE;AAfU,CAApB;;AAkBA,MAAMC,UAAU,GAAG;EACxBC,cAAc,EAAE;AADQ,CAAnB;;AAIA,MAAMC,WAAW,GAAG;EACzBC,WAAW,EAAE,qBADY;EAEzBC,WAAW,EAAE,iBAFY;EAGzBC,WAAW,EAAE,iBAHY;EAIzBC,aAAa,EAAE,mBAJU;EAKzBC,gBAAgB,EAAE,+BALO;EAMzBC,eAAe,EAAE,iBANQ;EAOzBC,WAAW,EAAE,cAPY;EAQzBC,WAAW,EAAE,aARY;EASzBC,SAAS,EAAE,gBATc;EAUzB9B,EAAE,EAAE,IAVqB;EAWzB+B,UAAU,EAAE;AAXa,CAApB;;AAcA,MAAMC,kBAAkB,GAC7B,gFADK;;AAEA,MAAMC,mBAAmB,GAAI,GAAED,kBAAmB,oBAAlD;;AACA,MAAME,mBAAmB,GAAI,GAAEF,kBAAmB,mCAAlD;;AAEA,MAAMG,KAAK,GAAG;EACnBC,IAAI,EAAE,MADa;EAEnBC,GAAG,EAAE,KAFc;EAGnBC,KAAK,EAAE,gBAHY;EAInBC,UAAU,EAAE,YAJO;EAKnBC,cAAc,EAAE,MALG;EAMnBC,eAAe,EAAE,gBANE;EAOnBC,oBAAoB,EAAE;AAPH,CAAd;;AAUA,MAAMC,cAAc,GAAG;EAC5BC,KAAK,EAAE,MADqB;EAE5BP,GAAG,EAAE;AAFuB,CAAvB"}
|
package/build/error-utils.d.ts
CHANGED
package/build/error-utils.js
CHANGED
|
@@ -21,7 +21,7 @@ var _constants = require("./constants");
|
|
|
21
21
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
22
22
|
|
|
23
23
|
const API_ERROR = {
|
|
24
|
-
PASSWORD_SHORT:
|
|
24
|
+
PASSWORD_SHORT: `The provided password does not pass the validation`,
|
|
25
25
|
MUST_BE_LOGGED: 'You must be logged in to publish packages.',
|
|
26
26
|
PLUGIN_ERROR: 'bug in the auth plugin system',
|
|
27
27
|
CONFIG_BAD_FORMAT: 'config file must be an object',
|
package/build/error-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-utils.js","names":["API_ERROR","PASSWORD_SHORT","
|
|
1
|
+
{"version":3,"file":"error-utils.js","names":["API_ERROR","PASSWORD_SHORT","MUST_BE_LOGGED","PLUGIN_ERROR","CONFIG_BAD_FORMAT","BAD_USERNAME_PASSWORD","NO_PACKAGE","PACKAGE_CANNOT_BE_ADDED","BAD_DATA","NOT_ALLOWED","NOT_ALLOWED_PUBLISH","INTERNAL_SERVER_ERROR","UNKNOWN_ERROR","NOT_PACKAGE_UPLINK","UPLINK_OFFLINE_PUBLISH","UPLINK_OFFLINE","NOT_MODIFIED_NO_DATA","CONTENT_MISMATCH","NOT_FILE_UPLINK","MAX_USERS_REACHED","VERSION_NOT_EXIST","NO_SUCH_FILE","UNSUPORTED_REGISTRY_CALL","FILE_NOT_FOUND","REGISTRATION_DISABLED","UNAUTHORIZED_ACCESS","BAD_STATUS_CODE","PACKAGE_EXIST","BAD_AUTH_HEADER","WEB_DISABLED","DEPRECATED_BASIC_HEADER","BAD_FORMAT_USER_GROUP","RESOURCE_UNAVAILABLE","BAD_PACKAGE_DATA","USERNAME_PASSWORD_REQUIRED","USERNAME_ALREADY_REGISTERED","SUPPORT_ERRORS","PLUGIN_MISSING_INTERFACE","TFA_DISABLED","STORAGE_NOT_IMPLEMENT","PARAMETERS_NOT_VALID","APP_ERROR","CONFIG_NOT_VALID","PROFILE_ERROR","PASSWORD_VALIDATION","getError","code","message","httpError","createError","getConflict","HTTP_STATUS","CONFLICT","getBadData","customMessage","getBadRequest","BAD_REQUEST","getInternalError","INTERNAL_ERROR","getUnauthorized","UNAUTHORIZED","getForbidden","FORBIDDEN","getServiceUnavailable","SERVICE_UNAVAILABLE","getNotFound","NOT_FOUND","getCode","statusCode"],"sources":["../src/error-utils.ts"],"sourcesContent":["import createError, { HttpError } from 'http-errors';\n\nimport { HTTP_STATUS } from './constants';\n\nexport const API_ERROR = {\n PASSWORD_SHORT: `The provided password does not pass the validation`,\n MUST_BE_LOGGED: 'You must be logged in to publish packages.',\n PLUGIN_ERROR: 'bug in the auth plugin system',\n CONFIG_BAD_FORMAT: 'config file must be an object',\n BAD_USERNAME_PASSWORD: 'bad username/password, access denied',\n NO_PACKAGE: 'no such package available',\n PACKAGE_CANNOT_BE_ADDED: 'this package cannot be added',\n BAD_DATA: 'bad data',\n NOT_ALLOWED: 'not allowed to access package',\n NOT_ALLOWED_PUBLISH: 'not allowed to publish package',\n INTERNAL_SERVER_ERROR: 'internal server error',\n UNKNOWN_ERROR: 'unknown error',\n NOT_PACKAGE_UPLINK: 'package does not exist on uplink',\n UPLINK_OFFLINE_PUBLISH: 'one of the uplinks is down, refuse to publish',\n UPLINK_OFFLINE: 'uplink is offline',\n NOT_MODIFIED_NO_DATA: 'no data',\n CONTENT_MISMATCH: 'content length mismatch',\n NOT_FILE_UPLINK: \"file doesn't exist on uplink\",\n MAX_USERS_REACHED: 'maximum amount of users reached',\n VERSION_NOT_EXIST: \"this version doesn't exist\",\n NO_SUCH_FILE: 'no such file available',\n UNSUPORTED_REGISTRY_CALL: 'unsupported registry call',\n FILE_NOT_FOUND: 'File not found',\n REGISTRATION_DISABLED: 'user registration disabled',\n UNAUTHORIZED_ACCESS: 'unauthorized access',\n BAD_STATUS_CODE: 'bad status code',\n PACKAGE_EXIST: 'this package is already present',\n BAD_AUTH_HEADER: 'bad authorization header',\n WEB_DISABLED: 'Web interface is disabled in the config file',\n DEPRECATED_BASIC_HEADER: 'basic authentication is deprecated, please use JWT instead',\n BAD_FORMAT_USER_GROUP: 'user groups is different than an array',\n RESOURCE_UNAVAILABLE: 'resource unavailable',\n BAD_PACKAGE_DATA: 'bad incoming package data',\n USERNAME_PASSWORD_REQUIRED: 'username and password is required',\n USERNAME_ALREADY_REGISTERED: 'username is already registered',\n};\n\nexport const SUPPORT_ERRORS = {\n PLUGIN_MISSING_INTERFACE: 'the plugin does not provide implementation of the requested feature',\n TFA_DISABLED: 'the two-factor authentication is not yet supported',\n STORAGE_NOT_IMPLEMENT: 'the storage does not support token saving',\n PARAMETERS_NOT_VALID: 'the parameters are not valid',\n};\n\nexport const APP_ERROR = {\n CONFIG_NOT_VALID: 'CONFIG: it does not look like a valid config file',\n PROFILE_ERROR: 'profile unexpected error',\n PASSWORD_VALIDATION: 'not valid password',\n};\n\nexport type VerdaccioError = HttpError & { code: number };\n\nfunction getError(code: number, message: string): VerdaccioError {\n const httpError = createError(code, message);\n\n httpError.code = code;\n\n return httpError as VerdaccioError;\n}\n\nexport function getConflict(message: string = API_ERROR.PACKAGE_EXIST): VerdaccioError {\n return getError(HTTP_STATUS.CONFLICT, message);\n}\n\nexport function getBadData(customMessage?: string): VerdaccioError {\n return getError(HTTP_STATUS.BAD_DATA, customMessage || API_ERROR.BAD_DATA);\n}\n\nexport function getBadRequest(customMessage: string): VerdaccioError {\n return getError(HTTP_STATUS.BAD_REQUEST, customMessage);\n}\n\nexport function getInternalError(customMessage?: string): VerdaccioError {\n return customMessage\n ? getError(HTTP_STATUS.INTERNAL_ERROR, customMessage)\n : getError(HTTP_STATUS.INTERNAL_ERROR, API_ERROR.UNKNOWN_ERROR);\n}\n\nexport function getUnauthorized(message = 'no credentials provided'): VerdaccioError {\n return getError(HTTP_STATUS.UNAUTHORIZED, message);\n}\n\nexport function getForbidden(message = \"can't use this filename\"): VerdaccioError {\n return getError(HTTP_STATUS.FORBIDDEN, message);\n}\n\nexport function getServiceUnavailable(\n message: string = API_ERROR.RESOURCE_UNAVAILABLE\n): VerdaccioError {\n return getError(HTTP_STATUS.SERVICE_UNAVAILABLE, message);\n}\n\nexport function getNotFound(customMessage?: string): VerdaccioError {\n return getError(HTTP_STATUS.NOT_FOUND, customMessage || API_ERROR.NO_PACKAGE);\n}\n\nexport function getCode(statusCode: number, customMessage: string): VerdaccioError {\n return getError(statusCode, customMessage);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;;AAEA;;;;AAEO,MAAMA,SAAS,GAAG;EACvBC,cAAc,EAAG,oDADM;EAEvBC,cAAc,EAAE,4CAFO;EAGvBC,YAAY,EAAE,+BAHS;EAIvBC,iBAAiB,EAAE,+BAJI;EAKvBC,qBAAqB,EAAE,sCALA;EAMvBC,UAAU,EAAE,2BANW;EAOvBC,uBAAuB,EAAE,8BAPF;EAQvBC,QAAQ,EAAE,UARa;EASvBC,WAAW,EAAE,+BATU;EAUvBC,mBAAmB,EAAE,gCAVE;EAWvBC,qBAAqB,EAAE,uBAXA;EAYvBC,aAAa,EAAE,eAZQ;EAavBC,kBAAkB,EAAE,kCAbG;EAcvBC,sBAAsB,EAAE,+CAdD;EAevBC,cAAc,EAAE,mBAfO;EAgBvBC,oBAAoB,EAAE,SAhBC;EAiBvBC,gBAAgB,EAAE,yBAjBK;EAkBvBC,eAAe,EAAE,8BAlBM;EAmBvBC,iBAAiB,EAAE,iCAnBI;EAoBvBC,iBAAiB,EAAE,4BApBI;EAqBvBC,YAAY,EAAE,wBArBS;EAsBvBC,wBAAwB,EAAE,2BAtBH;EAuBvBC,cAAc,EAAE,gBAvBO;EAwBvBC,qBAAqB,EAAE,4BAxBA;EAyBvBC,mBAAmB,EAAE,qBAzBE;EA0BvBC,eAAe,EAAE,iBA1BM;EA2BvBC,aAAa,EAAE,iCA3BQ;EA4BvBC,eAAe,EAAE,0BA5BM;EA6BvBC,YAAY,EAAE,8CA7BS;EA8BvBC,uBAAuB,EAAE,4DA9BF;EA+BvBC,qBAAqB,EAAE,wCA/BA;EAgCvBC,oBAAoB,EAAE,sBAhCC;EAiCvBC,gBAAgB,EAAE,2BAjCK;EAkCvBC,0BAA0B,EAAE,mCAlCL;EAmCvBC,2BAA2B,EAAE;AAnCN,CAAlB;;AAsCA,MAAMC,cAAc,GAAG;EAC5BC,wBAAwB,EAAE,qEADE;EAE5BC,YAAY,EAAE,oDAFc;EAG5BC,qBAAqB,EAAE,2CAHK;EAI5BC,oBAAoB,EAAE;AAJM,CAAvB;;AAOA,MAAMC,SAAS,GAAG;EACvBC,gBAAgB,EAAE,mDADK;EAEvBC,aAAa,EAAE,0BAFQ;EAGvBC,mBAAmB,EAAE;AAHE,CAAlB;;;AAQP,SAASC,QAAT,CAAkBC,IAAlB,EAAgCC,OAAhC,EAAiE;EAC/D,MAAMC,SAAS,GAAG,IAAAC,mBAAA,EAAYH,IAAZ,EAAkBC,OAAlB,CAAlB;EAEAC,SAAS,CAACF,IAAV,GAAiBA,IAAjB;EAEA,OAAOE,SAAP;AACD;;AAEM,SAASE,WAAT,CAAqBH,OAAe,GAAG/C,SAAS,CAAC2B,aAAjD,EAAgF;EACrF,OAAOkB,QAAQ,CAACM,sBAAA,CAAYC,QAAb,EAAuBL,OAAvB,CAAf;AACD;;AAEM,SAASM,UAAT,CAAoBC,aAApB,EAA4D;EACjE,OAAOT,QAAQ,CAACM,sBAAA,CAAY3C,QAAb,EAAuB8C,aAAa,IAAItD,SAAS,CAACQ,QAAlD,CAAf;AACD;;AAEM,SAAS+C,aAAT,CAAuBD,aAAvB,EAA8D;EACnE,OAAOT,QAAQ,CAACM,sBAAA,CAAYK,WAAb,EAA0BF,aAA1B,CAAf;AACD;;AAEM,SAASG,gBAAT,CAA0BH,aAA1B,EAAkE;EACvE,OAAOA,aAAa,GAChBT,QAAQ,CAACM,sBAAA,CAAYO,cAAb,EAA6BJ,aAA7B,CADQ,GAEhBT,QAAQ,CAACM,sBAAA,CAAYO,cAAb,EAA6B1D,SAAS,CAACY,aAAvC,CAFZ;AAGD;;AAEM,SAAS+C,eAAT,CAAyBZ,OAAO,GAAG,yBAAnC,EAA8E;EACnF,OAAOF,QAAQ,CAACM,sBAAA,CAAYS,YAAb,EAA2Bb,OAA3B,CAAf;AACD;;AAEM,SAASc,YAAT,CAAsBd,OAAO,GAAG,yBAAhC,EAA2E;EAChF,OAAOF,QAAQ,CAACM,sBAAA,CAAYW,SAAb,EAAwBf,OAAxB,CAAf;AACD;;AAEM,SAASgB,qBAAT,CACLhB,OAAe,GAAG/C,SAAS,CAACgC,oBADvB,EAEW;EAChB,OAAOa,QAAQ,CAACM,sBAAA,CAAYa,mBAAb,EAAkCjB,OAAlC,CAAf;AACD;;AAEM,SAASkB,WAAT,CAAqBX,aAArB,EAA6D;EAClE,OAAOT,QAAQ,CAACM,sBAAA,CAAYe,SAAb,EAAwBZ,aAAa,IAAItD,SAAS,CAACM,UAAnD,CAAf;AACD;;AAEM,SAAS6D,OAAT,CAAiBC,UAAjB,EAAqCd,aAArC,EAA4E;EACjF,OAAOT,QAAQ,CAACuB,UAAD,EAAad,aAAb,CAAf;AACD"}
|
package/build/index.d.ts
CHANGED
|
@@ -5,9 +5,10 @@ import * as pkgUtils from './pkg-utils';
|
|
|
5
5
|
import * as pluginUtils from './plugin-utils';
|
|
6
6
|
import * as searchUtils from './search-utils';
|
|
7
7
|
import * as streamUtils from './stream-utils';
|
|
8
|
+
import * as stringUtils from './string-utils';
|
|
8
9
|
import * as validatioUtils from './validation-utils';
|
|
9
10
|
import * as warningUtils from './warning-utils';
|
|
10
11
|
export { VerdaccioError, API_ERROR, SUPPORT_ERRORS, APP_ERROR } from './error-utils';
|
|
11
|
-
export { TOKEN_BASIC, TOKEN_BEARER, HTTP_STATUS, API_MESSAGE, HEADERS, DIST_TAGS, CHARACTER_ENCODING, HEADER_TYPE, LATEST,
|
|
12
|
+
export { TOKEN_BASIC, TOKEN_BEARER, HTTP_STATUS, API_MESSAGE, HEADERS, DIST_TAGS, CHARACTER_ENCODING, HEADER_TYPE, LATEST, DEFAULT_PASSWORD_VALIDATION, DEFAULT_USER, USERS, } from './constants';
|
|
12
13
|
declare const validationUtils: typeof validatioUtils;
|
|
13
|
-
export { fileUtils, pkgUtils, searchUtils, streamUtils, errorUtils, validatioUtils, validationUtils, constants, pluginUtils, warningUtils, };
|
|
14
|
+
export { fileUtils, pkgUtils, searchUtils, streamUtils, errorUtils, validatioUtils, validationUtils, stringUtils, constants, pluginUtils, warningUtils, };
|
package/build/index.js
CHANGED
|
@@ -27,10 +27,10 @@ Object.defineProperty(exports, "CHARACTER_ENCODING", {
|
|
|
27
27
|
return constants.CHARACTER_ENCODING;
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
|
-
Object.defineProperty(exports, "
|
|
30
|
+
Object.defineProperty(exports, "DEFAULT_PASSWORD_VALIDATION", {
|
|
31
31
|
enumerable: true,
|
|
32
32
|
get: function () {
|
|
33
|
-
return constants.
|
|
33
|
+
return constants.DEFAULT_PASSWORD_VALIDATION;
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
Object.defineProperty(exports, "DEFAULT_USER", {
|
|
@@ -99,7 +99,7 @@ Object.defineProperty(exports, "VerdaccioError", {
|
|
|
99
99
|
return errorUtils.VerdaccioError;
|
|
100
100
|
}
|
|
101
101
|
});
|
|
102
|
-
exports.warningUtils = exports.validationUtils = exports.validatioUtils = exports.streamUtils = exports.searchUtils = exports.pluginUtils = exports.pkgUtils = exports.fileUtils = exports.errorUtils = exports.constants = void 0;
|
|
102
|
+
exports.warningUtils = exports.validationUtils = exports.validatioUtils = exports.stringUtils = exports.streamUtils = exports.searchUtils = exports.pluginUtils = exports.pkgUtils = exports.fileUtils = exports.errorUtils = exports.constants = void 0;
|
|
103
103
|
|
|
104
104
|
var constants = _interopRequireWildcard(require("./constants"));
|
|
105
105
|
|
|
@@ -129,6 +129,10 @@ var streamUtils = _interopRequireWildcard(require("./stream-utils"));
|
|
|
129
129
|
|
|
130
130
|
exports.streamUtils = streamUtils;
|
|
131
131
|
|
|
132
|
+
var stringUtils = _interopRequireWildcard(require("./string-utils"));
|
|
133
|
+
|
|
134
|
+
exports.stringUtils = stringUtils;
|
|
135
|
+
|
|
132
136
|
var validatioUtils = _interopRequireWildcard(require("./validation-utils"));
|
|
133
137
|
|
|
134
138
|
exports.validatioUtils = validatioUtils;
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["validationUtils","validatioUtils"],"sources":["../src/index.ts"],"sourcesContent":["import * as constants from './constants';\nimport * as errorUtils from './error-utils';\nimport * as fileUtils from './file-utils';\nimport * as pkgUtils from './pkg-utils';\nimport * as pluginUtils from './plugin-utils';\nimport * as searchUtils from './search-utils';\nimport * as streamUtils from './stream-utils';\nimport * as validatioUtils from './validation-utils';\nimport * as warningUtils from './warning-utils';\n\nexport { VerdaccioError, API_ERROR, SUPPORT_ERRORS, APP_ERROR } from './error-utils';\nexport {\n TOKEN_BASIC,\n TOKEN_BEARER,\n HTTP_STATUS,\n API_MESSAGE,\n HEADERS,\n DIST_TAGS,\n CHARACTER_ENCODING,\n HEADER_TYPE,\n LATEST,\n
|
|
1
|
+
{"version":3,"file":"index.js","names":["validationUtils","validatioUtils"],"sources":["../src/index.ts"],"sourcesContent":["import * as constants from './constants';\nimport * as errorUtils from './error-utils';\nimport * as fileUtils from './file-utils';\nimport * as pkgUtils from './pkg-utils';\nimport * as pluginUtils from './plugin-utils';\nimport * as searchUtils from './search-utils';\nimport * as streamUtils from './stream-utils';\nimport * as stringUtils from './string-utils';\nimport * as validatioUtils from './validation-utils';\nimport * as warningUtils from './warning-utils';\n\nexport { VerdaccioError, API_ERROR, SUPPORT_ERRORS, APP_ERROR } from './error-utils';\nexport {\n TOKEN_BASIC,\n TOKEN_BEARER,\n HTTP_STATUS,\n API_MESSAGE,\n HEADERS,\n DIST_TAGS,\n CHARACTER_ENCODING,\n HEADER_TYPE,\n LATEST,\n DEFAULT_PASSWORD_VALIDATION,\n DEFAULT_USER,\n USERS,\n} from './constants';\nconst validationUtils = validatioUtils;\nexport {\n fileUtils,\n pkgUtils,\n searchUtils,\n streamUtils,\n errorUtils,\n // TODO: remove this typo\n validatioUtils,\n validationUtils,\n stringUtils,\n constants,\n pluginUtils,\n warningUtils,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;AACA;;;;AACA;;;;AACA;;;;AACA;;;;AACA;;;;AACA;;;;AACA;;;;AACA;;;;AACA;;;;;;;;AAiBA,MAAMA,eAAe,GAAGC,cAAxB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality values, or q-values and q-factors, are used to describe the order
|
|
3
|
+
* of priority of values in a comma-separated list.
|
|
4
|
+
* It is a special syntax allowed in some HTTP headers and in HTML.
|
|
5
|
+
* https://developer.mozilla.org/en-US/docs/Glossary/Quality_values
|
|
6
|
+
* @param headerValue
|
|
7
|
+
*/
|
|
8
|
+
export declare function getByQualityPriorityValue(headerValue: string | undefined | null): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getByQualityPriorityValue = getByQualityPriorityValue;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Quality values, or q-values and q-factors, are used to describe the order
|
|
10
|
+
* of priority of values in a comma-separated list.
|
|
11
|
+
* It is a special syntax allowed in some HTTP headers and in HTML.
|
|
12
|
+
* https://developer.mozilla.org/en-US/docs/Glossary/Quality_values
|
|
13
|
+
* @param headerValue
|
|
14
|
+
*/
|
|
15
|
+
function getByQualityPriorityValue(headerValue) {
|
|
16
|
+
if (typeof headerValue !== 'string') {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const split = headerValue.split(',');
|
|
21
|
+
|
|
22
|
+
if (split.length <= 1) {
|
|
23
|
+
const qList = split[0].split(';');
|
|
24
|
+
return qList[0];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let [header] = split.reduce((acc, item) => {
|
|
28
|
+
const qList = item.split(';');
|
|
29
|
+
|
|
30
|
+
if (qList.length > 1) {
|
|
31
|
+
const [accept, q] = qList;
|
|
32
|
+
const [, query] = q.split('=');
|
|
33
|
+
acc.push([accept.trim(), query ? query : 0]);
|
|
34
|
+
} else {
|
|
35
|
+
acc.push([qList[0], 0]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return acc;
|
|
39
|
+
}, []).sort(function (a, b) {
|
|
40
|
+
return b[1] - a[1];
|
|
41
|
+
});
|
|
42
|
+
return header[0];
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=string-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"string-utils.js","names":["getByQualityPriorityValue","headerValue","split","length","qList","header","reduce","acc","item","accept","q","query","push","trim","sort","a","b"],"sources":["../src/string-utils.ts"],"sourcesContent":["/**\n * Quality values, or q-values and q-factors, are used to describe the order\n * of priority of values in a comma-separated list.\n * It is a special syntax allowed in some HTTP headers and in HTML.\n * https://developer.mozilla.org/en-US/docs/Glossary/Quality_values\n * @param headerValue\n */\nexport function getByQualityPriorityValue(headerValue: string | undefined | null): string {\n if (typeof headerValue !== 'string') {\n return '';\n }\n\n const split = headerValue.split(',');\n\n if (split.length <= 1) {\n const qList = split[0].split(';');\n return qList[0];\n }\n\n let [header] = split\n .reduce((acc, item: string) => {\n const qList = item.split(';');\n if (qList.length > 1) {\n const [accept, q] = qList;\n const [, query] = q.split('=');\n acc.push([accept.trim(), query ? query : 0]);\n } else {\n acc.push([qList[0], 0]);\n }\n return acc;\n }, [] as any)\n .sort(function (a, b) {\n return b[1] - a[1];\n });\n return header[0];\n}\n"],"mappings":";;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASA,yBAAT,CAAmCC,WAAnC,EAAmF;EACxF,IAAI,OAAOA,WAAP,KAAuB,QAA3B,EAAqC;IACnC,OAAO,EAAP;EACD;;EAED,MAAMC,KAAK,GAAGD,WAAW,CAACC,KAAZ,CAAkB,GAAlB,CAAd;;EAEA,IAAIA,KAAK,CAACC,MAAN,IAAgB,CAApB,EAAuB;IACrB,MAAMC,KAAK,GAAGF,KAAK,CAAC,CAAD,CAAL,CAASA,KAAT,CAAe,GAAf,CAAd;IACA,OAAOE,KAAK,CAAC,CAAD,CAAZ;EACD;;EAED,IAAI,CAACC,MAAD,IAAWH,KAAK,CACjBI,MADY,CACL,CAACC,GAAD,EAAMC,IAAN,KAAuB;IAC7B,MAAMJ,KAAK,GAAGI,IAAI,CAACN,KAAL,CAAW,GAAX,CAAd;;IACA,IAAIE,KAAK,CAACD,MAAN,GAAe,CAAnB,EAAsB;MACpB,MAAM,CAACM,MAAD,EAASC,CAAT,IAAcN,KAApB;MACA,MAAM,GAAGO,KAAH,IAAYD,CAAC,CAACR,KAAF,CAAQ,GAAR,CAAlB;MACAK,GAAG,CAACK,IAAJ,CAAS,CAACH,MAAM,CAACI,IAAP,EAAD,EAAgBF,KAAK,GAAGA,KAAH,GAAW,CAAhC,CAAT;IACD,CAJD,MAIO;MACLJ,GAAG,CAACK,IAAJ,CAAS,CAACR,KAAK,CAAC,CAAD,CAAN,EAAW,CAAX,CAAT;IACD;;IACD,OAAOG,GAAP;EACD,CAXY,EAWV,EAXU,EAYZO,IAZY,CAYP,UAAUC,CAAV,EAAaC,CAAb,EAAgB;IACpB,OAAOA,CAAC,CAAC,CAAD,CAAD,GAAOD,CAAC,CAAC,CAAD,CAAf;EACD,CAdY,CAAf;EAeA,OAAOV,MAAM,CAAC,CAAD,CAAb;AACD"}
|
|
@@ -8,6 +8,7 @@ exports.isPackageNameScoped = isPackageNameScoped;
|
|
|
8
8
|
exports.normalizeMetadata = normalizeMetadata;
|
|
9
9
|
exports.validateName = validateName;
|
|
10
10
|
exports.validatePackage = validatePackage;
|
|
11
|
+
exports.validatePassword = validatePassword;
|
|
11
12
|
Object.defineProperty(exports, "validatePublishSingleVersion", {
|
|
12
13
|
enumerable: true,
|
|
13
14
|
get: function () {
|
|
@@ -123,4 +124,8 @@ function isObject(obj) {
|
|
|
123
124
|
|
|
124
125
|
return (typeof obj === 'object' || typeof obj.prototype === 'undefined') && Array.isArray(obj) === false;
|
|
125
126
|
}
|
|
127
|
+
|
|
128
|
+
function validatePassword(password, validation = _constants.DEFAULT_PASSWORD_VALIDATION) {
|
|
129
|
+
return typeof password === 'string' && validation instanceof RegExp ? password.match(validation) !== null : false;
|
|
130
|
+
}
|
|
126
131
|
//# sourceMappingURL=validation-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation-utils.js","names":["isPackageNameScoped","name","startsWith","validateName","normalizedName","toLowerCase","isScoped","scopedName","split","match","includes","validatePackage","nameList","length","slice","normalizeMetadata","manifest","assert","strictEqual","_manifest","isObject","DIST_TAGS","obj","prototype","Array","isArray"],"sources":["../src/validation-utils.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { Manifest } from '@verdaccio/types';\n\nimport { DIST_TAGS } from './constants';\n\nexport { validatePublishSingleVersion } from './schemes/publish-manifest';\n\nexport function isPackageNameScoped(name: string): boolean {\n return name.startsWith('@');\n}\n\n/**\n * From normalize-package-data/lib/fixer.js\n * @param {*} name the package name\n * @return {Boolean} whether is valid or not\n */\nexport function validateName(name: string): boolean {\n if (typeof name !== 'string') {\n return false;\n }\n\n let normalizedName: string = name.toLowerCase();\n\n const isScoped: boolean = isPackageNameScoped(name);\n const scopedName = name.split('/', 2)[1];\n\n if (isScoped && typeof scopedName !== 'undefined') {\n normalizedName = scopedName.toLowerCase();\n }\n\n /**\n * Some context about the first regex\n * - npm used to have a different tarball naming system.\n * eg: http://registry.npmjs.com/thirty-two\n * https://registry.npmjs.org/thirty-two/-/thirty-two@0.0.1.tgz\n * The file name thirty-two@0.0.1.tgz, the version and the pkg name was separated by an at (@)\n * while nowadays the naming system is based in dashes\n * https://registry.npmjs.org/verdaccio/-/verdaccio-1.4.0.tgz\n *\n * more info here: https://github.com/rlidwka/sinopia/issues/75\n */\n return !(\n !normalizedName.match(/^[-a-zA-Z0-9_.!~*'()@]+$/) ||\n normalizedName.startsWith('.') || // \".bin\", etc.\n ['node_modules', '__proto__', 'favicon.ico'].includes(normalizedName)\n );\n}\n\n/**\n * Validate a package.\n * @return {Boolean} whether the package is valid or not\n */\nexport function validatePackage(name: string): boolean {\n const nameList = name.split('/', 2);\n if (nameList.length === 1) {\n // normal package\n return validateName(nameList[0]);\n }\n // scoped package\n return nameList[0][0] === '@' && validateName(nameList[0].slice(1)) && validateName(nameList[1]);\n}\n\n/**\n * Validate the package metadata, add additional properties whether are missing within\n * the metadata properties.\n * @param {*} manifest\n * @param {*} name\n * @return {Object} the object with additional properties as dist-tags ad versions\n * FUTURE: rename to normalizeMetadata\n */\nexport function normalizeMetadata(manifest: Manifest, name: string): Manifest {\n assert.strictEqual(manifest.name, name);\n const _manifest = { ...manifest };\n\n if (!isObject(manifest[DIST_TAGS])) {\n _manifest[DIST_TAGS] = {};\n }\n\n // This may not be nee dit\n if (!isObject(manifest['versions'])) {\n _manifest['versions'] = {};\n }\n\n if (!isObject(manifest['time'])) {\n _manifest['time'] = {};\n }\n\n return _manifest;\n}\n\n/**\n * Check whether an element is an Object\n * @param {*} obj the element\n * @return {Boolean}\n */\nexport function isObject(obj: any): boolean {\n if (obj === null || typeof obj === 'undefined' || typeof obj === 'string') {\n return false;\n }\n\n return (\n (typeof obj === 'object' || typeof obj.prototype === 'undefined') &&\n Array.isArray(obj) === false\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"validation-utils.js","names":["isPackageNameScoped","name","startsWith","validateName","normalizedName","toLowerCase","isScoped","scopedName","split","match","includes","validatePackage","nameList","length","slice","normalizeMetadata","manifest","assert","strictEqual","_manifest","isObject","DIST_TAGS","obj","prototype","Array","isArray","validatePassword","password","validation","DEFAULT_PASSWORD_VALIDATION","RegExp"],"sources":["../src/validation-utils.ts"],"sourcesContent":["import assert from 'assert';\n\nimport { Manifest } from '@verdaccio/types';\n\nimport { DEFAULT_PASSWORD_VALIDATION, DIST_TAGS } from './constants';\n\nexport { validatePublishSingleVersion } from './schemes/publish-manifest';\n\nexport function isPackageNameScoped(name: string): boolean {\n return name.startsWith('@');\n}\n\n/**\n * From normalize-package-data/lib/fixer.js\n * @param {*} name the package name\n * @return {Boolean} whether is valid or not\n */\nexport function validateName(name: string): boolean {\n if (typeof name !== 'string') {\n return false;\n }\n\n let normalizedName: string = name.toLowerCase();\n\n const isScoped: boolean = isPackageNameScoped(name);\n const scopedName = name.split('/', 2)[1];\n\n if (isScoped && typeof scopedName !== 'undefined') {\n normalizedName = scopedName.toLowerCase();\n }\n\n /**\n * Some context about the first regex\n * - npm used to have a different tarball naming system.\n * eg: http://registry.npmjs.com/thirty-two\n * https://registry.npmjs.org/thirty-two/-/thirty-two@0.0.1.tgz\n * The file name thirty-two@0.0.1.tgz, the version and the pkg name was separated by an at (@)\n * while nowadays the naming system is based in dashes\n * https://registry.npmjs.org/verdaccio/-/verdaccio-1.4.0.tgz\n *\n * more info here: https://github.com/rlidwka/sinopia/issues/75\n */\n return !(\n !normalizedName.match(/^[-a-zA-Z0-9_.!~*'()@]+$/) ||\n normalizedName.startsWith('.') || // \".bin\", etc.\n ['node_modules', '__proto__', 'favicon.ico'].includes(normalizedName)\n );\n}\n\n/**\n * Validate a package.\n * @return {Boolean} whether the package is valid or not\n */\nexport function validatePackage(name: string): boolean {\n const nameList = name.split('/', 2);\n if (nameList.length === 1) {\n // normal package\n return validateName(nameList[0]);\n }\n // scoped package\n return nameList[0][0] === '@' && validateName(nameList[0].slice(1)) && validateName(nameList[1]);\n}\n\n/**\n * Validate the package metadata, add additional properties whether are missing within\n * the metadata properties.\n * @param {*} manifest\n * @param {*} name\n * @return {Object} the object with additional properties as dist-tags ad versions\n * FUTURE: rename to normalizeMetadata\n */\nexport function normalizeMetadata(manifest: Manifest, name: string): Manifest {\n assert.strictEqual(manifest.name, name);\n const _manifest = { ...manifest };\n\n if (!isObject(manifest[DIST_TAGS])) {\n _manifest[DIST_TAGS] = {};\n }\n\n // This may not be nee dit\n if (!isObject(manifest['versions'])) {\n _manifest['versions'] = {};\n }\n\n if (!isObject(manifest['time'])) {\n _manifest['time'] = {};\n }\n\n return _manifest;\n}\n\n/**\n * Check whether an element is an Object\n * @param {*} obj the element\n * @return {Boolean}\n */\nexport function isObject(obj: any): boolean {\n if (obj === null || typeof obj === 'undefined' || typeof obj === 'string') {\n return false;\n }\n\n return (\n (typeof obj === 'object' || typeof obj.prototype === 'undefined') &&\n Array.isArray(obj) === false\n );\n}\n\nexport function validatePassword(\n password: string,\n validation: RegExp = DEFAULT_PASSWORD_VALIDATION\n): boolean {\n return typeof password === 'string' && validation instanceof RegExp\n ? password.match(validation) !== null\n : false;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verdaccio/core",
|
|
3
|
-
"version": "6.0.0-6-next.
|
|
3
|
+
"version": "6.0.0-6-next.48",
|
|
4
4
|
"description": "core utilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"private",
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
"semver": "7.3.7",
|
|
40
40
|
"ajv": "8.11.0",
|
|
41
41
|
"process-warning": "1.0.0",
|
|
42
|
-
"core-js": "3.25.
|
|
42
|
+
"core-js": "3.25.2"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"lodash": "4.17.21",
|
|
46
|
-
"@verdaccio/types": "11.0.0-6-next.
|
|
46
|
+
"@verdaccio/types": "11.0.0-6-next.17"
|
|
47
47
|
},
|
|
48
48
|
"funding": {
|
|
49
49
|
"type": "opencollective",
|
package/src/constants.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import httpCodes from 'http-status-codes';
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const DEFAULT_PASSWORD_VALIDATION = /.{3}$/;
|
|
4
4
|
export const TIME_EXPIRATION_24H = '24h';
|
|
5
5
|
export const TIME_EXPIRATION_7D = '7d';
|
|
6
6
|
export const DIST_TAGS = 'dist-tags';
|
|
@@ -46,6 +46,7 @@ export const HEADERS = {
|
|
|
46
46
|
NONE_MATCH: 'If-None-Match',
|
|
47
47
|
ETAG: 'ETag',
|
|
48
48
|
JSON_CHARSET: 'application/json; charset=utf-8',
|
|
49
|
+
JSON_INSTALL_CHARSET: 'application/vnd.npm.install-v1+json; charset=utf-8',
|
|
49
50
|
OCTET_STREAM: 'application/octet-stream; charset=utf-8',
|
|
50
51
|
TEXT_CHARSET: 'text/plain; charset=utf-8',
|
|
51
52
|
WWW_AUTH: 'WWW-Authenticate',
|
package/src/error-utils.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import createError, { HttpError } from 'http-errors';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { HTTP_STATUS } from './constants';
|
|
4
4
|
|
|
5
5
|
export const API_ERROR = {
|
|
6
|
-
PASSWORD_SHORT:
|
|
7
|
-
`The provided password is too short. Please pick a password longer than ` +
|
|
8
|
-
`${passLength} characters.`,
|
|
6
|
+
PASSWORD_SHORT: `The provided password does not pass the validation`,
|
|
9
7
|
MUST_BE_LOGGED: 'You must be logged in to publish packages.',
|
|
10
8
|
PLUGIN_ERROR: 'bug in the auth plugin system',
|
|
11
9
|
CONFIG_BAD_FORMAT: 'config file must be an object',
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import * as pkgUtils from './pkg-utils';
|
|
|
5
5
|
import * as pluginUtils from './plugin-utils';
|
|
6
6
|
import * as searchUtils from './search-utils';
|
|
7
7
|
import * as streamUtils from './stream-utils';
|
|
8
|
+
import * as stringUtils from './string-utils';
|
|
8
9
|
import * as validatioUtils from './validation-utils';
|
|
9
10
|
import * as warningUtils from './warning-utils';
|
|
10
11
|
|
|
@@ -19,7 +20,7 @@ export {
|
|
|
19
20
|
CHARACTER_ENCODING,
|
|
20
21
|
HEADER_TYPE,
|
|
21
22
|
LATEST,
|
|
22
|
-
|
|
23
|
+
DEFAULT_PASSWORD_VALIDATION,
|
|
23
24
|
DEFAULT_USER,
|
|
24
25
|
USERS,
|
|
25
26
|
} from './constants';
|
|
@@ -33,6 +34,7 @@ export {
|
|
|
33
34
|
// TODO: remove this typo
|
|
34
35
|
validatioUtils,
|
|
35
36
|
validationUtils,
|
|
37
|
+
stringUtils,
|
|
36
38
|
constants,
|
|
37
39
|
pluginUtils,
|
|
38
40
|
warningUtils,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality values, or q-values and q-factors, are used to describe the order
|
|
3
|
+
* of priority of values in a comma-separated list.
|
|
4
|
+
* It is a special syntax allowed in some HTTP headers and in HTML.
|
|
5
|
+
* https://developer.mozilla.org/en-US/docs/Glossary/Quality_values
|
|
6
|
+
* @param headerValue
|
|
7
|
+
*/
|
|
8
|
+
export function getByQualityPriorityValue(headerValue: string | undefined | null): string {
|
|
9
|
+
if (typeof headerValue !== 'string') {
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const split = headerValue.split(',');
|
|
14
|
+
|
|
15
|
+
if (split.length <= 1) {
|
|
16
|
+
const qList = split[0].split(';');
|
|
17
|
+
return qList[0];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let [header] = split
|
|
21
|
+
.reduce((acc, item: string) => {
|
|
22
|
+
const qList = item.split(';');
|
|
23
|
+
if (qList.length > 1) {
|
|
24
|
+
const [accept, q] = qList;
|
|
25
|
+
const [, query] = q.split('=');
|
|
26
|
+
acc.push([accept.trim(), query ? query : 0]);
|
|
27
|
+
} else {
|
|
28
|
+
acc.push([qList[0], 0]);
|
|
29
|
+
}
|
|
30
|
+
return acc;
|
|
31
|
+
}, [] as any)
|
|
32
|
+
.sort(function (a, b) {
|
|
33
|
+
return b[1] - a[1];
|
|
34
|
+
});
|
|
35
|
+
return header[0];
|
|
36
|
+
}
|
package/src/validation-utils.ts
CHANGED
|
@@ -2,7 +2,7 @@ import assert from 'assert';
|
|
|
2
2
|
|
|
3
3
|
import { Manifest } from '@verdaccio/types';
|
|
4
4
|
|
|
5
|
-
import { DIST_TAGS } from './constants';
|
|
5
|
+
import { DEFAULT_PASSWORD_VALIDATION, DIST_TAGS } from './constants';
|
|
6
6
|
|
|
7
7
|
export { validatePublishSingleVersion } from './schemes/publish-manifest';
|
|
8
8
|
|
|
@@ -104,3 +104,12 @@ export function isObject(obj: any): boolean {
|
|
|
104
104
|
Array.isArray(obj) === false
|
|
105
105
|
);
|
|
106
106
|
}
|
|
107
|
+
|
|
108
|
+
export function validatePassword(
|
|
109
|
+
password: string,
|
|
110
|
+
validation: RegExp = DEFAULT_PASSWORD_VALIDATION
|
|
111
|
+
): boolean {
|
|
112
|
+
return typeof password === 'string' && validation instanceof RegExp
|
|
113
|
+
? password.match(validation) !== null
|
|
114
|
+
: false;
|
|
115
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { stringUtils } from '../src';
|
|
2
|
+
|
|
3
|
+
describe('string-utils', () => {
|
|
4
|
+
test('getByQualityPriorityValue', () => {
|
|
5
|
+
expect(stringUtils.getByQualityPriorityValue('')).toEqual('');
|
|
6
|
+
expect(stringUtils.getByQualityPriorityValue(null)).toEqual('');
|
|
7
|
+
expect(stringUtils.getByQualityPriorityValue(undefined)).toEqual('');
|
|
8
|
+
expect(stringUtils.getByQualityPriorityValue('something')).toEqual('something');
|
|
9
|
+
expect(stringUtils.getByQualityPriorityValue('something,')).toEqual('something');
|
|
10
|
+
expect(stringUtils.getByQualityPriorityValue('0,')).toEqual('0');
|
|
11
|
+
expect(stringUtils.getByQualityPriorityValue('application/json')).toEqual('application/json');
|
|
12
|
+
expect(stringUtils.getByQualityPriorityValue('application/json; q=1')).toEqual(
|
|
13
|
+
'application/json'
|
|
14
|
+
);
|
|
15
|
+
expect(stringUtils.getByQualityPriorityValue('application/json; q=')).toEqual(
|
|
16
|
+
'application/json'
|
|
17
|
+
);
|
|
18
|
+
expect(stringUtils.getByQualityPriorityValue('application/json;')).toEqual('application/json');
|
|
19
|
+
expect(
|
|
20
|
+
stringUtils.getByQualityPriorityValue(
|
|
21
|
+
'application/json; q=1.0, application/vnd.npm.install-v1+json; q=0.9, */*'
|
|
22
|
+
)
|
|
23
|
+
).toEqual('application/json');
|
|
24
|
+
expect(
|
|
25
|
+
stringUtils.getByQualityPriorityValue(
|
|
26
|
+
'application/json; q=1.0, application/vnd.npm.install-v1+json; q=, */*'
|
|
27
|
+
)
|
|
28
|
+
).toEqual('application/json');
|
|
29
|
+
expect(
|
|
30
|
+
stringUtils.getByQualityPriorityValue(
|
|
31
|
+
'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.9, */*'
|
|
32
|
+
)
|
|
33
|
+
).toEqual('application/vnd.npm.install-v1+json');
|
|
34
|
+
expect(
|
|
35
|
+
stringUtils.getByQualityPriorityValue(
|
|
36
|
+
'application/vnd.npm.install-v1+json; q=, application/json; q=0.9, */*'
|
|
37
|
+
)
|
|
38
|
+
).toEqual('application/json');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { DIST_TAGS } from '../src/constants';
|
|
1
|
+
import { DEFAULT_PASSWORD_VALIDATION, DIST_TAGS } from '../src/constants';
|
|
2
2
|
import { validatePublishSingleVersion } from '../src/schemes/publish-manifest';
|
|
3
3
|
import {
|
|
4
4
|
isObject,
|
|
5
5
|
normalizeMetadata,
|
|
6
6
|
validateName,
|
|
7
7
|
validatePackage,
|
|
8
|
+
validatePassword,
|
|
8
9
|
} from '../src/validation-utils';
|
|
9
10
|
|
|
10
11
|
describe('validatePackage', () => {
|
|
@@ -166,3 +167,53 @@ describe('validatePublishSingleVersion', () => {
|
|
|
166
167
|
).toBeFalsy();
|
|
167
168
|
});
|
|
168
169
|
});
|
|
170
|
+
|
|
171
|
+
describe('validatePassword', () => {
|
|
172
|
+
test('should validate password according the length', () => {
|
|
173
|
+
expect(validatePassword('12345', DEFAULT_PASSWORD_VALIDATION)).toBeTruthy();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('should validate invalid regex', () => {
|
|
177
|
+
// @ts-expect-error
|
|
178
|
+
expect(validatePassword('12345', 34234342)).toBeFalsy();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('should validate invalid regex (undefined)', () => {
|
|
182
|
+
expect(validatePassword('12345', undefined)).toBeTruthy();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('should validate invalid password)', () => {
|
|
186
|
+
// @ts-expect-error
|
|
187
|
+
expect(validatePassword(undefined)).toBeFalsy();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('should validate invalid password number)', () => {
|
|
191
|
+
// @ts-expect-error
|
|
192
|
+
expect(validatePassword(2342344234342)).toBeFalsy();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('should fails on validate password according the length', () => {
|
|
196
|
+
expect(validatePassword('12345', /.{10}$/)).toBeFalsy();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('should fails handle complex password validation', () => {
|
|
200
|
+
expect(validatePassword('12345', /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/)).toBeFalsy();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('should handle complex password validation', () => {
|
|
204
|
+
expect(
|
|
205
|
+
validatePassword(
|
|
206
|
+
'c<?_:srdsj&WyZgY}r4:l[F<RgV<}',
|
|
207
|
+
/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/
|
|
208
|
+
)
|
|
209
|
+
).toBeTruthy();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('should fails on validate password according the length and default config', () => {
|
|
213
|
+
expect(validatePassword('12')).toBeFalsy();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('should validate password according the length and default config', () => {
|
|
217
|
+
expect(validatePassword('1235678910')).toBeTruthy();
|
|
218
|
+
});
|
|
219
|
+
});
|