mirror of
https://github.com/verdaccio/verdaccio.git
synced 2025-02-21 07:29:37 +01:00
commit
5c6b515712
@ -51,7 +51,7 @@
|
|||||||
"@commitlint/config-conventional": "7.5.0",
|
"@commitlint/config-conventional": "7.5.0",
|
||||||
"@octokit/rest": "16.25.0",
|
"@octokit/rest": "16.25.0",
|
||||||
"@verdaccio/babel-preset": "0.1.0",
|
"@verdaccio/babel-preset": "0.1.0",
|
||||||
"@verdaccio/types": "5.0.0-beta.4",
|
"@verdaccio/types": "5.0.2",
|
||||||
"codecov": "3.3.0",
|
"codecov": "3.3.0",
|
||||||
"cross-env": "5.2.0",
|
"cross-env": "5.2.0",
|
||||||
"eslint": "5.16.0",
|
"eslint": "5.16.0",
|
||||||
|
@ -20,7 +20,7 @@ import web from './web';
|
|||||||
|
|
||||||
import type { $Application } from 'express';
|
import type { $Application } from 'express';
|
||||||
import type { $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler, IAuth } from '../../types';
|
import type { $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler, IAuth } from '../../types';
|
||||||
import type { Config as IConfig, IPluginMiddleware } from '@verdaccio/types';
|
import type { Config as IConfig, IPluginMiddleware, IPluginStorageFilter } from '@verdaccio/types';
|
||||||
import { setup, logger } from '../lib/logger';
|
import { setup, logger } from '../lib/logger';
|
||||||
import { log, final, errorReportingMiddleware } from './middleware';
|
import { log, final, errorReportingMiddleware } from './middleware';
|
||||||
|
|
||||||
@ -107,8 +107,14 @@ const defineAPI = function(config: IConfig, storage: IStorageHandler) {
|
|||||||
export default (async function(configHash: any) {
|
export default (async function(configHash: any) {
|
||||||
setup(configHash.logs);
|
setup(configHash.logs);
|
||||||
const config: IConfig = new AppConfig(_.cloneDeep(configHash));
|
const config: IConfig = new AppConfig(_.cloneDeep(configHash));
|
||||||
|
// register middleware plugins
|
||||||
|
const plugin_params = {
|
||||||
|
config: config,
|
||||||
|
logger: logger,
|
||||||
|
};
|
||||||
|
const filters = loadPlugin(config, config.filters || {}, plugin_params, (plugin: IPluginStorageFilter) => plugin.filter_metadata);
|
||||||
const storage: IStorageHandler = new Storage(config);
|
const storage: IStorageHandler = new Storage(config);
|
||||||
// waits until init calls have been initialized
|
// waits until init calls have been initialized
|
||||||
await storage.init(config);
|
await storage.init(config, filters);
|
||||||
return defineAPI(config, storage);
|
return defineAPI(config, storage);
|
||||||
});
|
});
|
||||||
|
@ -17,7 +17,7 @@ import { setupUpLinks, updateVersionsHiddenUpLink } from './uplink-util';
|
|||||||
import { mergeVersions } from './metadata-utils';
|
import { mergeVersions } from './metadata-utils';
|
||||||
import { ErrorCode, normalizeDistTags, validateMetadata, isObject } from './utils';
|
import { ErrorCode, normalizeDistTags, validateMetadata, isObject } from './utils';
|
||||||
import type { IStorage, IProxy, IStorageHandler, ProxyList, StringValue, IGetPackageOptions, ISyncUplinks } from '../../types';
|
import type { IStorage, IProxy, IStorageHandler, ProxyList, StringValue, IGetPackageOptions, ISyncUplinks } from '../../types';
|
||||||
import type { Versions, Package, Config, MergeTags, Version, DistFile, Callback, Logger } from '@verdaccio/types';
|
import type { Versions, Package, Config, MergeTags, Version, DistFile, Callback, Logger, IPluginStorageFilter } from '@verdaccio/types';
|
||||||
import type { IReadTarball, IUploadTarball } from '@verdaccio/streams';
|
import type { IReadTarball, IUploadTarball } from '@verdaccio/streams';
|
||||||
import { hasProxyTo } from './config-utils';
|
import { hasProxyTo } from './config-utils';
|
||||||
import { logger } from '../lib/logger';
|
import { logger } from '../lib/logger';
|
||||||
@ -27,6 +27,7 @@ class Storage implements IStorageHandler {
|
|||||||
config: Config;
|
config: Config;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
uplinks: ProxyList;
|
uplinks: ProxyList;
|
||||||
|
filters: Array<IPluginStorageFilter>;
|
||||||
|
|
||||||
constructor(config: Config) {
|
constructor(config: Config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
@ -34,7 +35,8 @@ class Storage implements IStorageHandler {
|
|||||||
this.logger = logger.child();
|
this.logger = logger.child();
|
||||||
}
|
}
|
||||||
|
|
||||||
init(config: Config) {
|
init(config: Config, filters: Array<IPluginStorageFilter> = []) {
|
||||||
|
this.filters = filters;
|
||||||
this.localStorage = new LocalStorage(this.config, logger);
|
this.localStorage = new LocalStorage(this.config, logger);
|
||||||
|
|
||||||
return this.localStorage.getSecret(config);
|
return this.localStorage.getSecret(config);
|
||||||
@ -503,11 +505,24 @@ class Storage implements IStorageHandler {
|
|||||||
return callback(null, packageInfo);
|
return callback(null, packageInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.localStorage.updateVersions(name, packageInfo, function(err, packageJsonLocal: Package) {
|
self.localStorage.updateVersions(name, packageInfo, async (err, packageJsonLocal: Package) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
return callback(null, packageJsonLocal, upLinksErrors);
|
// Any error here will cause a 404, like an uplink error. This is likely the right thing to do
|
||||||
|
// as a broken filter is a security risk.
|
||||||
|
const filterErrors = [];
|
||||||
|
// This MUST be done serially and not in parallel as they modify packageJsonLocal
|
||||||
|
for (const filter of self.filters) {
|
||||||
|
try {
|
||||||
|
// These filters can assume it's save to modify packageJsonLocal and return it directly for
|
||||||
|
// performance (i.e. need not be pure)
|
||||||
|
packageJsonLocal = await filter.filter_metadata(packageJsonLocal);
|
||||||
|
} catch (err) {
|
||||||
|
filterErrors.push(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback(null, packageJsonLocal, _.concat(upLinksErrors, filterErrors));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -42,6 +42,12 @@ describe('endpoint unit test', () => {
|
|||||||
file: './test-storage-api-spec/.htpasswd'
|
file: './test-storage-api-spec/.htpasswd'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
filters: {
|
||||||
|
'../partials/plugin/filter': {
|
||||||
|
pkg: 'npm_test',
|
||||||
|
version: '2.0.0'
|
||||||
|
}
|
||||||
|
},
|
||||||
storage: store,
|
storage: store,
|
||||||
self_path: store,
|
self_path: store,
|
||||||
uplinks: {
|
uplinks: {
|
||||||
@ -384,6 +390,37 @@ describe('endpoint unit test', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('be able to filter packages', (done) => {
|
||||||
|
request(app)
|
||||||
|
.get('/npm_test')
|
||||||
|
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||||
|
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||||
|
.expect(HTTP_STATUS.OK)
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
// Filter out 2.0.0
|
||||||
|
expect(Object.keys(res.body.versions)).toEqual(['1.0.0']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not found when a filter fails', (done) => {
|
||||||
|
request(app)
|
||||||
|
// Filter errors look like other uplink errors
|
||||||
|
.get('/trigger-filter-failure')
|
||||||
|
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||||
|
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||||
|
.expect(HTTP_STATUS.NOT_FOUND)
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('should forbid access to remote package', (done) => {
|
test('should forbid access to remote package', (done) => {
|
||||||
|
|
||||||
request(app)
|
request(app)
|
||||||
|
87
test/unit/partials/mock-store/npm_test/package.json
Normal file
87
test/unit/partials/mock-store/npm_test/package.json
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
"_id": "npm_test",
|
||||||
|
"name": "npm_test",
|
||||||
|
"description": "",
|
||||||
|
"dist-tags": {
|
||||||
|
"latest": "1.0.0"
|
||||||
|
},
|
||||||
|
"versions": {
|
||||||
|
"1.0.0": {
|
||||||
|
"name": "npm_test",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"dependencies": {
|
||||||
|
"test": "^1.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"readme": "ERROR: No README data found!",
|
||||||
|
"_id": "npm_test@1.0.0",
|
||||||
|
"_npmVersion": "5.5.1",
|
||||||
|
"_nodeVersion": "9.3.0",
|
||||||
|
"_npmUser": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"integrity": "sha512-tfzM1OFjWwg2d2Wke\/DV6icjeTZUVOZYLkbf8wmONRSAgMovL\/F+zyI24OhTtWyOXd1Kbj2YUMBvLpmpAjv8zg==",
|
||||||
|
"shasum": "3e4e6bd5097b295e520b947c9be3259a9509a673",
|
||||||
|
"tarball": "http:\/\/localhost:4873\/npm_test\/-\/npm_test-1.0.0.tgz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"2.0.0": {
|
||||||
|
"name": "npm_test",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"dependencies": {
|
||||||
|
"test": "^2.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"readme": "ERROR: No README data found!",
|
||||||
|
"_id": "npm_test@2.0.0",
|
||||||
|
"_npmVersion": "5.5.1",
|
||||||
|
"_nodeVersion": "9.3.0",
|
||||||
|
"_npmUser": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"integrity": "sha512-tzzM1OFjWwg2d2Wke\/DV6icjeTZUVOZYLkbf8wmONRSAgMovL\/F+zyI24OhTtWyOXd1Kbj2YUMBvLpmpAjv8zg==",
|
||||||
|
"shasum": "3a4e6bd5097b295e520b947c9be3259a9509a673",
|
||||||
|
"tarball": "http:\/\/localhost:4873\/npm_test\/-\/npm_test-2.0.0.tgz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"readme": "ERROR: No README data found!",
|
||||||
|
"_attachments": {
|
||||||
|
"npm_test-1.0.0.tgz": {
|
||||||
|
"content_type": "application\/octet-stream",
|
||||||
|
"data": "H4sIAAAAAAAAE+2ST08CMRDFOe+nmPTAyawt7ELCVT149ihqmu4gI9I2bUGM4bvbbhGM4eYmxmR\/l6bvtW+mf6xUK\/mMlzaP5Ys3etAxnPNJVcE5PVHV0RPjkairsZiK0YALUU+mMOBdN3KOjQ\/SxVZ+m5PPAsfxn\/BRADAt18hmwDxpY0k+BfSBXSRni86T0ckUJS95Vhv0ypENByeLa0ntjHSDu\/iPvpZajIJWhD66qRwcC6Xlj6KsYm7U94cN2+sfe7KRS34LabuMCaiWBubsxjnjZqANJAO8RUULwmbOYDgE3FEAcSqzwvc345oUd\/\/QKnITlsadzvNKCrVv7+X27ooV++Kv36qnp6enSz4B8bhKUwAIAAA=",
|
||||||
|
"length": 281
|
||||||
|
},
|
||||||
|
"npm_test-2.0.0.tgz": {
|
||||||
|
"content_type": "application\/octet-stream",
|
||||||
|
"data": "H4sIAAAAAAAAE+2ST08CMRDFOe+nmPTAyawt7ELCVT149ihqmu4gI9I2bUGM4bvbbhGM4eYmxmR\/l6bvtW+mf6xUK\/mMlzaP5Ys3etAxnPNJVcE5PVHV0RPjkairsZiK0YALUU+mMOBdN3KOjQ\/SxVZ+m5PPAsfxn\/BRADAt18hmwDxpY0k+BfSBXSRni86T0ckUJS95Vhv0ypENByeLa0ntjHSDu\/iPvpZajIJWhD66qRwcC6Xlj6KsYm7U94cN2+sfe7KRS34LabuMCaiWBubsxjnjZqANJAO8RUULwmbOYDgE3FEAcSqzwvc345oUd\/\/QKnITlsadzvNKCrVv7+X27ooV++Kv36qnp6enSz4B8bhKUwAIAAA=",
|
||||||
|
"length": 281
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
test/unit/partials/plugin/filter.js
Normal file
24
test/unit/partials/plugin/filter.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
class FilterPlugin {
|
||||||
|
constructor(config) {
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
filter_metadata(pkg) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// We use this to test what happens when a filter rejects
|
||||||
|
if(pkg.name === 'trigger-filter-failure') {
|
||||||
|
reject(new Error('Example filter failure'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Example filter that removes a single blocked package
|
||||||
|
if (this._config.pkg === pkg.name) {
|
||||||
|
// In reality, we also want to remove references in attachments and dist-tags, etc. This is just a POC
|
||||||
|
delete pkg.versions[this._config.version];
|
||||||
|
}
|
||||||
|
resolve(pkg);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.default = FilterPlugin;
|
Loading…
Reference in New Issue
Block a user