diff --git a/.eslintignore b/.eslintignore index 54696b26d..7840d0e66 100644 --- a/.eslintignore +++ b/.eslintignore @@ -12,3 +12,5 @@ Dockerfile *.rpi *.html *.scss +*.png +*.jpg diff --git a/README.md b/README.md index a361dea8d..1589f861c 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@ ![verdaccio gif](https://github.com/verdaccio/verdaccio/blob/master/assets/gif/verdaccio_big_30.gif?raw=true) -# Version 4 +# Version 4 -[Verdaccio](https://verdaccio.org/) is a simple, **zero-config-required local private npm registry**. -No need for an entire database just to get started! Verdaccio comes out of the box with -**its own tiny database**, and the ability to proxy other registries (eg. npmjs.org), -caching the downloaded modules along the way. -For those looking to extend their storage capabilities, Verdaccio -**supports various community-made plugins to hook into services such as Amazon's s3, +[Verdaccio](https://verdaccio.org/) is a simple, **zero-config-required local private npm registry**. +No need for an entire database just to get started! Verdaccio comes out of the box with +**its own tiny database**, and the ability to proxy other registries (eg. npmjs.org), +caching the downloaded modules along the way. +For those looking to extend their storage capabilities, Verdaccio +**supports various community-made plugins to hook into services such as Amazon's s3, Google Cloud Storage** or create your own plugin. @@ -61,7 +61,7 @@ If you want to use a modified version of some 3rd-party package (for example, yo ### E2E Testing -Verdaccio has proved to be a lightweight registry that can be +Verdaccio has proved to be a lightweight registry that can be booted in a couple of seconds, fast enough for any CI. Many open source projects use verdaccio for end to end testing, to mention some examples, **create-react-app**, **mozilla neutrino**, **pnpm**, **storybook**, **alfresco** or **eclipse theia**. You can read more in dedicated article to E2E in our blog. @@ -83,7 +83,7 @@ Now you can navigate to [http://localhost:4873/](http://localhost:4873/) where y > Warning: Verdaccio does not currently support PM2's cluster mode, running it with cluster mode may cause unknown behavior. -## Publishing +## Publishing #### 1. create an user and log in @@ -93,7 +93,7 @@ npm adduser --registry http://localhost:4873 > if you use HTTPS, add an appropriate CA information ("null" means get CA list from OS) -```bash +```bash $ npm set ca null ``` @@ -164,16 +164,15 @@ Verdaccio aims to support all features of a standard npm client that make sense - npm audit - **supported** -## Sponsors +## Special Thanks -#### Open Source License +Thanks to the following companies to help us to achieve our goals providing free open source licenses. -Thanks to the following sponsors to help to achieve our goals providing us free open source licenses. +[![jetbrain](assets/thanks/jetbrains/logo.png)](https://www.jetbrains.com/) +[![crowdin](assets/thanks/crowdin/logo.png)](https://crowdin.com/) +[![balsamiq](assets/thanks/balsamiq/logo.jpg)](https://balsamiq.com/) -[![jetbrain](assets/sponsor/jetbrains/logo.png)](https://www.jetbrains.com/) -[![crowdin](assets/sponsor/crowdin/logo.png)](https://crowdin.com/) - -#### Open Collective +## Open Collective Sponsors Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/verdaccio#sponsor)] @@ -188,7 +187,7 @@ Support this project by becoming a sponsor. Your logo will show up here with a l [![sponsor](https://opencollective.com/verdaccio/sponsor/8/avatar.svg)](https://opencollective.com/verdaccio/sponsor/8/website) [![sponsor](https://opencollective.com/verdaccio/sponsor/9/avatar.svg)](https://opencollective.com/verdaccio/sponsor/9/website) -## Backers +## Open Collective Backers Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/verdaccio#backer)] @@ -218,5 +217,5 @@ If you have any issue you can try the following options, do no desist to ask or Verdaccio is [MIT licensed](https://github.com/verdaccio/verdaccio/blob/master/LICENSE) -The Verdaccio documentation and logos (e.g., .md, .png, .sketch) files in the /docs and /assets folder) is +The Verdaccio documentation and logos (excluding /thanks, e.g., .md, .png, .sketch) files within the /assets folder) is [Creative Commons licensed](https://github.com/verdaccio/verdaccio/blob/master/LICENSE-docs). diff --git a/assets/thanks/balsamiq/logo.jpg b/assets/thanks/balsamiq/logo.jpg new file mode 100644 index 000000000..c9080e590 Binary files /dev/null and b/assets/thanks/balsamiq/logo.jpg differ diff --git a/assets/sponsor/crowdin/logo.png b/assets/thanks/crowdin/logo.png similarity index 100% rename from assets/sponsor/crowdin/logo.png rename to assets/thanks/crowdin/logo.png diff --git a/assets/sponsor/jetbrains/logo.png b/assets/thanks/jetbrains/logo.png similarity index 100% rename from assets/sponsor/jetbrains/logo.png rename to assets/thanks/jetbrains/logo.png diff --git a/package.json b/package.json index 6d18fc824..3d33084e9 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@commitlint/config-conventional": "7.1.2", "@material-ui/core": "3.9.0", "@material-ui/icons": "3.0.2", - "@verdaccio/types": "4.1.3", + "@verdaccio/types": "4.1.4", "autosuggest-highlight": "3.1.1", "babel-core": "7.0.0-bridge.0", "babel-eslint": "10.0.1", diff --git a/src/api/endpoint/api/search.js b/src/api/endpoint/api/search.js index c5872e4a4..0c89d2128 100644 --- a/src/api/endpoint/api/search.js +++ b/src/api/endpoint/api/search.js @@ -61,7 +61,7 @@ export default function(route, auth, storage) { stream.on('data', function each(pkg) { processing_pkgs++; - auth.allow_access(pkg.name, req.remote_user, function(err, allowed) { + auth.allow_access({ packageName: pkg.name }, req.remote_user, function(err, allowed) { processing_pkgs--; if (err) { diff --git a/src/api/middleware.js b/src/api/middleware.js index 9a474ae4e..8e24ad858 100644 --- a/src/api/middleware.js +++ b/src/api/middleware.js @@ -5,7 +5,7 @@ import _ from 'lodash'; -import { validateName as utilValidateName, validatePackage as utilValidatePackage, isObject, ErrorCode } from '../lib/utils'; +import { validateName as utilValidateName, validatePackage as utilValidatePackage, getVersionFromTarball, isObject, ErrorCode } from '../lib/utils'; import { API_ERROR, HEADER_TYPE, HEADERS, HTTP_STATUS, TOKEN_BASIC, TOKEN_BEARER } from '../lib/constants'; import { stringToMD5 } from '../lib/crypto-utils'; import type { $ResponseExtend, $RequestExtend, $NextFunctionVer, IAuth } from '../../types'; @@ -99,12 +99,11 @@ export function allow(auth: IAuth) { return function(action: string) { return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) { req.pause(); - let packageName = req.params.package; - if (req.params.scope) { - packageName = `@${req.params.scope}/${packageName}`; - } + const packageName = req.params.scope ? `@${req.params.scope}/${req.params.package}` : req.params.package; + const packageVersion = req.params.filename ? getVersionFromTarball(req.params.filename) : undefined; + // $FlowFixMe - auth['allow_' + action](packageName, req.remote_user, function(error, allowed) { + auth['allow_' + action]({ packageName, packageVersion }, req.remote_user, function(error, allowed) { req.resume(); if (error) { next(error); diff --git a/src/api/web/endpoint/package.js b/src/api/web/endpoint/package.js index 3f3a333f4..8c5cbc501 100644 --- a/src/api/web/endpoint/package.js +++ b/src/api/web/endpoint/package.js @@ -7,6 +7,7 @@ import _ from 'lodash'; import { addScope, addGravatarSupport, deleteProperties, sortByName, parseReadme } from '../../../lib/utils'; import { allow } from '../../middleware'; import { DIST_TAGS, HEADER_TYPE, HEADERS, HTTP_STATUS } from '../../../lib/constants'; +import logger from '../../../lib/logger'; import type { Router } from 'express'; import type { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler, $SidebarPackage } from '../../../../types'; import type { Config } from '@verdaccio/types'; @@ -17,7 +18,7 @@ function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth, const checkAllow = (name, remoteUser) => new Promise((resolve, reject) => { try { - auth.allow_access(name, remoteUser, (err, allowed) => { + auth.allow_access({ packageName: name }, remoteUser, (err, allowed) => { if (err) { resolve(false); } else { @@ -44,6 +45,7 @@ function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth, permissions.push(pkg); } } catch (err) { + logger.logger.error({ name: pkg.name, error: err }, 'permission process for @{name} has failed: @{error}'); throw err; } } diff --git a/src/api/web/endpoint/search.js b/src/api/web/endpoint/search.js index b40780b56..09cdd4ed5 100644 --- a/src/api/web/endpoint/search.js +++ b/src/api/web/endpoint/search.js @@ -20,7 +20,7 @@ function addSearchWebApi(route: Router, storage: IStorageHandler, auth: IAuth) { uplinksLook: false, callback: (err, entry) => { if (!err && entry) { - auth.allow_access(entry.name, req.remote_user, function(err, allowed) { + auth.allow_access({ packageName: entry.name }, req.remote_user, function(err, allowed) { if (err || !allowed) { return; } diff --git a/src/lib/auth.js b/src/lib/auth.js index bd154f419..7694b0d4e 100644 --- a/src/lib/auth.js +++ b/src/lib/auth.js @@ -23,7 +23,7 @@ import { import { convertPayloadToBase64, ErrorCode } from './utils'; import { getMatchedPackagesSpec } from './config-utils'; -import type { Config, Logger, Callback, IPluginAuth, RemoteUser, JWTSignOptions, Security } from '@verdaccio/types'; +import type { Config, Logger, Callback, IPluginAuth, RemoteUser, JWTSignOptions, Security, AuthPluginPackage } from '@verdaccio/types'; import type { $Response, NextFunction } from 'express'; import type { $RequestExtend, IAuth } from '../../types'; @@ -160,10 +160,10 @@ class Auth implements IAuth { /** * Allow user to access a package. */ - allow_access(packageName: string, user: RemoteUser, callback: Callback) { + allow_access({ packageName, packageVersion }: AuthPluginPackage, user: RemoteUser, callback: Callback) { const plugins = this.plugins.slice(0); // $FlowFixMe - const pkg = Object.assign({ name: packageName }, getMatchedPackagesSpec(packageName, this.config.packages)); + const pkg = Object.assign({ name: packageName, version: packageVersion }, getMatchedPackagesSpec(packageName, this.config.packages)); const self = this; this.logger.trace({ packageName }, 'allow access for @{packageName}'); @@ -193,11 +193,11 @@ class Auth implements IAuth { /** * Allow user to publish a package. */ - allow_publish(packageName: string, user: string, callback: Callback) { + allow_publish({ packageName, packageVersion }: AuthPluginPackage, user: string, callback: Callback) { const plugins = this.plugins.slice(0); const self = this; // $FlowFixMe - const pkg = Object.assign({ name: packageName }, getMatchedPackagesSpec(packageName, this.config.packages)); + const pkg = Object.assign({ name: packageName, version: packageVersion }, getMatchedPackagesSpec(packageName, this.config.packages)); this.logger.trace({ packageName }, 'allow publish for @{packageName}'); (function next() { diff --git a/src/lib/local-storage.js b/src/lib/local-storage.js index 679a2d2f4..276bb4e36 100644 --- a/src/lib/local-storage.js +++ b/src/lib/local-storage.js @@ -642,7 +642,7 @@ class LocalStorage implements IStorage { const { packages } = this.config; if (packages) { - const listPackagesConf = Object.keys(packages || {}); + const listPackagesConf = Object.keys(packages); listPackagesConf.map(pkg => { if (packages[pkg].storage) { diff --git a/src/lib/storage.js b/src/lib/storage.js index 778d10c53..81b50b924 100644 --- a/src/lib/storage.js +++ b/src/lib/storage.js @@ -322,17 +322,20 @@ class Storage implements IStorageHandler { ); lstream.on('error', function(err) { self.logger.error({ err: err }, 'uplink error: @{err.message}'); - cb(), (cb = function() {}); + cb(); + cb = function() {}; }); lstream.on('end', function() { - cb(), (cb = function() {}); + cb(); + cb = function() {}; }); stream.abort = function() { if (lstream.abort) { lstream.abort(); } - cb(), (cb = function() {}); + cb(); + cb = function() {}; }; }, // executed after all series @@ -411,7 +414,7 @@ class Storage implements IStorageHandler { const upLinks = []; const hasToLookIntoUplinks = _.isNil(options.uplinksLook) || options.uplinksLook; - if (!packageInfo || packageInfo === null) { + if (!packageInfo) { exists = false; packageInfo = generatePackageTemplate(name); } diff --git a/src/lib/up-storage.js b/src/lib/up-storage.js index 20b227a5e..d568d3682 100644 --- a/src/lib/up-storage.js +++ b/src/lib/up-storage.js @@ -145,7 +145,7 @@ class ProxyStorage implements IProxy { let error; const responseLength = err ? 0 : body.length; // $FlowFixMe - processBody(err, body); + processBody(); logActivity(); // $FlowFixMe cb(err, res, body); @@ -552,7 +552,7 @@ class ProxyStorage implements IProxy { // https://github.com/rlidwka/sinopia/issues/254 // if (this.proxy === false) { - headers['X-Forwarded-For'] = (req && req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'] + ', ' : '') + req.connection.remoteAddress; + headers['X-Forwarded-For'] = (req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'] + ', ' : '') + req.connection.remoteAddress; } } diff --git a/src/lib/utils.js b/src/lib/utils.js index 561a65dd4..34af6f5ed 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -502,3 +502,13 @@ export function parseReadme(packageName: string, readme: string): string { export function buildToken(type: string, token: string): string { return `${_.capitalize(type)} ${token}`; } + +/** + * return package version from tarball name + * @param {String} name + * @returns {String} + */ +export function getVersionFromTarball(name: string) { + // $FlowFixMe + return /.+-(\d.+)\.tgz/.test(name) ? name.match(/.+-(\d.+)\.tgz/)[1] : undefined; +} diff --git a/src/utils/user.js b/src/utils/user.js index 9369a20f0..e9a4f41c5 100644 --- a/src/utils/user.js +++ b/src/utils/user.js @@ -34,11 +34,10 @@ export const GENERIC_AVATAR: string = ` * Generate gravatar url from email address */ export function generateGravatarUrl(email: string = '', online: boolean = true): string { - let emailCopy = email; if (online) { if (_.isString(email) && _.size(email) > 0) { - emailCopy = email.trim().toLocaleLowerCase(); - const emailMD5 = stringToMD5(emailCopy); + email = email.trim().toLocaleLowerCase(); + const emailMD5 = stringToMD5(email); return `https://www.gravatar.com/avatar/${emailMD5}`; } return GENERIC_AVATAR; diff --git a/test/flow/plugins/middleware/example.middleware.plugin.js b/test/flow/plugins/middleware/example.middleware.plugin.js index 0789a0385..94a667d30 100644 --- a/test/flow/plugins/middleware/example.middleware.plugin.js +++ b/test/flow/plugins/middleware/example.middleware.plugin.js @@ -24,7 +24,7 @@ export default class ExampleMiddlewarePlugin implements IPluginMiddleware { name: 'test' }; auth.authenticate('user', 'password', () => {}); - auth.allow_access('packageName', remoteUser, () => {}); + auth.allow_access({packageName: 'packageName'}, remoteUser, () => {}); auth.add_user('user', 'password', () => {}); auth.aesEncrypt(new Buffer('pass')); // storage diff --git a/test/unit/api/utils.spec.js b/test/unit/api/utils.spec.js index 4e81f1b3d..3d884389b 100644 --- a/test/unit/api/utils.spec.js +++ b/test/unit/api/utils.spec.js @@ -11,7 +11,8 @@ import { combineBaseUrl, getVersion, normalizeDistTags, - getWebProtocol + getWebProtocol, + getVersionFromTarball } from '../../../src/lib/utils'; import { DIST_TAGS } from '../../../src/lib/constants'; import Logger, { setup } from '../../../src/lib/logger'; @@ -259,6 +260,21 @@ describe('Utilities', () => { }).toThrow(expect.hasAssertions()); }); }); + + describe('getVersionFromTarball', () => { + test('should get the right version', () => { + const simpleName = 'test-name-4.2.12.tgz' + const complexName = 'test-5.6.4-beta.2.tgz' + const otherComplexName = 'test-3.5.0-6.tgz' + expect(getVersionFromTarball(simpleName)).toEqual('4.2.12') + expect(getVersionFromTarball(complexName)).toEqual('5.6.4-beta.2') + expect(getVersionFromTarball(otherComplexName)).toEqual('3.5.0-6') + }) + + test('should don\'n fall at incorrect tarball name', () => { + expect(getVersionFromTarball('incorrectName')).toBeUndefined() + }) + }); }); describe('String utilities', () => { diff --git a/yarn.lock b/yarn.lock index b6e7495a0..aa110d803 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1201,10 +1201,10 @@ resolved "https://registry.npmjs.org/@verdaccio/streams/-/streams-1.0.0.tgz#d5d24c6747208728b9fd16b908e3932c3fb1f864" integrity sha512-AjEo5LXk4Yf0SaXSc3y4i1t+wxY552O7WrVJPtnC6H7nUsSrygg/ODCG1RSKelskOq6b5p/LyXnsTkmCFXyjDQ== -"@verdaccio/types@4.1.3": - version "4.1.3" - resolved "https://registry.npmjs.org/@verdaccio/types/-/types-4.1.3.tgz#72881020b5f56865b32c94114fa66e9bf627f0f7" - integrity sha512-DjTlLqrtaLb9ykRYAawRflYVgUyRn1A204xEC39ucaF06kZJbNu0Awm+kBhHsM9CsgM47r3z65vRJDSJNhkzjQ== +"@verdaccio/types@4.1.4": + version "4.1.4" + resolved "https://registry.npmjs.org/@verdaccio/types/-/types-4.1.4.tgz#6144410b9fd63d916aa279378a4946c701a82586" + integrity sha512-0kNIQvMakoHIk1dpgnXVgQ5qwxJGTtMpJkLZKiN5WpLi5yuQtjc+kD/0EWDV4164pKg1KUU6YFLpbcXWxR8zvQ== "@webassemblyjs/ast@1.7.8": version "1.7.8"