refactor(test): remove deprecate auth support, fix all test

This commit is contained in:
Juan Picado @jotadeveloper 2018-01-28 02:40:07 +01:00
parent aa3101ef9a
commit 5140b57efb
No known key found for this signature in database
GPG Key ID: 18AC54485952D158
22 changed files with 222 additions and 235 deletions

View File

@ -123,6 +123,7 @@
"stylelint-webpack-plugin": "0.10.1", "stylelint-webpack-plugin": "0.10.1",
"supertest": "^3.0.0", "supertest": "^3.0.0",
"url-loader": "0.6.2", "url-loader": "0.6.2",
"verdaccio-auth-memory": "^0.0.3",
"webpack": "3.10.0", "webpack": "3.10.0",
"webpack-dev-server": "2.11.1", "webpack-dev-server": "2.11.1",
"webpack-merge": "4.1.1" "webpack-merge": "4.1.1"

View File

@ -48,6 +48,7 @@ module.exports = function(route, auth, storage, config) {
route.get('/:package/-/:filename', can('access'), function(req, res) { route.get('/:package/-/:filename', can('access'), function(req, res) {
const stream = storage.get_tarball(req.params.package, req.params.filename); const stream = storage.get_tarball(req.params.package, req.params.filename);
stream.on('content-length', function(content) { stream.on('content-length', function(content) {
res.header('Content-Length', content); res.header('Content-Length', content);
}); });

View File

@ -30,7 +30,7 @@ module.exports = function(route, auth) {
// With npm registering is the same as logging in, // With npm registering is the same as logging in,
// and npm accepts only an 409 error. // and npm accepts only an 409 error.
// So, changing status code here. // So, changing status code here.
return next( createError[409](err.message) ); return next( createError[err.status || 409](err.message) );
} }
return next(err); return next(err);
} }

View File

@ -115,8 +115,12 @@ class Auth {
} else { } else {
// p.add_user() execution // p.add_user() execution
p[n](user, password, function(err, ok) { p[n](user, password, function(err, ok) {
if (err) return cb(err); if (err) {
if (ok) return self.authenticate(user, password, cb); return cb(err);
}
if (ok) {
return self.authenticate(user, password, cb);
}
next(); next();
}); });
} }
@ -142,8 +146,15 @@ class Auth {
} }
p.allow_access(user, pkg, function(err, ok) { p.allow_access(user, pkg, function(err, ok) {
if (err) return callback(err);
if (ok) return callback(null, ok); if (err) {
return callback(err);
}
if (ok) {
return callback(null, ok);
}
next(); // cb(null, false) causes next plugin to roll next(); // cb(null, false) causes next plugin to roll
}); });
})(); })();
@ -204,7 +215,9 @@ class Auth {
req.remote_user = buildAnonymousUser(); req.remote_user = buildAnonymousUser();
let authorization = req.headers.authorization; let authorization = req.headers.authorization;
if (authorization == null) return next(); if (authorization == null) {
return next();
}
let parts = authorization.split(' '); let parts = authorization.split(' ');

View File

@ -1,10 +1,8 @@
import fs from 'fs';
import path from 'path';
export default function(server) { export default function(server) {
describe('npm adduser', () => { describe('npm adduser', () => {
const user = String(Math.random()); const user = String(Math.random());
const pass = String(Math.random()); const pass = String(Math.random());
beforeAll(function() { beforeAll(function() {
return server.auth(user, pass) return server.auth(user, pass)
.status(201) .status(201)
@ -25,22 +23,4 @@ export default function(server) {
.body_error(/maximum amount of users reached/); .body_error(/maximum amount of users reached/);
}); });
}); });
describe('should adduser created with htpasswd', () => {
const user = 'preexisting';
const pass = 'preexisting';
beforeAll(function() {
return fs.appendFileSync(
path.join(__dirname, '../store/test-storage', '.htpasswd'),
'preexisting:$apr1$4YSboUa9$yVKjE7.PxIOuK3M4D7VjX.'
);
});
test('should log in', () => {
return server.auth(user, pass)
.status(201)
.body_ok(/you are authenticated as/);
});
});
} }

View File

@ -16,6 +16,12 @@ function createHash() {
export default function(server, server2) { export default function(server, server2) {
describe('basic test endpoints', () => { describe('basic test endpoints', () => {
beforeAll(function() {
return server.auth('test', 'test')
.status(201)
.body_ok(/'test'/);
});
require('./whoIam')(server); require('./whoIam')(server);
require('./ping')(server); require('./ping')(server);
@ -114,7 +120,14 @@ export default function(server, server2) {
/* test for before() */ /* test for before() */
}); });
test('downloading newly created package', () => { describe('should download a package', () => {
beforeAll(function() {
return server.auth('test', 'test')
.status(201)
.body_ok(/'test'/);
});
test('should download a newly created package from server1', () => {
return server.getPackage('testpkg') return server.getPackage('testpkg')
.status(200) .status(200)
.then(function (body) { .then(function (body) {
@ -127,7 +140,7 @@ export default function(server, server2) {
}); });
}); });
test('downloading package via server2', () => { test('should downloading a package from server2', () => {
return server2.getPackage('testpkg') return server2.getPackage('testpkg')
.status(200) .status(200)
.then(function (body) { .then(function (body) {
@ -139,6 +152,9 @@ export default function(server, server2) {
}); });
}); });
}); });
});
}); });
}); });
}); });

View File

@ -1,12 +1,8 @@
'use strict';
const assert = require('assert');
module.exports = function(server) { module.exports = function(server) {
test('who am I?', () => { test('who am I?', () => {
return server.whoami().then(function (username) { return server.whoami().then(function (username) {
assert.equal(username, 'test'); expect(username).toMatch('test');
}); });
}); });

View File

@ -1,25 +0,0 @@
'use strict';
function Plugin(config) {
let self = Object.create(Plugin.prototype);
self._config = config;
return self;
}
// plugin is expected to be compatible with...
Plugin.prototype.verdaccio_version = '1.1.0';
Plugin.prototype.authenticate = function(user, password, cb) {
if (user !== this._config.accept_user) {
// delegate to next plugin
return cb(null, false);
}
if (password !== this._config.with_password) {
const err = Error('i don\'t like your password');
err.status = 403;
return cb(err);
}
return cb(null, [user]);
};
module.exports = Plugin;

View File

@ -1,30 +0,0 @@
'use strict';
module.exports = Plugin;
function Plugin(config) {
let self = Object.create(Plugin.prototype);
self._config = config;
return self;
}
// plugin is expected to be compatible with...
Plugin.prototype.verdaccio_version = '1.1.0';
Plugin.prototype.allow_access = function(user, pkg, cb) {
if (!pkg.handled_by_auth_plugin) {
// delegate to next plugin
return cb(null, false);
}
if (user.name !== this._config.allow_user) {
let err = Error('i don\'t know anything about you');
err.status = 403;
return cb(err);
}
if (pkg.name !== this._config.to_access) {
let err = Error('you\'re not allowed here');
err.status = 403;
return cb(err);
}
return cb(null, true);
};

View File

@ -25,7 +25,7 @@ export default function (server, server2) {
test('downloading non-existent tarball #2 / srv2', () => { test('downloading non-existent tarball #2 / srv2', () => {
return server2.getTarball('testpkg-gh29', 'blahblah') return server2.getTarball('testpkg-gh29', 'blahblah')
.status(404) .status(404)
.body_error(/no such file/); .body_error(/no such file available/);
}); });
describe('tarball', () => { describe('tarball', () => {

View File

@ -34,21 +34,22 @@ import upLinkCache from './uplink.cache.spec';
import upLinkAuth from './uplink.auth.spec'; import upLinkAuth from './uplink.auth.spec';
describe('functional test verdaccio', function() { describe('functional test verdaccio', function() {
jest.setTimeout(20000);
const EXPRESS_PORT = 55550; const EXPRESS_PORT = 55550;
const SILENCE_LOG = !process.env.VERDACCIO_DEBUG; const SILENCE_LOG = !process.env.VERDACCIO_DEBUG;
const processRunning = []; const processRunning = [];
const config1 = new VerdaccioConfig( const config1 = new VerdaccioConfig(
'./store/test-storage', './store/test-storage',
'./store/config-1.yaml', './store/config-1.yaml',
'http://localhost:55551/'); 'http://localhost:55551/', 55551);
const config2 = new VerdaccioConfig( const config2 = new VerdaccioConfig(
'./store/test-storage2', './store/test-storage2',
'./store/config-2.yaml', './store/config-2.yaml',
'http://localhost:55552/'); 'http://localhost:55552/', 55552);
const config3 = new VerdaccioConfig( const config3 = new VerdaccioConfig(
'./store/test-storage3', './store/test-storage3',
'./store/config-3.yaml', './store/config-3.yaml',
'http://localhost:55553/'); 'http://localhost:55553/', 55553);
const server1: IServerBridge = new Server(config1.domainPath); const server1: IServerBridge = new Server(config1.domainPath);
const server2: IServerBridge = new Server(config2.domainPath); const server2: IServerBridge = new Server(config2.domainPath);
const server3: IServerBridge = new Server(config3.domainPath); const server3: IServerBridge = new Server(config3.domainPath);

View File

@ -32,8 +32,9 @@ export default class VerdaccioProcess implements IServerProcess {
this.childFork = fork(verdaccioRegisterWrap, this.childFork = fork(verdaccioRegisterWrap,
['-c', configPath], ['-c', configPath],
{ {
silent: this.silence silent: this.silence,
} execArgv: [`--inspect=${this.config.port + 5}`]
},
); );
this.childFork.on('message', (msg) => { this.childFork.on('message', (msg) => {
@ -49,16 +50,16 @@ export default class VerdaccioProcess implements IServerProcess {
} }
}); });
this.childFork.on('error', function(err) { this.childFork.on('error', (err) => {
reject(err); reject([err, this]);
}); });
this.childFork.on('disconnect', function(err) { this.childFork.on('disconnect', (err) => {
reject(err); reject([err, this]);
}); });
this.childFork.on('exit', function(err) { this.childFork.on('exit', (err) => {
reject(err); reject([err, this]);
}); });
}); });

View File

@ -4,6 +4,7 @@ export interface IVerdaccioConfig {
storagePath: string; storagePath: string;
configPath: string; configPath: string;
domainPath: string; domainPath: string;
port: number;
} }
export interface IRequestPromise { export interface IRequestPromise {

View File

@ -6,10 +6,12 @@ export class VerdaccioConfig implements IVerdaccioConfig {
storagePath: string; storagePath: string;
configPath: string; configPath: string;
domainPath: string; domainPath: string;
port: number;
constructor(storagePath: string, configPath: string, domainPath: string) { constructor(storagePath: string, configPath: string, domainPath: string, port: number) {
this.storagePath = storagePath; this.storagePath = storagePath;
this.configPath = configPath; this.configPath = configPath;
this.domainPath = domainPath; this.domainPath = domainPath;
this.port = port;
} }
} }

View File

@ -4,15 +4,6 @@ export default function(server) {
const buildToken = (auth) => { const buildToken = (auth) => {
return `Basic ${(new Buffer(auth).toString('base64'))}`; return `Basic ${(new Buffer(auth).toString('base64'))}`;
}; };
let oldAuth;
beforeAll(function() {
oldAuth = server.authstr;
});
afterAll(function() {
server.authstr = oldAuth;
});
/** /**
* Check whether the user is allowed to fetch packages * Check whether the user is allowed to fetch packages
@ -72,7 +63,7 @@ export default function(server) {
checkPublish(undefined, testAccessOnly, false); checkPublish(undefined, testAccessOnly, false);
checkPublish(badCredentials, testAccessOnly, false); checkPublish(badCredentials, testAccessOnly, false);
// // all are allowed to publish // all are allowed to publish
checkAccess(validCredentials, testPublishOnly, false); checkAccess(validCredentials, testPublishOnly, false);
checkAccess(undefined, testPublishOnly, false); checkAccess(undefined, testPublishOnly, false);
checkAccess(badCredentials, testPublishOnly, false); checkAccess(badCredentials, testPublishOnly, false);

View File

@ -1,9 +1,9 @@
import assert from 'assert'; import assert from 'assert';
export default function(server2){ export default function(server2){
const requestAuthFail = (user, pass, message) => { const requestAuthFail = (user, pass, message, statusCode) => {
return server2.auth(user, pass) return server2.auth(user, pass)
.status(409) .status(statusCode)
.body_error(message) .body_error(message)
.then(function() { .then(function() {
return server2.whoami(); return server2.whoami();
@ -12,9 +12,9 @@ export default function(server2){
assert.equal(username, null); assert.equal(username, null);
}); });
}; };
const requestAuthOk = (user, pass, regex) => { const requestAuthOk = (user, pass, regex, statusCode) => {
return server2.auth(user, pass) return server2.auth(user, pass)
.status(201) .status(statusCode)
.body_ok(regex) .body_ok(regex)
.then(function() { .then(function() {
return server2.whoami(); return server2.whoami();
@ -26,39 +26,22 @@ export default function(server2){
}; };
describe('test default authentication', () => { describe('test default authentication', () => {
let authstr;
beforeAll(function() {
authstr = server2.authstr;
});
test('should not authenticate with wrong password', () => { test('should not authenticate with wrong password', () => {
return requestAuthFail('authtest', 'wrongpass', 'this user already exists'); return requestAuthFail('authtest', 'wrongpass1', 'i don\'t like your password', 401);
});
test('should be wrong password handled by plugin', () => {
return requestAuthFail('authtest2', 'wrongpass', 'registration is disabled');
}); });
test('should right password handled by plugin', () => { test('should right password handled by plugin', () => {
return requestAuthOk('authtest2', 'blahblah', /'authtest2'/); return requestAuthOk('authtest2', 'blahblah', /'authtest2'/, 201);
}); });
afterAll(function() {
server2.authstr = authstr;
});
}); });
describe('test access authorization', () => { describe('test access authorization', () => {
let authstr;
beforeAll(function() {
authstr = server2.authstr;
});
describe('access with user authtest', () => { describe('access with user authtest', () => {
beforeAll(function() { beforeAll(function() {
return server2.auth('authtest', 'test') return server2.auth('authtest', 'blahblah')
.status(201) .status(201)
.body_ok(/'authtest'/); .body_ok(/'authtest'/);
}); });
@ -69,10 +52,10 @@ export default function(server2){
.body_error('no such package available'); .body_error('no such package available');
}); });
test('access test-auth-deny', () => { test('access test-deny', () => {
return server2.getPackage('test-auth-deny') return server2.getPackage('test-deny')
.status(403) .status(403)
.body_error('you\'re not allowed here'); .body_error('not allowed to access package');
}); });
test('access test-auth-regular', () => { test('access test-auth-regular', () => {
@ -92,13 +75,13 @@ export default function(server2){
test('access test-auth-allow', () => { test('access test-auth-allow', () => {
return server2.getPackage('test-auth-allow') return server2.getPackage('test-auth-allow')
.status(403) .status(403)
.body_error('i don\'t know anything about you'); .body_error('not allowed to access package');
}); });
test('access test-auth-deny', () => { test('access test-auth-deny', () => {
return server2.getPackage('test-auth-deny') return server2.getPackage('test-auth-deny')
.status(403) .status(403)
.body_error('i don\'t know anything about you'); .body_error('not allowed to access package');
}); });
test('access test-auth-regular', () => { test('access test-auth-regular', () => {
@ -108,8 +91,5 @@ export default function(server2){
}); });
}); });
afterAll(function() {
server2.authstr = authstr;
});
}); });
} }

View File

@ -18,8 +18,8 @@ export default function (server, server2) {
test('add pkg', () => {}); test('add pkg', () => {});
describe('should check readme file', () => { describe('should check readme file', () => {
const matchReadme = (server) => { const matchReadme = (serverRef) => {
return server.request({ return serverRef.request({
uri: '/-/verdaccio/package/readme/readme-test' uri: '/-/verdaccio/package/readme/readme-test'
}).status(200).then(function(body) { }).status(200).then(function(body) {
assert.equal(body, '<p>this is a readme</p>\n'); assert.equal(body, '<p>this is a readme</p>\n');

View File

@ -1,14 +1,18 @@
storage: ./test-storage storage: ./test-storage
users: max_users: 2
test:
password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
users_file: ./test-storage/.htpasswd
max_users: 1
web: web:
enable: true enable: true
title: verdaccio-server-1
auth:
auth-memory:
users:
test:
name: test
password: test
uplinks: uplinks:
express: express:
@ -26,36 +30,70 @@ logs:
packages: packages:
'@test/*': '@test/*':
allow_access: all allow_access: $all
allow_publish: all allow_publish: $all
proxy: server2 proxy: server2
'testfwd*': 'testfwd*':
allow_access: all allow_access: $all
allow_publish: all allow_publish: $all
proxy_access: server2 proxy_access: server2
proxy_publish: server2 proxy_publish: server2
'testloop': 'testloop':
allow_access: all allow_access: $all
allow_publish: all allow_publish: $all
proxy_access: server2 proxy_access: server2
proxy_publish: server2 proxy_publish: server2
'testexp*': 'testexp':
allow_access: all allow_access: $anonymous
allow_publish: all
# used by tags.spec.js
'testexp_tag*':
allow_access: $all
allow_publish: $all
proxy_access: express
# used by gzip.spec.js
'testexp_gzi*':
allow_access: $all
allow_publish: $all
proxy_access: express
# used by gh29.js
'testpkg-gh29':
allow_access: $all
allow_publish: $all
proxy_access: express
# used by preserve_tags_spec.js
'testpkg-preserve':
allow_access: $all
allow_publish: $all
proxy_access: express
# used by racycrash.js
'testexp-racycrash':
allow_access: $all
allow_publish: $all
proxy_access: express
# used by incomplete.js
'testexp-incomplete':
allow_access: $all
allow_publish: $all
proxy_access: express proxy_access: express
'test-nullstorage*': 'test-nullstorage*':
allow_access: all allow_access: $all
allow_publish: all allow_publish: $all
proxy_access: server2 proxy_access: server2
storage: false storage: false
'baduplink': 'baduplink':
allow_access: all allow_access: $all
allow_publish: all allow_publish: $all
proxy_access: baduplink proxy_access: baduplink
'test-access-only': 'test-access-only':
@ -79,12 +117,8 @@ packages:
storage: false storage: false
'*': '*':
allow_access: test undefined allow_access: test $anonymous
allow_publish: test undefined allow_publish: test $anonymous
# this should not matter
testpkg:
allow_access: none
listen: 55551 listen: 55551

View File

@ -1,11 +1,5 @@
storage: ./test-storage2 storage: ./test-storage2
users:
test:
password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
authtest:
password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
uplinks: uplinks:
server1: server1:
url: http://localhost:55551/ url: http://localhost:55551/
@ -13,62 +7,84 @@ uplinks:
web: web:
enable: true enable: true
title: verdaccio-server-2
middlewares: middlewares:
../fixtures/plugins/middlewares: ../fixtures/plugins/middlewares:
message: this is a custom route message: this is a custom route
auth: max_users: 3
../fixtures/plugins/authenticate:
accept_user: authtest2
with_password: blahblah
../fixtures/plugins/authorize: auth:
allow_user: authtest auth-memory:
to_access: test-auth-allow users:
test:
name: test
password: test
authtest2:
name: authtest2
password: blahblah
authtest:
name: authtest
password: blahblah
logs: logs:
- {type: stdout, format: pretty, level: trace} - {type: stdout, format: pretty, level: trace}
packages: packages:
'@test/*': '@test/*':
allow_access: all allow_access: $all
allow_publish: all allow_publish: $all
proxy: server1 proxy: server1
'testfwd': 'testfwd':
allow_access: all allow_access: $all
allow_publish: all allow_publish: $all
'testloop': 'testloop':
allow_access: all allow_access: $all
allow_publish: all allow_publish: $all
proxy_access: server1 proxy_access: server1
proxy_publish: server1 proxy_publish: server1
'testpkg*': # used by gh29.js
allow_access: test anonymous 'testpkg-gh29':
allow_publish: test anonymous allow_access: test $anonymous
allow_publish: test $anonymous
proxy_access: server1
# used by preserve_tags_spec.js
'testpkg-preserve':
allow_access: test $anonymous
allow_publish: test $anonymous
proxy_access: server1
'testpkg':
allow_access: test $anonymous
allow_publish: test $anonymous
proxy_access: server1 proxy_access: server1
'readme-*': 'readme-*':
allow_access: test anonymous allow_access: test $anonymous
allow_publish: test anonymous allow_publish: test $anonymous
proxy_access: server1 proxy_access: server1
'test-nullstorage*': 'test-nullstorage*':
allow_access: all allow_access: $all
allow_publish: all allow_publish: $all
'test-auth-regular': 'test-auth-regular':
allow_access: $authenticated allow_access: $authenticated
'test-auth-*': 'test-auth-*':
handled_by_auth_plugin: true allow_access: authtest
'test-deny':
allow_access: authtest2
'*': '*':
allow_access: test anonymous allow_access: test $anonymous
allow_publish: test anonymous allow_publish: test $anonymous
listen: 55552 listen: 55552

View File

@ -1,11 +1,8 @@
storage: ./test-storage3 storage: ./test-storage3
users:
test:
password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
web: web:
enable: true enable: true
title: verdaccio-server-3
uplinks: uplinks:
server1: server1:
@ -14,6 +11,13 @@ uplinks:
url: http://localhost:55552/ url: http://localhost:55552/
cache: false cache: false
auth:
auth-memory:
users:
test:
name: test
password: test
logs: logs:
- {type: stdout, format: pretty, level: trace} - {type: stdout, format: pretty, level: trace}

View File

@ -16,7 +16,6 @@ describe('endpoint unit test', () => {
let storage; let storage;
let auth; let auth;
let app; let app;
jest.setTimeout(500000);
beforeAll(function(done) { beforeAll(function(done) {
const store = path.join(__dirname, './partials/store/test-storage'); const store = path.join(__dirname, './partials/store/test-storage');
@ -131,13 +130,14 @@ describe('endpoint unit test', () => {
.put('/-/user/org.couchdb.user:jota') .put('/-/user/org.couchdb.user:jota')
.send(credentialsShort) .send(credentialsShort)
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(409) .expect(403)
.end(function(err, res) { .end(function(err, res) {
if (err) { if (err) {
return done(err); return done(err);
} }
expect(res.body.error).toBeDefined(); expect(res.body.error).toBeDefined();
//FIXME: message is not 100% accurate
expect(res.body.error).toMatch(/this user already exists/); expect(res.body.error).toMatch(/this user already exists/);
done(); done();
}); });
@ -149,13 +149,14 @@ describe('endpoint unit test', () => {
.put('/-/user/org.couchdb.user:jota') .put('/-/user/org.couchdb.user:jota')
.send(credentials) .send(credentials)
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(409) .expect(403)
.end(function(err, res) { .end(function(err, res) {
if (err) { if (err) {
return done(err); return done(err);
} }
expect(res.body.error).toBeDefined(); expect(res.body.error).toBeDefined();
//FIXME: message is not 100% accurate
expect(res.body.error).toMatch(/this user already exists/); expect(res.body.error).toMatch(/this user already exists/);
done(); done();
}); });
@ -173,7 +174,7 @@ describe('endpoint unit test', () => {
//TODO: this should return 401 and will fail when issue //TODO: this should return 401 and will fail when issue
// https://github.com/verdaccio/verdaccio-htpasswd/issues/5 // https://github.com/verdaccio/verdaccio-htpasswd/issues/5
// is being fixed // is being fixed
.expect(409) .expect(403)
.end(function(err, res) { .end(function(err, res) {
if (err) { if (err) {
return done(err); return done(err);

View File

@ -8688,6 +8688,10 @@ vendors@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22"
verdaccio-auth-memory@^0.0.3:
version "0.0.3"
resolved "https://registry.npmjs.org/verdaccio-auth-memory/-/verdaccio-auth-memory-0.0.3.tgz#6d175a8959c04a46f1441fa09370ede3517acf5b"
verror@1.10.0: verror@1.10.0:
version "1.10.0" version "1.10.0"
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"