chore: sync with verdaccio master
158
src/lib/constants.js
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
export const DEFAULT_PORT: string = '4873';
|
||||
export const DEFAULT_PROTOCOL: string = 'http';
|
||||
export const DEFAULT_DOMAIN: string = 'localhost';
|
||||
export const TIME_EXPIRATION_24H: string = '24h';
|
||||
export const TIME_EXPIRATION_7D: string = '7d';
|
||||
export const DIST_TAGS = 'dist-tags';
|
||||
export const USERS = 'users';
|
||||
export const DEFAULT_MIN_LIMIT_PASSWORD: number = 3;
|
||||
export const DEFAULT_USER = 'Anonymous';
|
||||
|
||||
export const keyPem = 'verdaccio-key.pem';
|
||||
export const certPem = 'verdaccio-cert.pem';
|
||||
export const csrPem = 'verdaccio-csr.pem';
|
||||
|
||||
export const HEADERS = {
|
||||
JSON: 'application/json',
|
||||
CONTENT_TYPE: 'Content-type',
|
||||
TEXT_PLAIN: 'text/plain',
|
||||
FORWARDED_PROTO: 'X-Forwarded-Proto',
|
||||
ETAG: 'ETag',
|
||||
JSON_CHARSET: 'application/json; charset=utf-8',
|
||||
OCTET_STREAM: 'application/octet-stream; charset=utf-8',
|
||||
TEXT_CHARSET: 'text/plain; charset=utf-8',
|
||||
WWW_AUTH: 'WWW-Authenticate',
|
||||
GZIP: 'gzip',
|
||||
};
|
||||
|
||||
export const CHARACTER_ENCODING = {
|
||||
UTF8: 'utf8',
|
||||
};
|
||||
|
||||
export const HEADER_TYPE = {
|
||||
CONTENT_ENCODING: 'content-encoding',
|
||||
CONTENT_TYPE: 'content-type',
|
||||
CONTENT_LENGTH: 'content-length',
|
||||
ACCEPT_ENCODING: 'accept-encoding',
|
||||
};
|
||||
|
||||
export const ERROR_CODE = {
|
||||
token_required: 'token is required',
|
||||
};
|
||||
|
||||
export const TOKEN_BASIC = 'Basic';
|
||||
export const TOKEN_BEARER = 'Bearer';
|
||||
export const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
|
||||
export const DEFAULT_UPLINK = 'npmjs';
|
||||
|
||||
export const ROLES = {
|
||||
$ALL: '$all',
|
||||
ALL: 'all',
|
||||
$AUTH: '$authenticated',
|
||||
$ANONYMOUS: '$anonymous',
|
||||
DEPRECATED_ALL: '@all',
|
||||
DEPRECATED_AUTH: '@authenticated',
|
||||
DEPRECATED_ANONYMOUS: '@anonymous',
|
||||
};
|
||||
|
||||
export const HTTP_STATUS = {
|
||||
OK: 200,
|
||||
CREATED: 201,
|
||||
MULTIPLE_CHOICES: 300,
|
||||
NOT_MODIFIED: 304,
|
||||
BAD_REQUEST: 400,
|
||||
UNAUTHORIZED: 401,
|
||||
FORBIDDEN: 403,
|
||||
NOT_FOUND: 404,
|
||||
CONFLICT: 409,
|
||||
UNSUPPORTED_MEDIA: 415,
|
||||
BAD_DATA: 422,
|
||||
INTERNAL_ERROR: 500,
|
||||
SERVICE_UNAVAILABLE: 503,
|
||||
LOOP_DETECTED: 508,
|
||||
};
|
||||
|
||||
export const API_MESSAGE = {
|
||||
PKG_CREATED: 'created new package',
|
||||
PKG_CHANGED: 'package changed',
|
||||
PKG_REMOVED: 'package removed',
|
||||
PKG_PUBLISHED: 'package published',
|
||||
TARBALL_UPLOADED: 'tarball uploaded successfully',
|
||||
TARBALL_REMOVED: 'tarball removed',
|
||||
TAG_UPDATED: 'tags updated',
|
||||
TAG_REMOVED: 'tag removed',
|
||||
TAG_ADDED: 'package tagged',
|
||||
LOGGED_OUT: 'Logged out',
|
||||
};
|
||||
|
||||
export const SUPPORT_ERRORS = {
|
||||
PLUGIN_MISSING_INTERFACE: 'the plugin does not provide implementation of the requested feature',
|
||||
TFA_DISABLED: 'the two-factor authentication is not yet supported',
|
||||
};
|
||||
|
||||
export const API_ERROR = {
|
||||
PASSWORD_SHORT: (passLength: number = DEFAULT_MIN_LIMIT_PASSWORD) =>
|
||||
`The provided password is too short. Please pick a password longer than ${passLength} characters.`,
|
||||
MUST_BE_LOGGED: 'You must be logged in to publish packages.',
|
||||
PLUGIN_ERROR: 'bug in the auth plugin system',
|
||||
CONFIG_BAD_FORMAT: 'config file must be an object',
|
||||
BAD_USERNAME_PASSWORD: 'bad username/password, access denied',
|
||||
NO_PACKAGE: 'no such package available',
|
||||
PACKAGE_CANNOT_BE_ADDED: 'this package cannot be added',
|
||||
BAD_DATA: 'bad data',
|
||||
NOT_ALLOWED: 'not allowed to access package',
|
||||
NOT_ALLOWED_PUBLISH: 'not allowed to publish package',
|
||||
INTERNAL_SERVER_ERROR: 'internal server error',
|
||||
UNKNOWN_ERROR: 'unknown error',
|
||||
NOT_PACKAGE_UPLINK: 'package does not exist on uplink',
|
||||
UPLINK_OFFLINE_PUBLISH: 'one of the uplinks is down, refuse to publish',
|
||||
UPLINK_OFFLINE: 'uplink is offline',
|
||||
CONTENT_MISMATCH: 'content length mismatch',
|
||||
NOT_FILE_UPLINK: "file doesn't exist on uplink",
|
||||
MAX_USERS_REACHED: 'maximum amount of users reached',
|
||||
VERSION_NOT_EXIST: "this version doesn't exist",
|
||||
FILE_NOT_FOUND: 'File not found',
|
||||
BAD_STATUS_CODE: 'bad status code',
|
||||
PACKAGE_EXIST: 'this package is already present',
|
||||
BAD_AUTH_HEADER: 'bad authorization header',
|
||||
WEB_DISABLED: 'Web interface is disabled in the config file',
|
||||
DEPRECATED_BASIC_HEADER: 'basic authentication is deprecated, please use JWT instead',
|
||||
BAD_FORMAT_USER_GROUP: 'user groups is different than an array',
|
||||
RESOURCE_UNAVAILABLE: 'resource unavailable',
|
||||
BAD_PACKAGE_DATA: 'bad incoming package data',
|
||||
USERNAME_PASSWORD_REQUIRED: 'username and password is required',
|
||||
USERNAME_ALREADY_REGISTERED: 'username is already registered',
|
||||
};
|
||||
|
||||
export const APP_ERROR = {
|
||||
CONFIG_NOT_VALID: 'CONFIG: it does not look like a valid config file',
|
||||
PROFILE_ERROR: 'profile unexpected error',
|
||||
PASSWORD_VALIDATION: 'not valid password',
|
||||
};
|
||||
|
||||
export const DEFAULT_NO_README = 'ERROR: No README data found!';
|
||||
export const MODULE_NOT_FOUND = 'MODULE_NOT_FOUND';
|
||||
|
||||
export const WEB_TITLE = 'Verdaccio';
|
||||
|
||||
export const PACKAGE_ACCESS = {
|
||||
SCOPE: '@*/*',
|
||||
ALL: '**',
|
||||
};
|
||||
|
||||
export const UPDATE_BANNER = {
|
||||
CHANGELOG_URL: 'https://github.com/verdaccio/verdaccio/releases/tag/',
|
||||
};
|
||||
|
||||
export const STORAGE = {
|
||||
PACKAGE_FILE_NAME: 'package.json',
|
||||
FILE_EXIST_ERROR: 'EEXISTS',
|
||||
NO_SUCH_FILE_ERROR: 'ENOENT',
|
||||
DEFAULT_REVISION: '0-0000000000000000',
|
||||
};
|
||||
73
src/lib/crypto-utils.js
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import { createDecipher, createCipher, createHash, pseudoRandomBytes } from 'crypto';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
import type { JWTSignOptions, RemoteUser } from '@verdaccio/types';
|
||||
|
||||
export const defaultAlgorithm = 'aes192';
|
||||
export const defaultTarballHashAlgorithm = 'sha1';
|
||||
|
||||
export function aesEncrypt(buf: Buffer, secret: string): Buffer {
|
||||
// deprecated
|
||||
// https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password_options
|
||||
const c = createCipher(defaultAlgorithm, secret);
|
||||
const b1 = c.update(buf);
|
||||
const b2 = c.final();
|
||||
return Buffer.concat([b1, b2]);
|
||||
}
|
||||
|
||||
export function aesDecrypt(buf: Buffer, secret: string) {
|
||||
try {
|
||||
// deprecated
|
||||
// https://nodejs.org/api/crypto.html#crypto_crypto_createdecipher_algorithm_password_options
|
||||
const c = createDecipher(defaultAlgorithm, secret);
|
||||
const b1 = c.update(buf);
|
||||
const b2 = c.final();
|
||||
return Buffer.concat([b1, b2]);
|
||||
} catch (_) {
|
||||
return new Buffer(0);
|
||||
}
|
||||
}
|
||||
|
||||
export function createTarballHash() {
|
||||
return createHash(defaultTarballHashAlgorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Express doesn't do ETAGS with requests <= 1024b
|
||||
* we use md5 here, it works well on 1k+ bytes, but sucks with fewer data
|
||||
* could improve performance using crc32 after benchmarks.
|
||||
* @param {Object} data
|
||||
* @return {String}
|
||||
*/
|
||||
export function stringToMD5(data: Buffer | string) {
|
||||
return createHash('md5')
|
||||
.update(data)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
export function generateRandomHexString(length: number = 8) {
|
||||
return pseudoRandomBytes(length).toString('hex');
|
||||
}
|
||||
|
||||
export async function signPayload(payload: RemoteUser, secretOrPrivateKey: string, options: JWTSignOptions): Promise<string> {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return jwt.sign(
|
||||
payload,
|
||||
secretOrPrivateKey,
|
||||
{
|
||||
notBefore: '1', // Make sure the time will not rollback :)
|
||||
...options,
|
||||
},
|
||||
(error, token) => (error ? reject(error) : resolve(token))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function verifyPayload(token: string, secretOrPrivateKey: string) {
|
||||
return jwt.verify(token, secretOrPrivateKey);
|
||||
}
|
||||
5
src/utils/string.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// @flow
|
||||
|
||||
export function spliceURL(...args: Array<string>): string {
|
||||
return Array.from(args).reduce((lastResult, current) => lastResult + current).replace(/([^:])(\/)+(.)/g, `$1/$3`);
|
||||
}
|
||||
47
src/utils/user.js
Normal file
@@ -0,0 +1,47 @@
|
||||
// @flow
|
||||
import {stringToMD5} from '../lib/crypto-utils';
|
||||
import _ from 'lodash';
|
||||
|
||||
// this is a generic avatar
|
||||
// https://www.iconfinder.com/icons/403017/anonym_avatar_default_head_person_unknown_user_icon
|
||||
// license: free commercial usage
|
||||
export const GENERIC_AVATAR: string = `
|
||||
data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1
|
||||
ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYW
|
||||
NrZ3JvdW5kPSJuZXcgLTI3IDI0IDEwMCAxMDAiIGhlaWdodD0iMTAwcHgiIGlkPSJ1bmtub3duIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3
|
||||
g9Ii0yNyAyNCAxMDAgMTAwIiB3aWR0aD0iMTAwcHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy
|
||||
8yMDAwL3N2ZyIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiIHhtbG5zOnhsaW5rPS
|
||||
JodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48Zz48Zz48ZGVmcz48Y2lyY2xlIGN4PSIyMyIgY3k9Ijc0IiBpZD0iY2lyY2xlIi
|
||||
ByPSI1MCIvPjwvZGVmcz48dXNlIGZpbGw9IiNGNUVFRTUiIG92ZXJmbG93PSJ2aXNpYmxlIiB4bGluazpocmVmPSIjY2lyY2xlIi8+PGN
|
||||
saXBQYXRoIGlkPSJjaXJjbGVfMV8iPjx1c2Ugb3ZlcmZsb3c9InZpc2libGUiIHhsaW5rOmhyZWY9IiNjaXJjbGUiLz48L2NsaXBQYXR
|
||||
oPjxnIGNsaXAtcGF0aD0idXJsKCNjaXJjbGVfMV8pIj48ZGVmcz48cGF0aCBkPSJNMzYsOTUuOWMwLDQsNC43LDUuMiw3LjEsNS44Yzc
|
||||
uNiwyLDIyLjgsNS45LDIyLjgsNS45YzMuMiwxLjEsNS43LDMuNSw3LjEsNi42djkuOEgtMjd2LTkuOCAgICAgICBjMS4zLTMuMSwzLjk
|
||||
tNS41LDcuMS02LjZjMCwwLDE1LjItMy45LDIyLjgtNS45YzIuNC0wLjYsNy4xLTEuOCw3LjEtNS44YzAtNCwwLTEwLjksMC0xMC45aDI
|
||||
2QzM2LDg1LDM2LDkxLjksMzYsOTUuOXoiIGlkPSJzaG91bGRlcnMiLz48L2RlZnM+PHVzZSBmaWxsPSIjRTZDMTlDIiBvdmVyZmxvdz0
|
||||
idmlzaWJsZSIgeGxpbms6aHJlZj0iI3Nob3VsZGVycyIvPjxjbGlwUGF0aCBpZD0ic2hvdWxkZXJzXzFfIj48dXNlIG92ZXJmbG93PSJ
|
||||
2aXNpYmxlIiB4bGluazpocmVmPSIjc2hvdWxkZXJzIi8+PC9jbGlwUGF0aD48cGF0aCBjbGlwLXBhdGg9InVybCgjc2hvdWxkZXJzXzF
|
||||
fKSIgZD0iTTIzLjIsMzVjMC4xLDAsMC4xLDAsMC4yLDBjMCwwLDAsMCwwLDAgICAgICBjMy4zLDAsOC4yLDAuMiwxMS40LDJjMy4zLD
|
||||
EuOSw3LjMsNS42LDguNSwxMi4xYzIuNCwxMy43LTIuMSwzNS40LTYuMyw0Mi40Yy00LDYuNy05LjgsOS4yLTEzLjUsOS40YzAsMC0wL
|
||||
jEsMC0wLjEsMCAgICAgIGMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMSwwLTAuMSwwLTAuMiwwYzAsMC0wLjEsMC0wLjEsMGMtMy43LTAuM
|
||||
i05LjUtMi43LTEzLjUtOS40Yy00LjItNy04LjctMjguNy02LjMtNDIuNCAgICAgIGMxLjItNi41LDUuMi0xMC4yLDguNS0xMi4xYzMuM
|
||||
i0xLjgsOC4xLTIsMTEuNC0yYzAsMCwwLDAsMCwwQzIzLjEsMzUsMjMuMSwzNSwyMy4yLDM1TDIzLjIsMzV6IiBmaWxsPSIjRDRCMDhDI
|
||||
iBpZD0iaGVhZC1zaGFkb3ciLz48L2c+PC9nPjxwYXRoIGQ9Ik0yMi42LDQwYzE5LjEsMCwyMC43LDEzLjgsMjAuOCwxNS4xYzEuMSwxM
|
||||
S45LTMsMjguMS02LjgsMzMuN2MtNCw1LjktOS44LDguMS0xMy41LDguMyAgICBjLTAuMiwwLTAuMiwwLTAuMywwYy0wLjEsMC0wLjEs
|
||||
MC0wLjIsMEMxOC44LDk2LjgsMTMsOTQuNiw5LDg4LjdjLTMuOC01LjYtNy45LTIxLjgtNi44LTMzLjhDMi4zLDUzLjcsMy41LDQwLDIyL
|
||||
jYsNDB6IiBmaWxsPSIjRjJDRUE1IiBpZD0iaGVhZCIvPjwvZz48L3N2Zz4=`;
|
||||
|
||||
/**
|
||||
* Generate gravatar url from email address
|
||||
*/
|
||||
export function generateGravatarUrl(email: string = '', online: boolean = true): string {
|
||||
if (online) {
|
||||
if (_.isString(email) && _.size(email) > 0) {
|
||||
email = email.trim().toLocaleLowerCase();
|
||||
const emailMD5 = stringToMD5(email);
|
||||
return `https://www.gravatar.com/avatar/${emailMD5}`;
|
||||
}
|
||||
return GENERIC_AVATAR;
|
||||
} else {
|
||||
return GENERIC_AVATAR;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 380 B After Width: | Height: | Size: 380 B |
|
Before Width: | Height: | Size: 898 B After Width: | Height: | Size: 898 B |
|
Before Width: | Height: | Size: 833 B After Width: | Height: | Size: 833 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 265 B After Width: | Height: | Size: 265 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 464 B After Width: | Height: | Size: 464 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 977 B After Width: | Height: | Size: 977 B |
|
Before Width: | Height: | Size: 851 B After Width: | Height: | Size: 851 B |
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 321 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 344 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 802 B After Width: | Height: | Size: 802 B |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |