mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-11-08 23:25:51 +01:00
feat: relocate core packages (#1906)
* chore: clean lint warnings * refactor: move core packages
This commit is contained in:
parent
33f8b00080
commit
3838d3d212
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@ -4,6 +4,7 @@
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
@ -21,12 +22,11 @@
|
||||
"name": "Unit Tests",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/node_modules/jest-cli/bin/jest.js",
|
||||
"program": "${workspaceRoot}/node_modules/bin/jest",
|
||||
"stopOnEntry": false,
|
||||
"args": [
|
||||
"--debug=true" ],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "pre-test",
|
||||
"runtimeExecutable": null,
|
||||
"runtimeArgs": [
|
||||
"--nolazy"
|
||||
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -2,11 +2,11 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/.nyc_output": true,
|
||||
"**/build": true,
|
||||
"**/build": false,
|
||||
"**/coverage": true,
|
||||
".idea": true,
|
||||
"storage_default_storage": true,
|
||||
".yarn": true
|
||||
},
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,21 @@
|
||||
const setup = jest.fn();
|
||||
const debug = require('debug')('verdaccio:test');
|
||||
|
||||
const setup = debug;
|
||||
const logger = {
|
||||
child: jest.fn(() => ({
|
||||
debug: jest.fn(),
|
||||
trace: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
fatal: jest.fn(),
|
||||
debug,
|
||||
trace: debug,
|
||||
warn: debug,
|
||||
info: debug,
|
||||
error: debug,
|
||||
fatal: debug,
|
||||
})),
|
||||
debug: jest.fn(),
|
||||
trace: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
fatal: jest.fn(),
|
||||
debug: debug,
|
||||
trace: debug,
|
||||
warn: debug,
|
||||
info: debug,
|
||||
error: debug,
|
||||
fatal: debug,
|
||||
};
|
||||
|
||||
export { setup, logger };
|
||||
|
@ -31,6 +31,7 @@
|
||||
"@verdaccio/middleware": "5.0.0-alpha.0",
|
||||
"@verdaccio/store": "5.0.0-alpha.0",
|
||||
"@verdaccio/utils": "5.0.0-alpha.0",
|
||||
"debug": "^4.1.1",
|
||||
"cookies": "0.8.0",
|
||||
"express": "4.17.1",
|
||||
"lodash": "4.17.15",
|
||||
@ -42,7 +43,7 @@
|
||||
"@verdaccio/dev-types": "5.0.0-alpha.0",
|
||||
"@verdaccio/types": "9.5.0",
|
||||
"body-parser": "1.19.0",
|
||||
"express": "4.17.1"
|
||||
"supertest": "next"
|
||||
},
|
||||
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { Router } from 'express';
|
||||
import buildDebug from 'debug';
|
||||
|
||||
import { allow } from '@verdaccio/middleware';
|
||||
import { convertDistRemoteToLocalTarballUrls, getVersion, ErrorCode } from '@verdaccio/utils';
|
||||
@ -7,6 +8,8 @@ import { HEADERS, DIST_TAGS, API_ERROR } from '@verdaccio/dev-commons';
|
||||
import { Config, Package } from '@verdaccio/types';
|
||||
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
|
||||
|
||||
const debug = buildDebug('verdaccio:api:package');
|
||||
|
||||
const downloadStream = (packageName: string, filename: string, storage: any, req: $RequestExtend, res: $ResponseExtend): void => {
|
||||
const stream = storage.getTarball(packageName, filename);
|
||||
|
||||
@ -26,36 +29,52 @@ export default function (route: Router, auth: IAuth, storage: IStorageHandler, c
|
||||
const can = allow(auth);
|
||||
// TODO: anonymous user?
|
||||
route.get('/:package/:version?', can('access'), function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
debug('init package by version');
|
||||
const name = req.params.package;
|
||||
const getPackageMetaCallback = function (err, metadata: Package): void {
|
||||
if (err) {
|
||||
debug('error on fetch metadata for %o with error %o', name, err.message);
|
||||
return next(err);
|
||||
}
|
||||
metadata = convertDistRemoteToLocalTarballUrls(metadata, req, config.url_prefix);
|
||||
debug('convert dist remote to local with prefix %o', config?.url_prefix);
|
||||
metadata = convertDistRemoteToLocalTarballUrls(metadata, req, config?.url_prefix);
|
||||
|
||||
let queryVersion = req.params.version;
|
||||
debug('query by param version: %o', queryVersion);
|
||||
if (_.isNil(queryVersion)) {
|
||||
debug('param %o version found', queryVersion);
|
||||
return next(metadata);
|
||||
}
|
||||
|
||||
let version = getVersion(metadata, queryVersion);
|
||||
debug('query by latest version %o and result %o', queryVersion, version);
|
||||
if (_.isNil(version) === false) {
|
||||
debug('latest version found %o', version);
|
||||
return next(version);
|
||||
}
|
||||
|
||||
if (_.isNil(metadata[DIST_TAGS]) === false) {
|
||||
if (_.isNil(metadata[DIST_TAGS][queryVersion]) === false) {
|
||||
queryVersion = metadata[DIST_TAGS][queryVersion];
|
||||
debug('dist-tag version found %o', queryVersion);
|
||||
version = getVersion(metadata, queryVersion);
|
||||
if (_.isNil(version) === false) {
|
||||
debug('dist-tag found %o', version);
|
||||
return next(version);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug('dist tag not detected');
|
||||
}
|
||||
return next(ErrorCode.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${req.params.version}`));
|
||||
|
||||
debug('package version not found %o', queryVersion);
|
||||
return next(ErrorCode.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${queryVersion}`));
|
||||
};
|
||||
|
||||
debug('get package name %o', name);
|
||||
debug('uplinks look up enabled');
|
||||
storage.getPackage({
|
||||
name: req.params.package,
|
||||
name,
|
||||
uplinksLook: true,
|
||||
req,
|
||||
callback: getPackageMetaCallback,
|
||||
|
@ -2,6 +2,7 @@ import Path from 'path';
|
||||
import _ from 'lodash';
|
||||
import mime from 'mime';
|
||||
import { Router } from 'express';
|
||||
import buildDebug from 'debug';
|
||||
|
||||
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
|
||||
import { API_MESSAGE, HEADERS, DIST_TAGS, API_ERROR, HTTP_STATUS } from '@verdaccio/dev-commons';
|
||||
@ -14,6 +15,8 @@ import { logger } from '@verdaccio/logger';
|
||||
import star from './star';
|
||||
import { isPublishablePackage } from './utils';
|
||||
|
||||
const debug = buildDebug('verdaccio:api:publish');
|
||||
|
||||
export default function publish(router: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
|
||||
const can = allow(auth);
|
||||
|
||||
@ -106,7 +109,7 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
||||
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
const packageName = req.params.package;
|
||||
|
||||
logger.debug({ packageName }, `publishing or updating a new version for @{packageName}`);
|
||||
debug('publishing or updating a new version for %o', packageName);
|
||||
|
||||
/**
|
||||
* Write tarball of stream data from package clients.
|
||||
@ -114,14 +117,16 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
||||
const createTarball = function (filename: string, data, cb: Callback): void {
|
||||
const stream = storage.addTarball(packageName, filename);
|
||||
stream.on('error', function (err) {
|
||||
debug('error on stream a tarball %o for %o with error %o', filename, packageName, err.message);
|
||||
cb(err);
|
||||
});
|
||||
stream.on('success', function () {
|
||||
debug('success on stream a tarball %o for %o', filename, packageName);
|
||||
cb();
|
||||
});
|
||||
// this is dumb and memory-consuming, but what choices do we have?
|
||||
// flow: we need first refactor this file before decides which type use here
|
||||
stream.end(new Buffer(data.data, 'base64'));
|
||||
stream.end(Buffer.from(data.data, 'base64'));
|
||||
stream.done();
|
||||
};
|
||||
|
||||
@ -129,6 +134,7 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
||||
* Add new package version in storage
|
||||
*/
|
||||
const createVersion = function (version: string, metadata: Version, cb: Callback): void {
|
||||
debug('add a new package version %o to storage %o', version, metadata);
|
||||
storage.addVersion(packageName, version, metadata, null, cb);
|
||||
};
|
||||
|
||||
@ -136,20 +142,26 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
||||
* Add new tags in storage
|
||||
*/
|
||||
const addTags = function (tags: MergeTags, cb: Callback): void {
|
||||
debug('add new tag %o to storage', packageName);
|
||||
storage.mergeTags(packageName, tags, cb);
|
||||
};
|
||||
|
||||
const afterChange = function (error, okMessage, metadata: Package): void {
|
||||
const metadataCopy: Package = { ...metadata };
|
||||
debug('after change metadata %o', metadata);
|
||||
|
||||
const { _attachments, versions } = metadataCopy;
|
||||
|
||||
// `npm star` wouldn't have attachments
|
||||
// and `npm deprecate` would have attachments as a empty object, i.e {}
|
||||
if (_.isNil(_attachments) || JSON.stringify(_attachments) === '{}') {
|
||||
debug('no attachments detected');
|
||||
if (error) {
|
||||
debug('no_attachments: after change error with %o', error.message);
|
||||
return next(error);
|
||||
}
|
||||
|
||||
debug('no_attachments: after change success');
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
return next({
|
||||
ok: okMessage,
|
||||
@ -165,11 +177,13 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
||||
if (isInvalidBodyFormat) {
|
||||
// npm is doing something strange again
|
||||
// if this happens in normal circumstances, report it as a bug
|
||||
debug('invalid body format');
|
||||
logger.info({ packageName }, `wrong package format on publish a package @{packageName}`);
|
||||
return next(ErrorCode.getBadRequest(API_ERROR.UNSUPORTED_REGISTRY_CALL));
|
||||
}
|
||||
|
||||
if (error && error.status !== HTTP_STATUS.CONFLICT) {
|
||||
debug('error on change or update a package with %o', error.message);
|
||||
return next(error);
|
||||
}
|
||||
|
||||
@ -177,7 +191,9 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
||||
const [firstAttachmentKey] = Object.keys(_attachments);
|
||||
|
||||
createTarball(Path.basename(firstAttachmentKey), _attachments[firstAttachmentKey], function (error) {
|
||||
debug('creating a tarball %o', firstAttachmentKey);
|
||||
if (error) {
|
||||
debug('error on create a tarball for %o with error %o', packageName, error.message);
|
||||
return next(error);
|
||||
}
|
||||
|
||||
@ -187,20 +203,24 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
||||
|
||||
createVersion(versionToPublish, versions[versionToPublish], function (error) {
|
||||
if (error) {
|
||||
debug('error on create a version for %o with error %o', packageName, error.message);
|
||||
return next(error);
|
||||
}
|
||||
|
||||
addTags(metadataCopy[DIST_TAGS], async function (error) {
|
||||
if (error) {
|
||||
debug('error on create a tag for %o with error %o', packageName, error.message);
|
||||
return next(error);
|
||||
}
|
||||
|
||||
try {
|
||||
await notify(metadataCopy, config, req.remote_user, `${metadataCopy.name}@${versionToPublish}`);
|
||||
} catch (error) {
|
||||
debug('error on notify add a new tag %o', `${metadataCopy.name}@${versionToPublish}`);
|
||||
logger.error({ error }, 'notify batch service has failed: @{error}');
|
||||
}
|
||||
|
||||
debug('add a tag succesfully for %o', `${metadataCopy.name}@${versionToPublish}`);
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
return next({ ok: okMessage, success: true });
|
||||
});
|
||||
@ -209,33 +229,40 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
||||
};
|
||||
|
||||
if (isPublishablePackage(req.body) === false && isObject(req.body.users)) {
|
||||
debug('starting star a package');
|
||||
return starApi(req, res, next);
|
||||
}
|
||||
|
||||
try {
|
||||
debug('pre validation metadata to publish %o', req.body);
|
||||
const metadata = validateMetadata(req.body, packageName);
|
||||
debug('post validation metadata to publish %o', metadata);
|
||||
// treating deprecation as updating a package
|
||||
if (req.params._rev || isRelatedToDeprecation(req.body)) {
|
||||
logger.debug({ packageName }, `updating a new version for @{packageName}`);
|
||||
debug('updating a new version for %o', packageName);
|
||||
// we check unpublish permissions, an update is basically remove versions
|
||||
const remote = req.remote_user;
|
||||
auth.allow_unpublish({ packageName }, remote, (error) => {
|
||||
debug('allowed to unpublish a package %o', packageName);
|
||||
if (error) {
|
||||
logger.debug({ packageName }, `not allowed to unpublish a version for @{packageName}`);
|
||||
debug('not allowed to unpublish a version for %o', packageName);
|
||||
return next(error);
|
||||
}
|
||||
|
||||
debug('update a package');
|
||||
storage.changePackage(packageName, metadata, req.params.revision, function (error) {
|
||||
afterChange(error, API_MESSAGE.PKG_CHANGED, metadata);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
logger.debug({ packageName }, `adding a new version for @{packageName}`);
|
||||
debug('adding a new version for the package %o', packageName);
|
||||
storage.addPackage(packageName, metadata, function (error) {
|
||||
debug('package metadata updated %o', metadata);
|
||||
afterChange(error, API_MESSAGE.PKG_CREATED, metadata);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
debug('error on publish, bad package format %o', packageName);
|
||||
logger.error({ packageName }, 'error on publish, bad package data for @{packageName}');
|
||||
return next(ErrorCode.getBadData(API_ERROR.BAD_PACKAGE_DATA));
|
||||
}
|
||||
@ -287,12 +314,15 @@ export function addVersion(storage: IStorageHandler) {
|
||||
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
const { version, tag } = req.params;
|
||||
const packageName = req.params.package;
|
||||
debug('add a new version %o and tag %o for %o', version, tag, packageName);
|
||||
|
||||
storage.addVersion(packageName, version, req.body, tag, function (error) {
|
||||
if (error) {
|
||||
debug('error on add new version');
|
||||
return next(error);
|
||||
}
|
||||
|
||||
debug('success on add new version');
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
return next({
|
||||
ok: API_MESSAGE.PKG_PUBLISHED,
|
||||
|
@ -2,9 +2,12 @@ import { USERS, HTTP_STATUS } from '@verdaccio/dev-commons';
|
||||
import { Response } from 'express';
|
||||
import _ from 'lodash';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
import buildDebug from 'debug';
|
||||
|
||||
import { $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
|
||||
|
||||
const debug = buildDebug('verdaccio:api:publish:star');
|
||||
|
||||
export default function (storage: IStorageHandler): (req: $RequestExtend, res: Response, next: $NextFunctionVer) => void {
|
||||
const validateInputs = (newUsers, localUsers, username, isStar): boolean => {
|
||||
const isExistlocalUsers = _.isNil(localUsers[username]) === false;
|
||||
@ -20,22 +23,27 @@ export default function (storage: IStorageHandler): (req: $RequestExtend, res: R
|
||||
|
||||
return (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
|
||||
const name = req.params.package;
|
||||
logger.debug({ name }, 'starring a package for @{name}');
|
||||
debug('starring a package for %o', name);
|
||||
const afterChangePackage = function (err?: Error) {
|
||||
if (err) {
|
||||
debug('error on update package for %o', name);
|
||||
return next(err);
|
||||
}
|
||||
|
||||
debug('succes update package for %o', name);
|
||||
res.status(HTTP_STATUS.OK);
|
||||
next({
|
||||
success: true,
|
||||
});
|
||||
};
|
||||
|
||||
debug('get package info package for %o', name);
|
||||
storage.getPackage({
|
||||
name,
|
||||
req,
|
||||
callback: function (err, info) {
|
||||
if (err) {
|
||||
debug('error on get package info package for %o', name);
|
||||
return next(err);
|
||||
}
|
||||
const newStarUser = req.body[USERS];
|
||||
@ -43,6 +51,7 @@ export default function (storage: IStorageHandler): (req: $RequestExtend, res: R
|
||||
const localStarUsers = info[USERS];
|
||||
// Check is star or unstar
|
||||
const isStar = Object.keys(newStarUser).includes(remoteUsername);
|
||||
debug('is start? %o', isStar);
|
||||
if (_.isNil(localStarUsers) === false && validateInputs(newStarUser, localStarUsers, remoteUsername, isStar)) {
|
||||
return afterChangePackage();
|
||||
}
|
||||
@ -61,6 +70,7 @@ export default function (storage: IStorageHandler): (req: $RequestExtend, res: R
|
||||
},
|
||||
{}
|
||||
);
|
||||
debug('update package for %o', name);
|
||||
storage.changePackage(name, { ...info, users }, req.body._rev, function (err) {
|
||||
afterChangePackage(err);
|
||||
});
|
||||
|
@ -1,13 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import Cookies from 'cookies';
|
||||
import { Response, Router } from 'express';
|
||||
|
||||
import { createRemoteUser, createSessionToken, getAuthenticatedMessage, validatePassword, ErrorCode } from '@verdaccio/utils';
|
||||
import { createRemoteUser, getAuthenticatedMessage, validatePassword, ErrorCode } from '@verdaccio/utils';
|
||||
import { getApiToken } from '@verdaccio/auth';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
|
||||
import { Config, RemoteUser } from '@verdaccio/types';
|
||||
import { $RequestExtend, $ResponseExtend, $NextFunctionVer, IAuth } from '@verdaccio/dev-types';
|
||||
import { $RequestExtend, $NextFunctionVer, IAuth } from '@verdaccio/dev-types';
|
||||
import { API_ERROR, API_MESSAGE, HTTP_STATUS } from '@verdaccio/dev-commons';
|
||||
|
||||
export default function (route: Router, auth: IAuth, config: Config): void {
|
||||
|
@ -15,10 +15,12 @@ jest.mock('@verdaccio/auth', () => ({
|
||||
apiJWTmiddleware() {
|
||||
return mockApiJWTmiddleware();
|
||||
}
|
||||
allow_access(_d, f_, cb) {
|
||||
allow_access(_d, _f, cb) {
|
||||
// always allow access
|
||||
cb(null, true);
|
||||
}
|
||||
allow_publish(_d, f_, cb) {
|
||||
allow_publish(_d, _f, cb) {
|
||||
// always allow publish
|
||||
cb(null, true);
|
||||
}
|
||||
},
|
||||
@ -26,12 +28,12 @@ jest.mock('@verdaccio/auth', () => ({
|
||||
|
||||
describe('package', () => {
|
||||
let app;
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
app = await initializeServer('package.yaml');
|
||||
await publishVersion(app, 'package.yaml', 'foo', '1.0.0');
|
||||
});
|
||||
|
||||
test('should return a package', async (done) => {
|
||||
await publishVersion(app, 'package.yaml', 'foo', '1.0.0');
|
||||
return supertest(app)
|
||||
.get('/foo')
|
||||
.set('Accept', HEADERS.JSON)
|
||||
@ -44,33 +46,35 @@ describe('package', () => {
|
||||
});
|
||||
|
||||
test('should return a package by version', async (done) => {
|
||||
await publishVersion(app, 'package.yaml', 'foo2', '1.0.0');
|
||||
return supertest(app)
|
||||
.get('/foo/1.0.0')
|
||||
.get('/foo2/1.0.0')
|
||||
.set('Accept', HEADERS.JSON)
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.then((response) => {
|
||||
expect(response.body.name).toEqual('foo');
|
||||
expect(response.body.name).toEqual('foo2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: investigate the 404
|
||||
test.skip('should return a package by dist-tag', async (done) => {
|
||||
// await publishVersion(app, 'package.yaml', 'foo3', '1.0.0');
|
||||
await publishVersion(app, 'package.yaml', 'foo-tagged', '1.0.0');
|
||||
await publishTaggedVersion(app, 'package.yaml', 'foo-tagged', '1.0.1', 'test');
|
||||
return supertest(app)
|
||||
.get('/foo-tagged/1.0.0')
|
||||
.get('/foo-tagged/1.0.1')
|
||||
.set('Accept', HEADERS.JSON)
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.CREATED)
|
||||
.then((response) => {
|
||||
expect(response.body.name).toEqual('foo');
|
||||
expect(response.body.name).toEqual('foo3');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('should return 404', async () => {
|
||||
test.skip('should return 404', async () => {
|
||||
return supertest(app)
|
||||
.get('/404-not-found')
|
||||
.set('Accept', HEADERS.JSON)
|
||||
|
@ -49,7 +49,7 @@ export function generatePackageMetadata(pkgName: string, version = '1.0.0', dist
|
||||
[`${pkgName}-${version}.tgz`]: {
|
||||
content_type: 'application/octet-stream',
|
||||
data:
|
||||
'H4sIAAAAAAAAE+2W32vbMBDH85y/QnjQp9qxLEeBMsbGlocNBmN7bFdQ5WuqxJaEpGQdo//79KPeQsnIw5KUDX/9IOvurLuz/DHSjK/YAiY6jcXSKjk6sMqypHWNdtmD6hlBI0wqQmo8nVbVqMR4OsNoVB66kF1aW8eML+Vv10m9oF/jP6IfY4QyyTrILlD2eqkcm+gVzpdrJrPz4NuAsULJ4MZFWdBkbcByI7R79CRjx0ScCdnAvf+SkjUFWu8IubzBgXUhDPidQlfZ3BhlLpBUKDiQ1cDFrYDmKkNnZwjuhUM4808+xNVW8P2bMk1Y7vJrtLC1u1MmLPjBF40+Cc4ahV6GDmI/DWygVRpMwVX3KtXUCg7Sxp7ff3nbt6TBFy65gK1iffsN41yoEHtdFbOiisWMH8bPvXUH0SP3k+KG3UBr+DFy7OGfEJr4x5iWVeS/pLQe+D+FIv/agIWI6GX66kFuIhT+1gDjrp/4d7WAvAwEJPh0u14IufWkM0zaW2W6nLfM2lybgJ4LTJ0/jWiAK8OcMjt8MW3OlfQppcuhhQ6k+2OgkK2Q8DssFPi/IHpU9fz3/+xj5NjDf8QFE39VmE4JDfzPCBn4P4X6/f88f/Pu47zomiPk2Lv/dOv8h+P/34/D/p9CL+Kp67mrGDRo0KBBp9ZPsETQegASAAA=',
|
||||
'H4sIAAAAAAAAE+2W32vbMBDH85y/QnjQp9qxLEeBMsbGlocNBmN7bFdQ5WuqxJaEpGQdo//79KPeQsnIw5KUDX/9IOvurLuz/DHSjK/YAiY6jcXSKjk6sMqypHWNdtmD6hlBI0wqQmo8nVbVqMR4OsNoVB66kF1aW8eML+Vv10m9oF/jP6IfY4QyyTrILlD2eqkcm+gVzpdrJrPz4NuAsULJ4MZFWdBkbcByI7R79CRjx0ScCdnAvf+SkjUFWu8IubzBgXUhDPidQlfZ3BhlLpBUKDiQ1cDFrYDmKkNnZwjuhUM4808+xNVW8P2bMk1Y7vJrtLC1u1MmLPjBF40+Cc4ahV6GDmI/DWygVRpMwVX3KtXUCg7Sxp7ff3nbt6TBFy65gK1iffsN41yoEHtdFbOiisWMH8bPvXUH0SP3k+KG3UBr+DFy7OGfEJr4x5iWVeS/pLQe+D+FIv/agIWI6GX66kFuIhT+1gDjrp/4d7WAvAwEJPh0u14IufWkM0zaW2W6nLfM2lybgJ4LTJ0/jWiAK8OcMjt8MW3OlfQppcuhhQ6k+2OgkK2Q8DssFPi/IHpU9fz3/+xj5NjDf8QFE39VmE4JDfzPCBn4P4X6/f88f/Pu47zomiPk2Lv/dOv8h+P/34/D/p9CL+Kp67mrGDRo0KBBp9ZPsETQegASAAA=+2W32vbMBDH85y',
|
||||
length: 512,
|
||||
},
|
||||
},
|
||||
|
3
packages/core/file-locking/.babelrc
Normal file
3
packages/core/file-locking/.babelrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../../.babelrc"
|
||||
}
|
5
packages/core/file-locking/.eslintignore
Normal file
5
packages/core/file-locking/.eslintignore
Normal file
@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
coverage/
|
||||
lib/
|
||||
.nyc_output
|
||||
tests-report/
|
1
packages/core/file-locking/.gitignore
vendored
Normal file
1
packages/core/file-locking/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
lib/
|
290
packages/core/file-locking/CHANGELOG.md
Normal file
290
packages/core/file-locking/CHANGELOG.md
Normal file
@ -0,0 +1,290 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [9.7.2](https://github.com/verdaccio/monorepo/compare/v9.7.1...v9.7.2) (2020-07-20)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.7.1](https://github.com/verdaccio/monorepo/compare/v9.7.0...v9.7.1) (2020-07-10)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.7.0](https://github.com/verdaccio/monorepo/compare/v9.6.1...v9.7.0) (2020-06-24)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.6.1](https://github.com/verdaccio/monorepo/compare/v9.6.0...v9.6.1) (2020-06-07)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.5.0](https://github.com/verdaccio/monorepo/compare/v9.4.1...v9.5.0) (2020-05-02)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.4.0](https://github.com/verdaccio/monorepo/compare/v9.3.4...v9.4.0) (2020-03-21)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.2](https://github.com/verdaccio/monorepo/compare/v9.3.1...v9.3.2) (2020-03-08)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.1](https://github.com/verdaccio/monorepo/compare/v9.3.0...v9.3.1) (2020-02-23)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.3.0](https://github.com/verdaccio/monorepo/compare/v9.2.0...v9.3.0) (2020-01-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.0.0](https://github.com/verdaccio/monorepo/compare/v8.5.3...v9.0.0) (2020-01-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **eslint-config:** enable eslint curly ([#308](https://github.com/verdaccio/monorepo/issues/308)) ([91acb12](https://github.com/verdaccio/monorepo/commit/91acb121847018e737c21b367fcaab8baa918347))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.5.2](https://github.com/verdaccio/monorepo/compare/v8.5.1...v8.5.2) (2019-12-25)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.5.1](https://github.com/verdaccio/monorepo/compare/v8.5.0...v8.5.1) (2019-12-24)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.5.0](https://github.com/verdaccio/monorepo/compare/v8.4.2...v8.5.0) (2019-12-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.4.2](https://github.com/verdaccio/monorepo/compare/v8.4.1...v8.4.2) (2019-11-23)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.4.1](https://github.com/verdaccio/monorepo/compare/v8.4.0...v8.4.1) (2019-11-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.4.0](https://github.com/verdaccio/monorepo/compare/v8.3.0...v8.4.0) (2019-11-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.3.0](https://github.com/verdaccio/monorepo/compare/v8.2.0...v8.3.0) (2019-10-27)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.2.0](https://github.com/verdaccio/monorepo/compare/v8.2.0-next.0...v8.2.0) (2019-10-23)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.2.0-next.0](https://github.com/verdaccio/monorepo/compare/v8.1.4...v8.2.0-next.0) (2019-10-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixed lint errors ([5e677f7](https://github.com/verdaccio/monorepo/commit/5e677f7))
|
||||
* fixed lint errors ([c80e915](https://github.com/verdaccio/monorepo/commit/c80e915))
|
||||
* quotes should be single ([ae9aa44](https://github.com/verdaccio/monorepo/commit/ae9aa44))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.1.2](https://github.com/verdaccio/monorepo/compare/v8.1.1...v8.1.2) (2019-09-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.1.1](https://github.com/verdaccio/monorepo/compare/v8.1.0...v8.1.1) (2019-09-26)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.1.0](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.1...v8.1.0) (2019-09-07)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.0.1-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.0...v8.0.1-next.1) (2019-08-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.0.1-next.0](https://github.com/verdaccio/monorepo/compare/v8.0.0...v8.0.1-next.0) (2019-08-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.4...v8.0.0) (2019-08-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.4](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.3...v8.0.0-next.4) (2019-08-18)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.2](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.1...v8.0.0-next.2) (2019-08-03)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.0...v8.0.0-next.1) (2019-08-01)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/file-locking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.0](https://github.com/verdaccio/monorepo/compare/v2.0.0...v8.0.0-next.0) (2019-08-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* eslint and typescript errors ([8b3f153](https://github.com/verdaccio/monorepo/commit/8b3f153))
|
||||
* lint issues ([d195fff](https://github.com/verdaccio/monorepo/commit/d195fff))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* remote lodash as dependency ([affb65b](https://github.com/verdaccio/monorepo/commit/affb65b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [1.0.4](https://github.com/verdaccio/file-locking/compare/v1.0.3...v1.0.4) (2019-07-16)
|
||||
|
||||
|
||||
### Build System
|
||||
|
||||
* **deps:** update dependencies ([45b12de](https://github.com/verdaccio/file-locking/commit/45b12de))
|
||||
* **deps:** update husky dependency ([bdb7bad](https://github.com/verdaccio/file-locking/commit/bdb7bad))
|
||||
|
||||
|
||||
|
||||
<a name="1.0.3"></a>
|
||||
## [1.0.3](https://github.com/verdaccio/file-locking/compare/v1.0.2...v1.0.3) (2019-06-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update build script and remove source map ([ec3db50](https://github.com/verdaccio/file-locking/commit/ec3db50))
|
||||
|
||||
|
||||
|
||||
<a name="1.0.2"></a>
|
||||
## [1.0.2](https://github.com/verdaccio/file-locking/compare/v1.0.1...v1.0.2) (2019-06-15)
|
||||
|
||||
|
||||
|
||||
<a name="1.0.1"></a>
|
||||
## [1.0.1](https://github.com/verdaccio/file-locking/compare/v1.0.0...v1.0.1) (2019-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* eslint and typescript errors ([3538e7c](https://github.com/verdaccio/file-locking/commit/3538e7c))
|
21
packages/core/file-locking/LICENSE
Normal file
21
packages/core/file-locking/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Verdaccio
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
22
packages/core/file-locking/README.md
Normal file
22
packages/core/file-locking/README.md
Normal file
@ -0,0 +1,22 @@
|
||||
## Deprecated repository
|
||||
|
||||
**This repository has been moved to a monorepo you can find in [verdaccio/monorepo](https://github.com/verdaccio/monorepo). This package is located in [`core/file-locking` folder](https://github.com/verdaccio/monorepo/tree/master/core/file-locking)**
|
||||
|
||||
---
|
||||
|
||||
# File Locking
|
||||
|
||||
This an utility to lock and unlock files
|
||||
|
||||
[![verdaccio (latest)](https://img.shields.io/npm/v/@verdaccio/file-locking/latest.svg)](https://www.npmjs.com/package/verdaccio)
|
||||
[![docker pulls](https://img.shields.io/docker/pulls/verdaccio/verdaccio.svg?maxAge=43200)](https://verdaccio.org/docs/en/docker.html)
|
||||
[![backers](https://opencollective.com/verdaccio/tiers/backer/badge.svg?label=Backer&color=brightgreen)](https://opencollective.com/verdaccio)
|
||||
[![stackshare](https://img.shields.io/badge/Follow%20on-StackShare-blue.svg?logo=stackshare&style=flat)](https://stackshare.io/verdaccio)
|
||||
[![discord](https://img.shields.io/discord/388674437219745793.svg)](http://chat.verdaccio.org/)
|
||||
[![node](https://img.shields.io/node/v/@verdaccio/file-locking/latest.svg)](https://www.npmjs.com/package/verdaccio)
|
||||
![MIT](https://img.shields.io/github/license/mashape/apistatus.svg)
|
||||
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/verdaccio/localized.svg)](https://crowdin.com/project/verdaccio)
|
||||
|
||||
|
||||
[![Twitter followers](https://img.shields.io/twitter/follow/verdaccio_npm.svg?style=social&label=Follow)](https://twitter.com/verdaccio_npm)
|
||||
[![Github](https://img.shields.io/github/stars/verdaccio/verdaccio.svg?style=social&label=Stars)](https://github.com/verdaccio/verdaccio/stargazers)
|
3
packages/core/file-locking/jest.config.js
Normal file
3
packages/core/file-locking/jest.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
const config = require('../../../jest/config');
|
||||
|
||||
module.exports = Object.assign({}, config, {});
|
47
packages/core/file-locking/package.json
Normal file
47
packages/core/file-locking/package.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@verdaccio/file-locking",
|
||||
"version": "10.0.0-beta",
|
||||
"description": "library that handle file locking",
|
||||
"keywords": [
|
||||
"verdaccio",
|
||||
"lock",
|
||||
"fs"
|
||||
],
|
||||
"author": "Juan Picado <juanpicado19@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://verdaccio.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/verdaccio/monorepo",
|
||||
"directory": "core/file-locking"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/verdaccio/monorepo/issues"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"main": "build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"files": [
|
||||
"build"
|
||||
],
|
||||
"dependencies": {
|
||||
"lockfile": "1.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@verdaccio/types": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"type-check": "tsc --noEmit",
|
||||
"build:types": "tsc --emitDeclarationOnly --declaration true",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"build": "pnpm run build:js && pnpm run build:types"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/verdaccio"
|
||||
}
|
||||
}
|
3
packages/core/file-locking/src/index.ts
Normal file
3
packages/core/file-locking/src/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './unclock';
|
||||
export * from './readFile';
|
||||
export * from './lockfile';
|
29
packages/core/file-locking/src/lockfile.ts
Normal file
29
packages/core/file-locking/src/lockfile.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Callback } from '@verdaccio/types';
|
||||
|
||||
import { lockfile, statDir, statfile } from './utils';
|
||||
|
||||
/**
|
||||
* locks a file by creating a lock file
|
||||
* @param name
|
||||
* @param callback
|
||||
*/
|
||||
const lockFile = function (name: string, callback: Callback): void {
|
||||
Promise.resolve()
|
||||
.then(() => {
|
||||
return statDir(name);
|
||||
})
|
||||
.then(() => {
|
||||
return statfile(name);
|
||||
})
|
||||
.then(() => {
|
||||
return lockfile(name);
|
||||
})
|
||||
.then(() => {
|
||||
callback(null);
|
||||
})
|
||||
.catch((err) => {
|
||||
callback(err);
|
||||
});
|
||||
};
|
||||
|
||||
export { lockFile };
|
82
packages/core/file-locking/src/readFile.ts
Normal file
82
packages/core/file-locking/src/readFile.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import fs from 'fs';
|
||||
|
||||
import { Callback } from '@verdaccio/types';
|
||||
|
||||
import { lockFile } from './lockfile';
|
||||
|
||||
export type ReadFileOptions = {
|
||||
parse?: boolean;
|
||||
lock?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads a local file, which involves
|
||||
* optionally taking a lock
|
||||
* reading the file contents
|
||||
* optionally parsing JSON contents
|
||||
* @param {*} name
|
||||
* @param {*} options
|
||||
* @param {*} callback
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
function readFile(name: string, options: ReadFileOptions = {}, callback: Callback = (): void => {}): void {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
options.lock = options.lock || false;
|
||||
options.parse = options.parse || false;
|
||||
|
||||
const lock = function (options: ReadFileOptions): Promise<null | NodeJS.ErrnoException> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
if (!options.lock) {
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
lockFile(name, function (err: NodeJS.ErrnoException | null) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
return resolve(null);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const read = function (): Promise<NodeJS.ErrnoException | string> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
fs.readFile(name, 'utf8', function (err, contents) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(contents);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const parseJSON = function (contents: string): Promise<unknown> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
if (!options.parse) {
|
||||
return resolve(contents);
|
||||
}
|
||||
try {
|
||||
contents = JSON.parse(contents);
|
||||
return resolve(contents);
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => lock(options))
|
||||
.then(() => read())
|
||||
.then((content) => parseJSON(content as string))
|
||||
.then(
|
||||
(result) => callback(null, result),
|
||||
(err) => callback(err)
|
||||
);
|
||||
}
|
||||
|
||||
export { readFile };
|
10
packages/core/file-locking/src/unclock.ts
Normal file
10
packages/core/file-locking/src/unclock.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import locker from 'lockfile';
|
||||
import { Callback } from '@verdaccio/types';
|
||||
|
||||
// unlocks file by removing existing lock file
|
||||
export function unlockFile(name: string, next: Callback): void {
|
||||
const lockFileName = `${name}.lock`;
|
||||
locker.unlock(lockFileName, function () {
|
||||
return next(null);
|
||||
});
|
||||
}
|
56
packages/core/file-locking/src/utils.ts
Normal file
56
packages/core/file-locking/src/utils.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import locker from 'lockfile';
|
||||
|
||||
export const statDir = (name: string): Promise<Error | null> => {
|
||||
return new Promise((resolve, reject): void => {
|
||||
// test to see if the directory exists
|
||||
const dirPath = path.dirname(name);
|
||||
fs.stat(dirPath, function (err, stats) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
} else if (!stats.isDirectory()) {
|
||||
return resolve(new Error(`${path.dirname(name)} is not a directory`));
|
||||
} else {
|
||||
return resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const statfile = (name: string): Promise<Error | null> => {
|
||||
return new Promise((resolve, reject): void => {
|
||||
// test to see if the directory exists
|
||||
fs.stat(name, function (err, stats) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
} else if (!stats.isFile()) {
|
||||
return resolve(new Error(`${path.dirname(name)} is not a file`));
|
||||
} else {
|
||||
return resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const lockfile = (name: string): Promise<unknown> => {
|
||||
return new Promise((resolve): void => {
|
||||
const lockOpts = {
|
||||
// time (ms) to wait when checking for stale locks
|
||||
wait: 1000,
|
||||
// how often (ms) to re-check stale locks
|
||||
pollPeriod: 100,
|
||||
// locks are considered stale after 5 minutes
|
||||
stale: 5 * 60 * 1000,
|
||||
// number of times to attempt to create a lock
|
||||
retries: 100,
|
||||
// time (ms) between tries
|
||||
retryWait: 100,
|
||||
};
|
||||
const lockFileName = `${name}.lock`;
|
||||
locker.lock(lockFileName, lockOpts, () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
@ -0,0 +1,23 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`testing locking readFile read file with options (parse, lock) should to be found to be read it as object 1`] = `
|
||||
Object {
|
||||
"name": "assets",
|
||||
"version": "0.0.1",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`testing locking readFile read file with no options should to be found to be read it as object 1`] = `
|
||||
Object {
|
||||
"name": "assets",
|
||||
"version": "0.0.1",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`testing locking readFile read file with no options should to be found to be read it as string 1`] = `
|
||||
"{
|
||||
\\"name\\": \\"assets\\",
|
||||
\\"version\\": \\"0.0.1\\"
|
||||
}
|
||||
"
|
||||
`;
|
4
packages/core/file-locking/tests/assets/package.json
Normal file
4
packages/core/file-locking/tests/assets/package.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "assets",
|
||||
"version": "0.0.1"
|
||||
}
|
4
packages/core/file-locking/tests/assets/package2.json
Normal file
4
packages/core/file-locking/tests/assets/package2.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "assets",
|
||||
"version": "0.0.1"
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "assets",
|
||||
"version": "0.0.1",
|
||||
}
|
117
packages/core/file-locking/tests/lock.spec.ts
Normal file
117
packages/core/file-locking/tests/lock.spec.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
import { lockFile, unlockFile, readFile } from '../src/index';
|
||||
|
||||
interface Error {
|
||||
message: string;
|
||||
}
|
||||
|
||||
const getFilePath = (filename: string): string => {
|
||||
return path.resolve(__dirname, `assets/${filename}`);
|
||||
};
|
||||
|
||||
const removeTempFile = (filename: string): void => {
|
||||
const filepath = getFilePath(filename);
|
||||
fs.unlink(filepath, (error) => {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
describe('testing locking', () => {
|
||||
describe('lockFile', () => {
|
||||
test('file should be found to be locked', (done) => {
|
||||
lockFile(getFilePath('package.json'), (error: Error) => {
|
||||
expect(error).toBeNull();
|
||||
removeTempFile('package.json.lock');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('file should fail to be found to be locked', (done) => {
|
||||
lockFile(getFilePath('package.fail.json'), (error: Error) => {
|
||||
expect(error.message).toMatch(/ENOENT: no such file or directory, stat '(.*)package.fail.json'/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlockFile', () => {
|
||||
test('file should to be found to be unLock', (done) => {
|
||||
unlockFile(getFilePath('package.json.lock'), (error: Error) => {
|
||||
expect(error).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('readFile', () => {
|
||||
test('read file with no options should to be found to be read it as string', (done) => {
|
||||
readFile(getFilePath('package.json'), {}, (error: Error, data: string) => {
|
||||
expect(error).toBeNull();
|
||||
expect(data).toMatchSnapshot();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('read file with no options should to be found to be read it as object', (done) => {
|
||||
const options = {
|
||||
parse: true,
|
||||
};
|
||||
readFile(getFilePath('package.json'), options, (error: Error, data: string) => {
|
||||
expect(error).toBeNull();
|
||||
expect(data).toMatchSnapshot();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('read file with options (parse) should to be not found to be read it', (done) => {
|
||||
const options = {
|
||||
parse: true,
|
||||
};
|
||||
readFile(getFilePath('package.fail.json'), options, (error: Error) => {
|
||||
expect(error.message).toMatch(/ENOENT: no such file or directory, open '(.*)package.fail.json'/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('read file with options should to be found to be read it and fails to be parsed', (done) => {
|
||||
const options = {
|
||||
parse: true,
|
||||
};
|
||||
const errorMessage = process.platform === 'win32' ? 'Unexpected token } in JSON at position 47' : 'Unexpected token } in JSON at position 44';
|
||||
readFile(getFilePath('wrong.package.json'), options, (error: Error) => {
|
||||
expect(error.message).toEqual(errorMessage);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('read file with options (parse, lock) should to be found to be read it as object', (done) => {
|
||||
const options = {
|
||||
parse: true,
|
||||
lock: true,
|
||||
};
|
||||
readFile(getFilePath('package2.json'), options, (error: Error, data: string) => {
|
||||
expect(error).toBeNull();
|
||||
expect(data).toMatchSnapshot();
|
||||
removeTempFile('package2.json.lock');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('read file with options (parse, lock) should to be found to be read it and fails to be parsed', (done) => {
|
||||
const options = {
|
||||
parse: true,
|
||||
lock: true,
|
||||
};
|
||||
const errorMessage = process.platform === 'win32' ? 'Unexpected token } in JSON at position 47' : 'Unexpected token } in JSON at position 44';
|
||||
readFile(getFilePath('wrong.package.json'), options, (error: Error) => {
|
||||
expect(error.message).toEqual(errorMessage);
|
||||
removeTempFile('wrong.package.json.lock');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
9
packages/core/file-locking/tsconfig.json
Normal file
9
packages/core/file-locking/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
20
packages/core/file-locking/types/lockfile.d.ts
vendored
Normal file
20
packages/core/file-locking/types/lockfile.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
declare module 'lockfile' {
|
||||
type Callback = (err?: Error) => void;
|
||||
|
||||
interface LockOptions {
|
||||
wait?: number;
|
||||
pollPeriod?: number;
|
||||
stale?: number;
|
||||
retries?: number;
|
||||
retryWait?: number;
|
||||
}
|
||||
|
||||
interface LockFileExport {
|
||||
lock(fileName: string, opts: LockOptions, cb: Callback): void;
|
||||
unlock(fileName: string, cb: Callback): void;
|
||||
}
|
||||
|
||||
const lockFileExport: LockFileExport;
|
||||
|
||||
export default lockFileExport;
|
||||
}
|
3
packages/core/htpasswd/.babelrc
Normal file
3
packages/core/htpasswd/.babelrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../../.babelrc"
|
||||
}
|
6
packages/core/htpasswd/.eslintignore
Normal file
6
packages/core/htpasswd/.eslintignore
Normal file
@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
coverage/
|
||||
lib/
|
||||
.nyc_output
|
||||
tests-report/
|
||||
fixtures/
|
5
packages/core/htpasswd/.eslintrc.json
Normal file
5
packages/core/htpasswd/.eslintrc.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"rules": {
|
||||
"@typescript-eslint/no-use-before-define": "off"
|
||||
}
|
||||
}
|
328
packages/core/htpasswd/CHANGELOG.md
Normal file
328
packages/core/htpasswd/CHANGELOG.md
Normal file
@ -0,0 +1,328 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [9.7.2](https://github.com/verdaccio/monorepo/compare/v9.7.1...v9.7.2) (2020-07-20)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.7.1](https://github.com/verdaccio/monorepo/compare/v9.7.0...v9.7.1) (2020-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update dependencies ([#375](https://github.com/verdaccio/monorepo/issues/375)) ([1e7aeec](https://github.com/verdaccio/monorepo/commit/1e7aeec31b056979285e272793a95b8c75d57c77))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.7.0](https://github.com/verdaccio/monorepo/compare/v9.6.1...v9.7.0) (2020-06-24)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.6.1](https://github.com/verdaccio/monorepo/compare/v9.6.0...v9.6.1) (2020-06-07)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.5.0](https://github.com/verdaccio/monorepo/compare/v9.4.1...v9.5.0) (2020-05-02)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.4.1](https://github.com/verdaccio/monorepo/compare/v9.4.0...v9.4.1) (2020-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **verdaccio-htpasswd:** generate non-constant legacy 2 byte salt ([#357](https://github.com/verdaccio/monorepo/issues/357)) ([d522595](https://github.com/verdaccio/monorepo/commit/d522595122b7deaac8e3bc568f73658041811aaf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.4.0](https://github.com/verdaccio/monorepo/compare/v9.3.4...v9.4.0) (2020-03-21)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.2](https://github.com/verdaccio/monorepo/compare/v9.3.1...v9.3.2) (2020-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update dependencies ([#332](https://github.com/verdaccio/monorepo/issues/332)) ([b6165ae](https://github.com/verdaccio/monorepo/commit/b6165aea9b7e4012477081eae68bfa7159c58f56))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.1](https://github.com/verdaccio/monorepo/compare/v9.3.0...v9.3.1) (2020-02-23)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.3.0](https://github.com/verdaccio/monorepo/compare/v9.2.0...v9.3.0) (2020-01-29)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.0.0](https://github.com/verdaccio/monorepo/compare/v8.5.3...v9.0.0) (2020-01-07)
|
||||
|
||||
|
||||
### chore
|
||||
|
||||
* update dependencies ([68add74](https://github.com/verdaccio/monorepo/commit/68add743159867f678ddb9168d2bc8391844de47))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **eslint-config:** enable eslint curly ([#308](https://github.com/verdaccio/monorepo/issues/308)) ([91acb12](https://github.com/verdaccio/monorepo/commit/91acb121847018e737c21b367fcaab8baa918347))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* @verdaccio/eslint-config requires ESLint >=6.8.0 and Prettier >=1.19.1 to fix compatibility with overrides.extends config
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.5.2](https://github.com/verdaccio/monorepo/compare/v8.5.1...v8.5.2) (2019-12-25)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.5.1](https://github.com/verdaccio/monorepo/compare/v8.5.0...v8.5.1) (2019-12-24)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.5.0](https://github.com/verdaccio/monorepo/compare/v8.4.2...v8.5.0) (2019-12-22)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.4.2](https://github.com/verdaccio/monorepo/compare/v8.4.1...v8.4.2) (2019-11-23)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.4.1](https://github.com/verdaccio/monorepo/compare/v8.4.0...v8.4.1) (2019-11-22)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.4.0](https://github.com/verdaccio/monorepo/compare/v8.3.0...v8.4.0) (2019-11-22)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.3.0](https://github.com/verdaccio/monorepo/compare/v8.2.0...v8.3.0) (2019-10-27)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.2.0](https://github.com/verdaccio/monorepo/compare/v8.2.0-next.0...v8.2.0) (2019-10-23)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.2.0-next.0](https://github.com/verdaccio/monorepo/compare/v8.1.4...v8.2.0-next.0) (2019-10-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixed lint errors ([5e677f7](https://github.com/verdaccio/monorepo/commit/5e677f7))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.1.2](https://github.com/verdaccio/monorepo/compare/v8.1.1...v8.1.2) (2019-09-29)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.1.1](https://github.com/verdaccio/monorepo/compare/v8.1.0...v8.1.1) (2019-09-26)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.1.0](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.1...v8.1.0) (2019-09-07)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.0.1-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.0...v8.0.1-next.1) (2019-08-29)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.0.1-next.0](https://github.com/verdaccio/monorepo/compare/v8.0.0...v8.0.1-next.0) (2019-08-29)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.4...v8.0.0) (2019-08-22)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.4](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.3...v8.0.0-next.4) (2019-08-18)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.2](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.1...v8.0.0-next.2) (2019-08-03)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.0...v8.0.0-next.1) (2019-08-01)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.0](https://github.com/verdaccio/monorepo/compare/v2.0.0...v8.0.0-next.0) (2019-08-01)
|
||||
|
||||
**Note:** Version bump only for package verdaccio-htpasswd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
# [2.0.0](https://github.com/verdaccio/verdaccio-htpasswd/compare/v2.0.0-beta.1...v2.0.0) (2019-04-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* drop node v6 suport ([d1d52e8](https://github.com/verdaccio/verdaccio-htpasswd/commit/d1d52e8))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-beta.1"></a>
|
||||
# [2.0.0-beta.1](https://github.com/verdaccio/verdaccio-htpasswd/compare/v2.0.0-beta.0...v2.0.0-beta.1) (2019-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* package.json to reduce vulnerabilities ([259bdaf](https://github.com/verdaccio/verdaccio-htpasswd/commit/259bdaf))
|
||||
* update [@verdaccio](https://github.com/verdaccio)/file-locking@1.0.0 ([ec0bbfd](https://github.com/verdaccio/verdaccio-htpasswd/commit/ec0bbfd))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-beta.0"></a>
|
||||
# [2.0.0-beta.0](https://github.com/verdaccio/verdaccio-htpasswd/compare/v1.0.1...v2.0.0-beta.0) (2019-02-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* migrate to typescript ([79f6937](https://github.com/verdaccio/verdaccio-htpasswd/commit/79f6937))
|
||||
* remove Node6 from CircleCI ([d3a05ab](https://github.com/verdaccio/verdaccio-htpasswd/commit/d3a05ab))
|
||||
* use verdaccio babel preset ([3a63f88](https://github.com/verdaccio/verdaccio-htpasswd/commit/3a63f88))
|
||||
|
||||
|
||||
|
||||
<a name="1.0.1"></a>
|
||||
## [1.0.1](https://github.com/verdaccio/verdaccio-htpasswd/compare/v1.0.0...v1.0.1) (2018-09-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* password hash & increase coverage ([6420c26](https://github.com/verdaccio/verdaccio-htpasswd/commit/6420c26))
|
||||
|
||||
|
||||
|
||||
<a name="1.0.0"></a>
|
||||
# [1.0.0](https://github.com/verdaccio/verdaccio-htpasswd/compare/v0.2.2...v1.0.0) (2018-09-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adds error message for user registration ([0bab945](https://github.com/verdaccio/verdaccio-htpasswd/commit/0bab945))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **change-passwd:** implement change password [#32](https://github.com/verdaccio/verdaccio-htpasswd/issues/32) ([830b143](https://github.com/verdaccio/verdaccio-htpasswd/commit/830b143))
|
21
packages/core/htpasswd/LICENSE
Normal file
21
packages/core/htpasswd/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Verdaccio
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
65
packages/core/htpasswd/README.md
Normal file
65
packages/core/htpasswd/README.md
Normal file
@ -0,0 +1,65 @@
|
||||
|
||||
[![verdaccio (latest)](https://img.shields.io/npm/v/verdaccio-htpasswd/latest.svg)](https://www.npmjs.com/package/verdaccio-htpasswd)
|
||||
[![Known Vulnerabilities](https://snyk.io/test/github/verdaccio/verdaccio-htpasswd/badge.svg?targetFile=package.json)](https://snyk.io/test/github/verdaccio/verdaccio-htpasswd?targetFile=package.json)
|
||||
[![CircleCI](https://circleci.com/gh/verdaccio/verdaccio-htpasswd.svg?style=svg)](https://circleci.com/gh/ayusharma/verdaccio-htpasswd) [![codecov](https://codecov.io/gh/ayusharma/verdaccio-htpasswd/branch/master/graph/badge.svg)](https://codecov.io/gh/ayusharma/verdaccio-htpasswd)
|
||||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fverdaccio%2Fverdaccio-htpasswd.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fverdaccio%2Fverdaccio-htpasswd?ref=badge_shield)
|
||||
[![backers](https://opencollective.com/verdaccio/tiers/backer/badge.svg?label=Backer&color=brightgreen)](https://opencollective.com/verdaccio)
|
||||
[![discord](https://img.shields.io/discord/388674437219745793.svg)](http://chat.verdaccio.org/)
|
||||
![MIT](https://img.shields.io/github/license/mashape/apistatus.svg)
|
||||
[![node](https://img.shields.io/node/v/verdaccio-htpasswd/latest.svg)](https://www.npmjs.com/package/verdaccio-htpasswd)
|
||||
|
||||
|
||||
# Verdaccio Module For User Auth Via Htpasswd
|
||||
|
||||
`verdaccio-htpasswd` is a default authentication plugin for the [Verdaccio](https://github.com/verdaccio/verdaccio).
|
||||
|
||||
> This plugin is being used as dependency after `v3.0.0-beta.x`. The `v2.x` still contains this plugin built-in.
|
||||
|
||||
## Install
|
||||
|
||||
As simple as running:
|
||||
|
||||
$ npm install -g verdaccio-htpasswd
|
||||
|
||||
## Configure
|
||||
|
||||
auth:
|
||||
htpasswd:
|
||||
file: ./htpasswd
|
||||
# Maximum amount of users allowed to register, defaults to "+infinity".
|
||||
# You can set this to -1 to disable registration.
|
||||
#max_users: 1000
|
||||
|
||||
## Logging In
|
||||
|
||||
To log in using NPM, run:
|
||||
|
||||
```
|
||||
npm adduser --registry https://your.registry.local
|
||||
```
|
||||
|
||||
## Generate htpasswd username/password combination
|
||||
|
||||
If you wish to handle access control using htpasswd file, you can generate
|
||||
username/password combination form
|
||||
[here](http://www.htaccesstools.com/htpasswd-generator/) and add it to htpasswd
|
||||
file.
|
||||
|
||||
## How does it work?
|
||||
|
||||
The htpasswd file contains rows corresponding to a pair of username and password
|
||||
separated with a colon character. The password is encrypted using the UNIX system's
|
||||
crypt method and may use MD5 or SHA1.
|
||||
|
||||
## Plugin Development in Verdaccio
|
||||
|
||||
There are many ways to extend [Verdaccio](https://github.com/verdaccio/verdaccio),
|
||||
currently it support authentication plugins, middleware plugins (since v2.7.0)
|
||||
and storage plugins since (v3.x).
|
||||
#### Useful Links
|
||||
- [Plugin Development](http://www.verdaccio.org/docs/en/dev-plugins.html)
|
||||
- [List of Plugins](http://www.verdaccio.org/docs/en/plugins.html)
|
||||
|
||||
|
||||
## License
|
||||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fverdaccio%2Fverdaccio-htpasswd.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fverdaccio%2Fverdaccio-htpasswd?ref=badge_large)
|
3
packages/core/htpasswd/jest.config.js
Normal file
3
packages/core/htpasswd/jest.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
const config = require('../../../jest/config');
|
||||
|
||||
module.exports = Object.assign({}, config, {});
|
50
packages/core/htpasswd/package.json
Normal file
50
packages/core/htpasswd/package.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@verdaccio/htpasswd",
|
||||
"version": "10.0.0-beta",
|
||||
"description": "htpasswd auth plugin for Verdaccio",
|
||||
"keywords": [
|
||||
"verdaccio",
|
||||
"plugin",
|
||||
"auth",
|
||||
"htpasswd"
|
||||
],
|
||||
"author": "Ayush Sharma <ayush.aceit@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://verdaccio.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/verdaccio/monorepo",
|
||||
"directory": "plugins/htpasswd"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/verdaccio/monorepo/issues"
|
||||
},
|
||||
"main": "./build/index.js",
|
||||
"types": "./build/index.d.ts",
|
||||
"files": [
|
||||
"build"
|
||||
],
|
||||
"dependencies": {
|
||||
"@verdaccio/file-locking": "workspace:*",
|
||||
"apache-md5": "1.1.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
"http-errors": "1.8.0",
|
||||
"unix-crypt-td-js": "1.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@verdaccio/types": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"type-check": "tsc --noEmit",
|
||||
"build:types": "tsc --emitDeclarationOnly --declaration true",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"build": "pnpm run build:js && pnpm run build:types"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/verdaccio"
|
||||
}
|
||||
}
|
52
packages/core/htpasswd/src/crypt3.ts
Normal file
52
packages/core/htpasswd/src/crypt3.ts
Normal file
@ -0,0 +1,52 @@
|
||||
/** Node.js Crypt(3) Library
|
||||
Inspired by (and intended to be compatible with) sendanor/crypt3
|
||||
see https://github.com/sendanor/node-crypt3
|
||||
The key difference is the removal of the dependency on the unix crypt(3) function
|
||||
which is not platform independent, and requires compilation. Instead, a pure
|
||||
javascript version is used.
|
||||
*/
|
||||
|
||||
import crypto from 'crypto';
|
||||
|
||||
import crypt from 'unix-crypt-td-js';
|
||||
|
||||
/**
|
||||
* Create salt
|
||||
* @param {string} type The type of salt: md5, blowfish (only some linux
|
||||
* distros), sha256 or sha512. Default is sha512.
|
||||
* @returns {string} Generated salt string
|
||||
*/
|
||||
export function createSalt(type = 'crypt'): string {
|
||||
switch (type) {
|
||||
case 'crypt':
|
||||
// Legacy crypt salt with no prefix (only the first 2 bytes will be used).
|
||||
return crypto.randomBytes(2).toString('base64');
|
||||
|
||||
case 'md5':
|
||||
return '$1$' + crypto.randomBytes(10).toString('base64');
|
||||
|
||||
case 'blowfish':
|
||||
return '$2a$' + crypto.randomBytes(10).toString('base64');
|
||||
|
||||
case 'sha256':
|
||||
return '$5$' + crypto.randomBytes(10).toString('base64');
|
||||
|
||||
case 'sha512':
|
||||
return '$6$' + crypto.randomBytes(10).toString('base64');
|
||||
|
||||
default:
|
||||
throw new TypeError(`Unknown salt type at crypt3.createSalt: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crypt(3) password and data encryption.
|
||||
* @param {string} key user's typed password
|
||||
* @param {string} salt Optional salt, for example SHA-512 use "$6$salt$".
|
||||
* @returns {string} A generated hash in format $id$salt$encrypted
|
||||
* @see https://en.wikipedia.org/wiki/Crypt_(C)
|
||||
*/
|
||||
|
||||
export default function crypt3(key: string, salt: string = createSalt()): string {
|
||||
return crypt(key, salt);
|
||||
}
|
239
packages/core/htpasswd/src/htpasswd.ts
Normal file
239
packages/core/htpasswd/src/htpasswd.ts
Normal file
@ -0,0 +1,239 @@
|
||||
import fs from 'fs';
|
||||
import Path from 'path';
|
||||
|
||||
import { Callback, AuthConf, Config, IPluginAuth } from '@verdaccio/types';
|
||||
import { unlockFile } from '@verdaccio/file-locking';
|
||||
|
||||
import { verifyPassword, lockAndRead, parseHTPasswd, addUserToHTPasswd, changePasswordToHTPasswd, sanityCheck } from './utils';
|
||||
|
||||
export interface VerdaccioConfigApp extends Config {
|
||||
file: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTPasswd - Verdaccio auth class
|
||||
*/
|
||||
export default class HTPasswd implements IPluginAuth<VerdaccioConfigApp> {
|
||||
/**
|
||||
*
|
||||
* @param {*} config htpasswd file
|
||||
* @param {object} stuff config.yaml in object from
|
||||
*/
|
||||
private users: {};
|
||||
private stuff: {};
|
||||
private config: {};
|
||||
private verdaccioConfig: Config;
|
||||
private maxUsers: number;
|
||||
private path: string;
|
||||
private logger: {};
|
||||
private lastTime: any;
|
||||
// constructor
|
||||
public constructor(config: AuthConf, stuff: VerdaccioConfigApp) {
|
||||
this.users = {};
|
||||
|
||||
// config for this module
|
||||
this.config = config;
|
||||
this.stuff = stuff;
|
||||
|
||||
// verdaccio logger
|
||||
this.logger = stuff.logger;
|
||||
|
||||
// verdaccio main config object
|
||||
this.verdaccioConfig = stuff.config;
|
||||
|
||||
// all this "verdaccio_config" stuff is for b/w compatibility only
|
||||
this.maxUsers = config.max_users ? config.max_users : Infinity;
|
||||
|
||||
this.lastTime = null;
|
||||
|
||||
const { file } = config;
|
||||
|
||||
if (!file) {
|
||||
throw new Error('should specify "file" in config');
|
||||
}
|
||||
|
||||
this.path = Path.resolve(Path.dirname(this.verdaccioConfig.self_path), file);
|
||||
}
|
||||
|
||||
/**
|
||||
* authenticate - Authenticate user.
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {function} cd
|
||||
* @returns {function}
|
||||
*/
|
||||
public authenticate(user: string, password: string, cb: Callback): void {
|
||||
this.reload((err) => {
|
||||
if (err) {
|
||||
return cb(err.code === 'ENOENT' ? null : err);
|
||||
}
|
||||
if (!this.users[user]) {
|
||||
return cb(null, false);
|
||||
}
|
||||
if (!verifyPassword(password, this.users[user])) {
|
||||
return cb(null, false);
|
||||
}
|
||||
|
||||
// authentication succeeded!
|
||||
// return all usergroups this user has access to;
|
||||
// (this particular package has no concept of usergroups, so just return
|
||||
// user herself)
|
||||
return cb(null, [user]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user
|
||||
* 1. lock file for writing (other processes can still read)
|
||||
* 2. reload .htpasswd
|
||||
* 3. write new data into .htpasswd.tmp
|
||||
* 4. move .htpasswd.tmp to .htpasswd
|
||||
* 5. reload .htpasswd
|
||||
* 6. unlock file
|
||||
*
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {function} realCb
|
||||
* @returns {function}
|
||||
*/
|
||||
public adduser(user: string, password: string, realCb: Callback): any {
|
||||
const pathPass = this.path;
|
||||
let sanity = sanityCheck(user, password, verifyPassword, this.users, this.maxUsers);
|
||||
|
||||
// preliminary checks, just to ensure that file won't be reloaded if it's
|
||||
// not needed
|
||||
if (sanity) {
|
||||
return realCb(sanity, false);
|
||||
}
|
||||
|
||||
lockAndRead(pathPass, (err, res): void => {
|
||||
let locked = false;
|
||||
|
||||
// callback that cleans up lock first
|
||||
const cb = (err): void => {
|
||||
if (locked) {
|
||||
unlockFile(pathPass, () => {
|
||||
// ignore any error from the unlock
|
||||
realCb(err, !err);
|
||||
});
|
||||
} else {
|
||||
realCb(err, !err);
|
||||
}
|
||||
};
|
||||
|
||||
if (!err) {
|
||||
locked = true;
|
||||
}
|
||||
|
||||
// ignore ENOENT errors, we'll just create .htpasswd in that case
|
||||
if (err && err.code !== 'ENOENT') {
|
||||
return cb(err);
|
||||
}
|
||||
const body = (res || '').toString('utf8');
|
||||
this.users = parseHTPasswd(body);
|
||||
|
||||
// real checks, to prevent race conditions
|
||||
// parsing users after reading file.
|
||||
sanity = sanityCheck(user, password, verifyPassword, this.users, this.maxUsers);
|
||||
|
||||
if (sanity) {
|
||||
return cb(sanity);
|
||||
}
|
||||
|
||||
try {
|
||||
this._writeFile(addUserToHTPasswd(body, user, password), cb);
|
||||
} catch (err) {
|
||||
return cb(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload users
|
||||
* @param {function} callback
|
||||
*/
|
||||
public reload(callback: Callback): void {
|
||||
fs.stat(this.path, (err, stats) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (this.lastTime === stats.mtime) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
this.lastTime = stats.mtime;
|
||||
|
||||
fs.readFile(this.path, 'utf8', (err, buffer) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
Object.assign(this.users, parseHTPasswd(buffer));
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _stringToUt8(authentication: string): string {
|
||||
return (authentication || '').toString();
|
||||
}
|
||||
|
||||
private _writeFile(body: string, cb: Callback): void {
|
||||
fs.writeFile(this.path, body, (err) => {
|
||||
if (err) {
|
||||
cb(err);
|
||||
} else {
|
||||
this.reload(() => {
|
||||
cb(null);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* changePassword - change password for existing user.
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {function} cd
|
||||
* @returns {function}
|
||||
*/
|
||||
public changePassword(user: string, password: string, newPassword: string, realCb: Callback): void {
|
||||
lockAndRead(this.path, (err, res) => {
|
||||
let locked = false;
|
||||
const pathPassFile = this.path;
|
||||
|
||||
// callback that cleans up lock first
|
||||
const cb = (err): void => {
|
||||
if (locked) {
|
||||
unlockFile(pathPassFile, () => {
|
||||
// ignore any error from the unlock
|
||||
realCb(err, !err);
|
||||
});
|
||||
} else {
|
||||
realCb(err, !err);
|
||||
}
|
||||
};
|
||||
|
||||
if (!err) {
|
||||
locked = true;
|
||||
}
|
||||
|
||||
if (err && err.code !== 'ENOENT') {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const body = this._stringToUt8(res);
|
||||
this.users = parseHTPasswd(body);
|
||||
|
||||
if (!this.users[user]) {
|
||||
return cb(new Error('User not found'));
|
||||
}
|
||||
|
||||
try {
|
||||
this._writeFile(changePasswordToHTPasswd(body, user, password, newPassword), cb);
|
||||
} catch (err) {
|
||||
return cb(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
11
packages/core/htpasswd/src/index.ts
Normal file
11
packages/core/htpasswd/src/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import HTPasswd from './htpasswd';
|
||||
|
||||
/**
|
||||
* A new instance of HTPasswd class.
|
||||
* @param {object} config
|
||||
* @param {object} stuff
|
||||
* @returns {object}
|
||||
*/
|
||||
export default function (config, stuff): HTPasswd {
|
||||
return new HTPasswd(config, stuff);
|
||||
}
|
174
packages/core/htpasswd/src/utils.ts
Normal file
174
packages/core/htpasswd/src/utils.ts
Normal file
@ -0,0 +1,174 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
import md5 from 'apache-md5';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import createError, { HttpError } from 'http-errors';
|
||||
import { readFile } from '@verdaccio/file-locking';
|
||||
import { Callback } from '@verdaccio/types';
|
||||
|
||||
import crypt3 from './crypt3';
|
||||
|
||||
// this function neither unlocks file nor closes it
|
||||
// it'll have to be done manually later
|
||||
export function lockAndRead(name: string, cb: Callback): void {
|
||||
readFile(name, { lock: true }, (err, res) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
return cb(null, res);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* parseHTPasswd - convert htpasswd lines to object.
|
||||
* @param {string} input
|
||||
* @returns {object}
|
||||
*/
|
||||
export function parseHTPasswd(input: string): Record<string, any> {
|
||||
return input.split('\n').reduce((result, line) => {
|
||||
const args = line.split(':', 3);
|
||||
if (args.length > 1) {
|
||||
result[args[0]] = args[1];
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* verifyPassword - matches password and it's hash.
|
||||
* @param {string} passwd
|
||||
* @param {string} hash
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function verifyPassword(passwd: string, hash: string): boolean {
|
||||
if (hash.match(/^\$2(a|b|y)\$/)) {
|
||||
return bcrypt.compareSync(passwd, hash);
|
||||
} else if (hash.indexOf('{PLAIN}') === 0) {
|
||||
return passwd === hash.substr(7);
|
||||
} else if (hash.indexOf('{SHA}') === 0) {
|
||||
return (
|
||||
crypto
|
||||
.createHash('sha1')
|
||||
// https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding
|
||||
.update(passwd, 'utf8')
|
||||
.digest('base64') === hash.substr(5)
|
||||
);
|
||||
}
|
||||
// for backwards compatibility, first check md5 then check crypt3
|
||||
return md5(passwd, hash) === hash || crypt3(passwd, hash) === hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* addUserToHTPasswd - Generate a htpasswd format for .htpasswd
|
||||
* @param {string} body
|
||||
* @param {string} user
|
||||
* @param {string} passwd
|
||||
* @returns {string}
|
||||
*/
|
||||
export function addUserToHTPasswd(body: string, user: string, passwd: string): string {
|
||||
if (user !== encodeURIComponent(user)) {
|
||||
const err = createError('username should not contain non-uri-safe characters');
|
||||
|
||||
err.status = 409;
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (crypt3) {
|
||||
passwd = crypt3(passwd);
|
||||
} else {
|
||||
passwd = '{SHA}' + crypto.createHash('sha1').update(passwd, 'utf8').digest('base64');
|
||||
}
|
||||
const comment = 'autocreated ' + new Date().toJSON();
|
||||
let newline = `${user}:${passwd}:${comment}\n`;
|
||||
|
||||
if (body.length && body[body.length - 1] !== '\n') {
|
||||
newline = '\n' + newline;
|
||||
}
|
||||
return body + newline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanity check for a user
|
||||
* @param {string} user
|
||||
* @param {object} users
|
||||
* @param {number} maxUsers
|
||||
* @returns {object}
|
||||
*/
|
||||
export function sanityCheck(user: string, password: string, verifyFn: Callback, users: {}, maxUsers: number): HttpError | null {
|
||||
let err;
|
||||
|
||||
// check for user or password
|
||||
if (!user || !password) {
|
||||
err = Error('username and password is required');
|
||||
err.status = 400;
|
||||
return err;
|
||||
}
|
||||
|
||||
const hash = users[user];
|
||||
|
||||
if (maxUsers < 0) {
|
||||
err = Error('user registration disabled');
|
||||
err.status = 409;
|
||||
return err;
|
||||
}
|
||||
|
||||
if (hash) {
|
||||
const auth = verifyFn(password, users[user]);
|
||||
if (auth) {
|
||||
err = Error('username is already registered');
|
||||
err.status = 409;
|
||||
return err;
|
||||
}
|
||||
err = Error('unauthorized access');
|
||||
err.status = 401;
|
||||
return err;
|
||||
} else if (Object.keys(users).length >= maxUsers) {
|
||||
err = Error('maximum amount of users reached');
|
||||
err.status = 403;
|
||||
return err;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getCryptoPassword(password: string): string {
|
||||
return `{SHA}${crypto.createHash('sha1').update(password, 'utf8').digest('base64')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* changePasswordToHTPasswd - change password for existing user
|
||||
* @param {string} body
|
||||
* @param {string} user
|
||||
* @param {string} passwd
|
||||
* @param {string} newPasswd
|
||||
* @returns {string}
|
||||
*/
|
||||
export function changePasswordToHTPasswd(body: string, user: string, passwd: string, newPasswd: string): string {
|
||||
let lines = body.split('\n');
|
||||
lines = lines.map((line) => {
|
||||
const [username, password] = line.split(':', 3);
|
||||
|
||||
if (username === user) {
|
||||
let _passwd;
|
||||
let _newPasswd;
|
||||
if (crypt3) {
|
||||
_passwd = crypt3(passwd, password);
|
||||
_newPasswd = crypt3(newPasswd);
|
||||
} else {
|
||||
_passwd = getCryptoPassword(passwd);
|
||||
_newPasswd = getCryptoPassword(newPasswd);
|
||||
}
|
||||
|
||||
if (password == _passwd) {
|
||||
// replace old password hash with new password hash
|
||||
line = line.replace(_passwd, _newPasswd);
|
||||
} else {
|
||||
throw new Error('Invalid old Password');
|
||||
}
|
||||
}
|
||||
return line;
|
||||
});
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
39
packages/core/htpasswd/tests/__fixtures__/config.yaml
Normal file
39
packages/core/htpasswd/tests/__fixtures__/config.yaml
Normal file
@ -0,0 +1,39 @@
|
||||
storage: './test-storage'
|
||||
listen: 'http://localhost:1443/'
|
||||
auth:
|
||||
htpasswd:
|
||||
file: ./htpasswd
|
||||
# Maximum amount of users allowed to register, defaults to "+inf".
|
||||
# You can set this to -1 to disable registration.
|
||||
max_users: 1000
|
||||
|
||||
# a list of other known repositories we can talk to
|
||||
uplinks:
|
||||
npmjs:
|
||||
url: https://registry.npmjs.org/
|
||||
|
||||
packages:
|
||||
'@*/*':
|
||||
# scoped packages
|
||||
access: $all
|
||||
publish: $authenticated
|
||||
|
||||
'*':
|
||||
# allow all users (including non-authenticated users) to read and
|
||||
# publish all packages
|
||||
#
|
||||
# you can specify usernames/groupnames (depending on your auth plugin)
|
||||
# and three keywords: "$all", "$anonymous", "$authenticated"
|
||||
access: $all
|
||||
|
||||
# allow all known users to publish packages
|
||||
# (anyone can register by default, remember?)
|
||||
publish: $authenticated
|
||||
|
||||
# if package is not available locally, proxy requests to 'npmjs' registry
|
||||
proxy: npmjs
|
||||
|
||||
# log settings
|
||||
logs:
|
||||
- {type: stdout, format: pretty, level: http}
|
||||
#- {type: file, path: verdaccio.log, level: info}
|
2
packages/core/htpasswd/tests/__fixtures__/htpasswd
Normal file
2
packages/core/htpasswd/tests/__fixtures__/htpasswd
Normal file
@ -0,0 +1,2 @@
|
||||
test:$6FrCaT/v0dwE:autocreated 2018-01-17T03:40:22.958Z
|
||||
username:$66to3JK5RgZM:autocreated 2018-01-17T03:40:46.315Z
|
50
packages/core/htpasswd/tests/__mocks__/Config.js
Normal file
50
packages/core/htpasswd/tests/__mocks__/Config.js
Normal file
@ -0,0 +1,50 @@
|
||||
export default class Config {
|
||||
constructor() {
|
||||
this.storage = './test-storage';
|
||||
this.listen = 'http://localhost:1443/';
|
||||
this.auth = {
|
||||
htpasswd: {
|
||||
file: './htpasswd',
|
||||
max_users: 1000,
|
||||
},
|
||||
};
|
||||
this.uplinks = {
|
||||
npmjs: {
|
||||
url: 'https://registry.npmjs.org',
|
||||
cache: true,
|
||||
},
|
||||
};
|
||||
this.packages = {
|
||||
'@*/*': {
|
||||
access: ['$all'],
|
||||
publish: ['$authenticated'],
|
||||
proxy: [],
|
||||
},
|
||||
'*': {
|
||||
access: ['$all'],
|
||||
publish: ['$authenticated'],
|
||||
proxy: ['npmjs'],
|
||||
},
|
||||
'**': {
|
||||
access: [],
|
||||
publish: [],
|
||||
proxy: [],
|
||||
},
|
||||
};
|
||||
this.logs = [
|
||||
{
|
||||
type: 'stdout',
|
||||
format: 'pretty',
|
||||
level: 35,
|
||||
},
|
||||
];
|
||||
this.self_path = './tests/__fixtures__/config.yaml';
|
||||
this.https = {
|
||||
enable: false,
|
||||
};
|
||||
this.user_agent = 'verdaccio/3.0.0-alpha.7';
|
||||
this.users = {};
|
||||
this.server_id = '5cf430af30a1';
|
||||
this.secret = 'ebde3e3a2a789a0623bf3de58cd127f0b309f573686cc91dc6d0f8fc6214b542';
|
||||
}
|
||||
}
|
1
packages/core/htpasswd/tests/__mocks__/Logger.ts
Normal file
1
packages/core/htpasswd/tests/__mocks__/Logger.ts
Normal file
@ -0,0 +1 @@
|
||||
export default class Logger {}
|
@ -0,0 +1,17 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`addUserToHTPasswd - crypt3 should add new htpasswd to the end 1`] = `
|
||||
"username:$66to3JK5RgZM:autocreated 2018-01-14T11:17:40.712Z
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`addUserToHTPasswd - crypt3 should add new htpasswd to the end in multiline input 1`] = `
|
||||
"test1:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z
|
||||
test2:$6FrCaT/v0dwE:autocreated 2017-12-14T13:30:20.838Z
|
||||
username:$66to3JK5RgZM:autocreated 2018-01-14T11:17:40.712Z
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`addUserToHTPasswd - crypt3 should throw an error for incorrect username with space 1`] = `"username should not contain non-uri-safe characters"`;
|
||||
|
||||
exports[`changePasswordToHTPasswd should change the password 1`] = `"root:$6JaJqI5HUf.Q:autocreated 2018-08-20T13:38:12.164Z"`;
|
31
packages/core/htpasswd/tests/crypt3.test.ts
Normal file
31
packages/core/htpasswd/tests/crypt3.test.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { createSalt } from '../src/crypt3';
|
||||
|
||||
jest.mock('crypto', () => {
|
||||
return {
|
||||
randomBytes: (): { toString: () => string } => {
|
||||
return {
|
||||
toString: (): string => '/UEGzD0RxSNDZA==',
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('createSalt', () => {
|
||||
test('should match with the correct salt type', () => {
|
||||
expect(createSalt('crypt')).toEqual('/UEGzD0RxSNDZA==');
|
||||
expect(createSalt('md5')).toEqual('$1$/UEGzD0RxSNDZA==');
|
||||
expect(createSalt('blowfish')).toEqual('$2a$/UEGzD0RxSNDZA==');
|
||||
expect(createSalt('sha256')).toEqual('$5$/UEGzD0RxSNDZA==');
|
||||
expect(createSalt('sha512')).toEqual('$6$/UEGzD0RxSNDZA==');
|
||||
});
|
||||
|
||||
test('should fails on unkwon type', () => {
|
||||
expect(function () {
|
||||
createSalt('bad');
|
||||
}).toThrow(/Unknown salt type at crypt3.createSalt: bad/);
|
||||
});
|
||||
|
||||
test('should generate legacy crypt salt by default', () => {
|
||||
expect(createSalt()).toEqual(createSalt('crypt'));
|
||||
});
|
||||
});
|
285
packages/core/htpasswd/tests/htpasswd.test.ts
Normal file
285
packages/core/htpasswd/tests/htpasswd.test.ts
Normal file
@ -0,0 +1,285 @@
|
||||
/* eslint-disable jest/no-mocks-import */
|
||||
import crypto from 'crypto';
|
||||
// @ts-ignore
|
||||
import fs from 'fs';
|
||||
|
||||
import HTPasswd, { VerdaccioConfigApp } from '../src/htpasswd';
|
||||
|
||||
// FIXME: remove this mocks imports
|
||||
import Logger from './__mocks__/Logger';
|
||||
import Config from './__mocks__/Config';
|
||||
|
||||
const stuff = {
|
||||
logger: new Logger(),
|
||||
config: new Config(),
|
||||
};
|
||||
|
||||
const config = {
|
||||
file: './htpasswd',
|
||||
max_users: 1000,
|
||||
};
|
||||
|
||||
describe('HTPasswd', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = new HTPasswd(config, (stuff as unknown) as VerdaccioConfigApp);
|
||||
jest.resetModules();
|
||||
|
||||
crypto.randomBytes = jest.fn(() => {
|
||||
return {
|
||||
toString: (): string => '$6',
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor()', () => {
|
||||
test('should files whether file path does not exist', () => {
|
||||
expect(function () {
|
||||
new HTPasswd({}, ({
|
||||
config: {},
|
||||
} as unknown) as VerdaccioConfigApp);
|
||||
}).toThrow(/should specify "file" in config/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('authenticate()', () => {
|
||||
test('it should authenticate user with given credentials', (done) => {
|
||||
const callbackTest = (a, b): void => {
|
||||
expect(a).toBeNull();
|
||||
expect(b).toContain('test');
|
||||
done();
|
||||
};
|
||||
const callbackUsername = (a, b): void => {
|
||||
expect(a).toBeNull();
|
||||
expect(b).toContain('username');
|
||||
done();
|
||||
};
|
||||
wrapper.authenticate('test', 'test', callbackTest);
|
||||
wrapper.authenticate('username', 'password', callbackUsername);
|
||||
});
|
||||
|
||||
test('it should not authenticate user with given credentials', (done) => {
|
||||
const callback = (a, b): void => {
|
||||
expect(a).toBeNull();
|
||||
expect(b).toBeFalsy();
|
||||
done();
|
||||
};
|
||||
wrapper.authenticate('test', 'somerandompassword', callback);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addUser()', () => {
|
||||
test('it should not pass sanity check', (done) => {
|
||||
const callback = (a): void => {
|
||||
expect(a.message).toEqual('unauthorized access');
|
||||
done();
|
||||
};
|
||||
wrapper.adduser('test', 'somerandompassword', callback);
|
||||
});
|
||||
|
||||
test('it should add the user', (done) => {
|
||||
let dataToWrite;
|
||||
// @ts-ignore
|
||||
fs.writeFile = jest.fn((name, data, callback) => {
|
||||
dataToWrite = data;
|
||||
callback();
|
||||
});
|
||||
const callback = (a, b): void => {
|
||||
expect(a).toBeNull();
|
||||
expect(b).toBeTruthy();
|
||||
expect(fs.writeFile).toHaveBeenCalled();
|
||||
expect(dataToWrite.indexOf('usernotpresent')).not.toEqual(-1);
|
||||
done();
|
||||
};
|
||||
wrapper.adduser('usernotpresent', 'somerandompassword', callback);
|
||||
});
|
||||
|
||||
describe('addUser() error handling', () => {
|
||||
test('sanityCheck should return an Error', (done) => {
|
||||
jest.doMock('../src/utils.ts', () => {
|
||||
return {
|
||||
sanityCheck: (): Error => Error('some error'),
|
||||
};
|
||||
});
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, stuff);
|
||||
wrapper.adduser('sanityCheck', 'test', (sanity) => {
|
||||
expect(sanity.message).toBeDefined();
|
||||
expect(sanity.message).toMatch('some error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('lockAndRead should return an Error', (done) => {
|
||||
jest.doMock('../src/utils.ts', () => {
|
||||
return {
|
||||
sanityCheck: (): any => null,
|
||||
lockAndRead: (_a, b): any => b(new Error('lock error')),
|
||||
};
|
||||
});
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, stuff);
|
||||
wrapper.adduser('lockAndRead', 'test', (sanity) => {
|
||||
expect(sanity.message).toBeDefined();
|
||||
expect(sanity.message).toMatch('lock error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('addUserToHTPasswd should return an Error', (done) => {
|
||||
jest.doMock('../src/utils.ts', () => {
|
||||
return {
|
||||
sanityCheck: (): any => null,
|
||||
parseHTPasswd: (): void => {},
|
||||
lockAndRead: (_a, b): any => b(null, ''),
|
||||
unlockFile: (_a, b): any => b(),
|
||||
};
|
||||
});
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, stuff);
|
||||
wrapper.adduser('addUserToHTPasswd', 'test', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFile should return an Error', (done) => {
|
||||
jest.doMock('../src/utils.ts', () => {
|
||||
return {
|
||||
sanityCheck: (): any => null,
|
||||
parseHTPasswd: (): void => {},
|
||||
lockAndRead: (_a, b): any => b(null, ''),
|
||||
addUserToHTPasswd: (): void => {},
|
||||
};
|
||||
});
|
||||
jest.doMock('fs', () => {
|
||||
const original = jest.requireActual('fs');
|
||||
return {
|
||||
...original,
|
||||
writeFile: jest.fn((_name, _data, callback) => {
|
||||
callback(new Error('write error'));
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, stuff);
|
||||
wrapper.adduser('addUserToHTPasswd', 'test', (err) => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(err.message).toMatch('write error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('reload()', () => {
|
||||
test('it should read the file and set the users', (done) => {
|
||||
const output = { test: '$6FrCaT/v0dwE', username: '$66to3JK5RgZM' };
|
||||
const callback = (): void => {
|
||||
expect(wrapper.users).toEqual(output);
|
||||
done();
|
||||
};
|
||||
wrapper.reload(callback);
|
||||
});
|
||||
|
||||
test('reload should fails on check file', (done) => {
|
||||
jest.doMock('fs', () => {
|
||||
return {
|
||||
stat: (_name, callback): void => {
|
||||
callback(new Error('stat error'), null);
|
||||
},
|
||||
};
|
||||
});
|
||||
const callback = (err): void => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(err.message).toMatch('stat error');
|
||||
done();
|
||||
};
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, stuff);
|
||||
wrapper.reload(callback);
|
||||
});
|
||||
|
||||
test('reload times match', (done) => {
|
||||
jest.doMock('fs', () => {
|
||||
return {
|
||||
stat: (_name, callback): void => {
|
||||
callback(null, {
|
||||
mtime: null,
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
const callback = (err): void => {
|
||||
expect(err).toBeUndefined();
|
||||
done();
|
||||
};
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, stuff);
|
||||
wrapper.reload(callback);
|
||||
});
|
||||
|
||||
test('reload should fails on read file', (done) => {
|
||||
jest.doMock('fs', () => {
|
||||
return {
|
||||
stat: jest.requireActual('fs').stat,
|
||||
readFile: (_name, _format, callback): void => {
|
||||
callback(new Error('read error'), null);
|
||||
},
|
||||
};
|
||||
});
|
||||
const callback = (err): void => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(err.message).toMatch('read error');
|
||||
done();
|
||||
};
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, stuff);
|
||||
wrapper.reload(callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('changePassword - it should throw an error for user not found', (done) => {
|
||||
const callback = (error, isSuccess): void => {
|
||||
expect(error).not.toBeNull();
|
||||
expect(error.message).toBe('User not found');
|
||||
expect(isSuccess).toBeFalsy();
|
||||
done();
|
||||
};
|
||||
wrapper.changePassword('usernotpresent', 'oldPassword', 'newPassword', callback);
|
||||
});
|
||||
|
||||
test('changePassword - it should throw an error for wrong password', (done) => {
|
||||
const callback = (error, isSuccess): void => {
|
||||
expect(error).not.toBeNull();
|
||||
expect(error.message).toBe('Invalid old Password');
|
||||
expect(isSuccess).toBeFalsy();
|
||||
done();
|
||||
};
|
||||
wrapper.changePassword('username', 'wrongPassword', 'newPassword', callback);
|
||||
});
|
||||
|
||||
test('changePassword - it should change password', (done) => {
|
||||
let dataToWrite;
|
||||
// @ts-ignore
|
||||
fs.writeFile = jest.fn((_name, data, callback) => {
|
||||
dataToWrite = data;
|
||||
callback();
|
||||
});
|
||||
const callback = (error, isSuccess): void => {
|
||||
expect(error).toBeNull();
|
||||
expect(isSuccess).toBeTruthy();
|
||||
expect(fs.writeFile).toHaveBeenCalled();
|
||||
expect(dataToWrite.indexOf('username')).not.toEqual(-1);
|
||||
done();
|
||||
};
|
||||
wrapper.changePassword('username', 'password', 'newPassword', callback);
|
||||
});
|
||||
});
|
252
packages/core/htpasswd/tests/utils.test.ts
Normal file
252
packages/core/htpasswd/tests/utils.test.ts
Normal file
@ -0,0 +1,252 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { verifyPassword, lockAndRead, parseHTPasswd, addUserToHTPasswd, sanityCheck, changePasswordToHTPasswd, getCryptoPassword } from '../src/utils';
|
||||
|
||||
const mockReadFile = jest.fn();
|
||||
const mockUnlockFile = jest.fn();
|
||||
|
||||
jest.mock('@verdaccio/file-locking', () => ({
|
||||
readFile: () => mockReadFile(),
|
||||
unlockFile: () => mockUnlockFile(),
|
||||
}));
|
||||
|
||||
describe('parseHTPasswd', () => {
|
||||
it('should parse the password for a single line', () => {
|
||||
const input = 'test:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z';
|
||||
const output = { test: '$6b9MlB3WUELU' };
|
||||
expect(parseHTPasswd(input)).toEqual(output);
|
||||
});
|
||||
|
||||
it('should parse the password for two lines', () => {
|
||||
const input = `user1:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z
|
||||
user2:$6FrCaT/v0dwE:autocreated 2017-12-14T13:30:20.838Z`;
|
||||
const output = { user1: '$6b9MlB3WUELU', user2: '$6FrCaT/v0dwE' };
|
||||
expect(parseHTPasswd(input)).toEqual(output);
|
||||
});
|
||||
|
||||
it('should parse the password for multiple lines', () => {
|
||||
const input = `user1:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z
|
||||
user2:$6FrCaT/v0dwE:autocreated 2017-12-14T13:30:20.838Z
|
||||
user3:$6FrCdfd\v0dwE:autocreated 2017-12-14T13:30:20.838Z
|
||||
user4:$6FrCasdvppdwE:autocreated 2017-12-14T13:30:20.838Z`;
|
||||
const output = {
|
||||
user1: '$6b9MlB3WUELU',
|
||||
user2: '$6FrCaT/v0dwE',
|
||||
user3: '$6FrCdfd\v0dwE',
|
||||
user4: '$6FrCasdvppdwE',
|
||||
};
|
||||
expect(parseHTPasswd(input)).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyPassword', () => {
|
||||
it('should verify the MD5/Crypt3 password with true', () => {
|
||||
const input = ['test', '$apr1$sKXK9.lG$rZ4Iy63Vtn8jF9/USc4BV0'];
|
||||
expect(verifyPassword(input[0], input[1])).toBeTruthy();
|
||||
});
|
||||
it('should verify the MD5/Crypt3 password with false', () => {
|
||||
const input = ['testpasswordchanged', '$apr1$sKXK9.lG$rZ4Iy63Vtn8jF9/USc4BV0'];
|
||||
expect(verifyPassword(input[0], input[1])).toBeFalsy();
|
||||
});
|
||||
it('should verify the plain password with true', () => {
|
||||
const input = ['testpasswordchanged', '{PLAIN}testpasswordchanged'];
|
||||
expect(verifyPassword(input[0], input[1])).toBeTruthy();
|
||||
});
|
||||
it('should verify the plain password with false', () => {
|
||||
const input = ['testpassword', '{PLAIN}testpasswordchanged'];
|
||||
expect(verifyPassword(input[0], input[1])).toBeFalsy();
|
||||
});
|
||||
it('should verify the crypto SHA password with true', () => {
|
||||
const input = ['testpassword', '{SHA}i7YRj4/Wk1rQh2o740pxfTJwj/0='];
|
||||
expect(verifyPassword(input[0], input[1])).toBeTruthy();
|
||||
});
|
||||
it('should verify the crypto SHA password with false', () => {
|
||||
const input = ['testpasswordchanged', '{SHA}i7YRj4/Wk1rQh2o740pxfTJwj/0='];
|
||||
expect(verifyPassword(input[0], input[1])).toBeFalsy();
|
||||
});
|
||||
it('should verify the bcrypt password with true', () => {
|
||||
const input = ['testpassword', '$2y$04$Wqed4yN0OktGbiUdxSTwtOva1xfESfkNIZfcS9/vmHLsn3.lkFxJO'];
|
||||
expect(verifyPassword(input[0], input[1])).toBeTruthy();
|
||||
});
|
||||
it('should verify the bcrypt password with false', () => {
|
||||
const input = ['testpasswordchanged', '$2y$04$Wqed4yN0OktGbiUdxSTwtOva1xfESfkNIZfcS9/vmHLsn3.lkFxJO'];
|
||||
expect(verifyPassword(input[0], input[1])).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('addUserToHTPasswd - crypt3', () => {
|
||||
beforeAll(() => {
|
||||
// @ts-ignore
|
||||
global.Date = jest.fn(() => {
|
||||
return {
|
||||
parse: jest.fn(),
|
||||
toJSON: (): string => '2018-01-14T11:17:40.712Z',
|
||||
};
|
||||
});
|
||||
|
||||
crypto.randomBytes = jest.fn(() => {
|
||||
return {
|
||||
toString: (): string => '$6',
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should add new htpasswd to the end', () => {
|
||||
const input = ['', 'username', 'password'];
|
||||
expect(addUserToHTPasswd(input[0], input[1], input[2])).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should add new htpasswd to the end in multiline input', () => {
|
||||
const body = `test1:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z
|
||||
test2:$6FrCaT/v0dwE:autocreated 2017-12-14T13:30:20.838Z`;
|
||||
const input = [body, 'username', 'password'];
|
||||
expect(addUserToHTPasswd(input[0], input[1], input[2])).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should throw an error for incorrect username with space', () => {
|
||||
const [a, b, c] = ['', 'firstname lastname', 'password'];
|
||||
expect(() => addUserToHTPasswd(a, b, c)).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
// ToDo: mock crypt3 with false
|
||||
describe('addUserToHTPasswd - crypto', () => {
|
||||
it('should create password with crypto', () => {
|
||||
jest.resetModules();
|
||||
jest.doMock('../src/crypt3.ts', () => false);
|
||||
const input = ['', 'username', 'password'];
|
||||
const utils = require('../src/utils.ts');
|
||||
expect(utils.addUserToHTPasswd(input[0], input[1], input[2])).toEqual('username:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=:autocreated 2018-01-14T11:17:40.712Z\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('lockAndRead', () => {
|
||||
it('should call the readFile method', () => {
|
||||
// console.log(fileLocking);
|
||||
// const spy = jest.spyOn(fileLocking, 'readFile');
|
||||
const cb = (): void => {};
|
||||
lockAndRead('.htpasswd', cb);
|
||||
expect(mockReadFile).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanityCheck', () => {
|
||||
let users;
|
||||
|
||||
beforeEach(() => {
|
||||
users = { test: '$6FrCaT/v0dwE' };
|
||||
});
|
||||
|
||||
test('should throw error for user already exists', () => {
|
||||
const verifyFn = jest.fn();
|
||||
const input = sanityCheck('test', users.test, verifyFn, users, Infinity);
|
||||
expect(input.status).toEqual(401);
|
||||
expect(input.message).toEqual('unauthorized access');
|
||||
expect(verifyFn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should throw error for registration disabled of users', () => {
|
||||
const verifyFn = (): void => {};
|
||||
const input = sanityCheck('username', users.test, verifyFn, users, -1);
|
||||
expect(input.status).toEqual(409);
|
||||
expect(input.message).toEqual('user registration disabled');
|
||||
});
|
||||
|
||||
test('should throw error max number of users', () => {
|
||||
const verifyFn = (): void => {};
|
||||
const input = sanityCheck('username', users.test, verifyFn, users, 1);
|
||||
expect(input.status).toEqual(403);
|
||||
expect(input.message).toEqual('maximum amount of users reached');
|
||||
});
|
||||
|
||||
test('should not throw anything and sanity check', () => {
|
||||
const verifyFn = (): void => {};
|
||||
const input = sanityCheck('username', users.test, verifyFn, users, 2);
|
||||
expect(input).toBeNull();
|
||||
});
|
||||
|
||||
test('should throw error for required username field', () => {
|
||||
const verifyFn = (): void => {};
|
||||
const input = sanityCheck(undefined, users.test, verifyFn, users, 2);
|
||||
expect(input.message).toEqual('username and password is required');
|
||||
expect(input.status).toEqual(400);
|
||||
});
|
||||
|
||||
test('should throw error for required password field', () => {
|
||||
const verifyFn = (): void => {};
|
||||
const input = sanityCheck('username', undefined, verifyFn, users, 2);
|
||||
expect(input.message).toEqual('username and password is required');
|
||||
expect(input.status).toEqual(400);
|
||||
});
|
||||
|
||||
test('should throw error for required username & password fields', () => {
|
||||
const verifyFn = (): void => {};
|
||||
const input = sanityCheck(undefined, undefined, verifyFn, users, 2);
|
||||
expect(input.message).toEqual('username and password is required');
|
||||
expect(input.status).toEqual(400);
|
||||
});
|
||||
|
||||
test('should throw error for existing username and password', () => {
|
||||
const verifyFn = jest.fn(() => true);
|
||||
const input = sanityCheck('test', users.test, verifyFn, users, 2);
|
||||
expect(input.status).toEqual(409);
|
||||
expect(input.message).toEqual('username is already registered');
|
||||
expect(verifyFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should throw error for existing username and password with max number of users reached', () => {
|
||||
const verifyFn = jest.fn(() => true);
|
||||
const input = sanityCheck('test', users.test, verifyFn, users, 1);
|
||||
expect(input.status).toEqual(409);
|
||||
expect(input.message).toEqual('username is already registered');
|
||||
expect(verifyFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('changePasswordToHTPasswd', () => {
|
||||
test('should throw error for wrong password', () => {
|
||||
const body = 'test:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z';
|
||||
|
||||
try {
|
||||
changePasswordToHTPasswd(body, 'test', 'somerandompassword', 'newPassword');
|
||||
} catch (error) {
|
||||
expect(error.message).toEqual('Invalid old Password');
|
||||
}
|
||||
});
|
||||
|
||||
test('should change the password', () => {
|
||||
const body = 'root:$6qLTHoPfGLy2:autocreated 2018-08-20T13:38:12.164Z';
|
||||
|
||||
expect(changePasswordToHTPasswd(body, 'root', 'demo123', 'newPassword')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should generate a different result on salt change', () => {
|
||||
crypto.randomBytes = jest.fn(() => {
|
||||
return {
|
||||
toString: (): string => 'AB',
|
||||
};
|
||||
});
|
||||
|
||||
const body = 'root:$6qLTHoPfGLy2:autocreated 2018-08-20T13:38:12.164Z';
|
||||
|
||||
expect(changePasswordToHTPasswd(body, 'root', 'demo123', 'demo123')).toEqual('root:ABfaAAjDKIgfw:autocreated 2018-08-20T13:38:12.164Z');
|
||||
});
|
||||
|
||||
test('should change the password when crypt3 is not available', () => {
|
||||
jest.resetModules();
|
||||
jest.doMock('../src/crypt3.ts', () => false);
|
||||
const utils = require('../src/utils.ts');
|
||||
const body = 'username:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=:autocreated 2018-01-14T11:17:40.712Z';
|
||||
expect(utils.changePasswordToHTPasswd(body, 'username', 'password', 'newPassword')).toEqual(
|
||||
'username:{SHA}KD1HqTOO0RALX+Klr/LR98eZv9A=:autocreated 2018-01-14T11:17:40.712Z'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCryptoPassword', () => {
|
||||
test('should return the password hash', () => {
|
||||
const passwordHash = `{SHA}y9vkk2zovmMYTZ8uE/wkkjQ3G5o=`;
|
||||
|
||||
expect(getCryptoPassword('demo123')).toBe(passwordHash);
|
||||
});
|
||||
});
|
9
packages/core/htpasswd/tsconfig.json
Normal file
9
packages/core/htpasswd/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
3
packages/core/local-storage/.babelrc
Normal file
3
packages/core/local-storage/.babelrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../../.babelrc"
|
||||
}
|
5
packages/core/local-storage/.eslintrc.json
Normal file
5
packages/core/local-storage/.eslintrc.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"rules": {
|
||||
"@typescript-eslint/no-use-before-define": "off"
|
||||
}
|
||||
}
|
467
packages/core/local-storage/CHANGELOG.md
Normal file
467
packages/core/local-storage/CHANGELOG.md
Normal file
@ -0,0 +1,467 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [9.7.2](https://github.com/verdaccio/monorepo/compare/v9.7.1...v9.7.2) (2020-07-20)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.7.1](https://github.com/verdaccio/monorepo/compare/v9.7.0...v9.7.1) (2020-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update dependencies ([#375](https://github.com/verdaccio/monorepo/issues/375)) ([1e7aeec](https://github.com/verdaccio/monorepo/commit/1e7aeec31b056979285e272793a95b8c75d57c77))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.7.0](https://github.com/verdaccio/monorepo/compare/v9.6.1...v9.7.0) (2020-06-24)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.6.1](https://github.com/verdaccio/monorepo/compare/v9.6.0...v9.6.1) (2020-06-07)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.5.1](https://github.com/verdaccio/monorepo/compare/v9.5.0...v9.5.1) (2020-06-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* restore Node v8 support ([#361](https://github.com/verdaccio/monorepo/issues/361)) ([9be55a1](https://github.com/verdaccio/monorepo/commit/9be55a1deebe954e8eef9edc59af9fd16e29daed))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.5.0](https://github.com/verdaccio/monorepo/compare/v9.4.1...v9.5.0) (2020-05-02)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.4.0](https://github.com/verdaccio/monorepo/compare/v9.3.4...v9.4.0) (2020-03-21)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.4](https://github.com/verdaccio/monorepo/compare/v9.3.3...v9.3.4) (2020-03-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update mkdirp@1.0.3 ([#341](https://github.com/verdaccio/monorepo/issues/341)) ([96db337](https://github.com/verdaccio/monorepo/commit/96db3378a4f2334ec89cfb113af95e9a3a6eb050))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.2](https://github.com/verdaccio/monorepo/compare/v9.3.1...v9.3.2) (2020-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update dependencies ([#332](https://github.com/verdaccio/monorepo/issues/332)) ([b6165ae](https://github.com/verdaccio/monorepo/commit/b6165aea9b7e4012477081eae68bfa7159c58f56))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.1](https://github.com/verdaccio/monorepo/compare/v9.3.0...v9.3.1) (2020-02-23)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.3.0](https://github.com/verdaccio/monorepo/compare/v9.2.0...v9.3.0) (2020-01-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.0.0](https://github.com/verdaccio/monorepo/compare/v8.5.3...v9.0.0) (2020-01-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* prevent circular structure exception ([#312](https://github.com/verdaccio/monorepo/issues/312)) ([f565461](https://github.com/verdaccio/monorepo/commit/f565461f5bb2873467eeb4372a12fbf4a4974d17))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.5.2](https://github.com/verdaccio/monorepo/compare/v8.5.1...v8.5.2) (2019-12-25)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.5.1](https://github.com/verdaccio/monorepo/compare/v8.5.0...v8.5.1) (2019-12-24)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.5.0](https://github.com/verdaccio/monorepo/compare/v8.4.2...v8.5.0) (2019-12-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.4.2](https://github.com/verdaccio/monorepo/compare/v8.4.1...v8.4.2) (2019-11-23)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.4.1](https://github.com/verdaccio/monorepo/compare/v8.4.0...v8.4.1) (2019-11-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.4.0](https://github.com/verdaccio/monorepo/compare/v8.3.0...v8.4.0) (2019-11-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.3.0](https://github.com/verdaccio/monorepo/compare/v8.2.0...v8.3.0) (2019-10-27)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.2.0](https://github.com/verdaccio/monorepo/compare/v8.2.0-next.0...v8.2.0) (2019-10-23)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.2.0-next.0](https://github.com/verdaccio/monorepo/compare/v8.1.4...v8.2.0-next.0) (2019-10-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixed lint errors ([5e677f7](https://github.com/verdaccio/monorepo/commit/5e677f7))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.1.2](https://github.com/verdaccio/monorepo/compare/v8.1.1...v8.1.2) (2019-09-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.1.1](https://github.com/verdaccio/monorepo/compare/v8.1.0...v8.1.1) (2019-09-26)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.1.0](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.1...v8.1.0) (2019-09-07)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.0.1-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.0...v8.0.1-next.1) (2019-08-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.0.1-next.0](https://github.com/verdaccio/monorepo/compare/v8.0.0...v8.0.1-next.0) (2019-08-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.4...v8.0.0) (2019-08-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.4](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.3...v8.0.0-next.4) (2019-08-18)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/local-storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.3](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.2...v8.0.0-next.3) (2019-08-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* restore closure ([32b9d7e](https://github.com/verdaccio/monorepo/commit/32b9d7e))
|
||||
* **build:** error on types for fs callback ([cc35acb](https://github.com/verdaccio/monorepo/commit/cc35acb))
|
||||
* Add DATE and VERSION in search result ([e352b75](https://github.com/verdaccio/monorepo/commit/e352b75))
|
||||
* avoid open write stream if resource exist [#1191](https://github.com/verdaccio/monorepo/issues/1191) ([f041d3f](https://github.com/verdaccio/monorepo/commit/f041d3f))
|
||||
* bug fixing integration ([6c75ac8](https://github.com/verdaccio/monorepo/commit/6c75ac8))
|
||||
* build before publish ([cd6c7ff](https://github.com/verdaccio/monorepo/commit/cd6c7ff))
|
||||
* check whether path exist before return result ([a4d2af1](https://github.com/verdaccio/monorepo/commit/a4d2af1))
|
||||
* flow issues ([f42a284](https://github.com/verdaccio/monorepo/commit/f42a284))
|
||||
* ignore flow on this one, we need it ([c8e0b2b](https://github.com/verdaccio/monorepo/commit/c8e0b2b))
|
||||
* local storage requires package.json file for read, save and create all the time ([33c847b](https://github.com/verdaccio/monorepo/commit/33c847b))
|
||||
* migration from main repository merge [#306](https://github.com/verdaccio/monorepo/issues/306) ([8fbe86e](https://github.com/verdaccio/monorepo/commit/8fbe86e))
|
||||
* missing callback ([abfc422](https://github.com/verdaccio/monorepo/commit/abfc422))
|
||||
* missing error code ([7121939](https://github.com/verdaccio/monorepo/commit/7121939))
|
||||
* move to local storage the fs location handler ([3b12083](https://github.com/verdaccio/monorepo/commit/3b12083))
|
||||
* mtimeMs is not backward compatible ([c6f74eb](https://github.com/verdaccio/monorepo/commit/c6f74eb))
|
||||
* remove temp file whether is emtpy and fails ([655102f](https://github.com/verdaccio/monorepo/commit/655102f))
|
||||
* remove uncessary async ([3e3e3a6](https://github.com/verdaccio/monorepo/commit/3e3e3a6))
|
||||
* remove unused parameters ([554e301](https://github.com/verdaccio/monorepo/commit/554e301))
|
||||
* restore build path ([4902042](https://github.com/verdaccio/monorepo/commit/4902042))
|
||||
* return time as milliseconds ([15467ba](https://github.com/verdaccio/monorepo/commit/15467ba))
|
||||
* sync after set secret ([2abae4f](https://github.com/verdaccio/monorepo/commit/2abae4f))
|
||||
* temp files are written into the storage ([89a1dc8](https://github.com/verdaccio/monorepo/commit/89a1dc8))
|
||||
* unit test ([995a27c](https://github.com/verdaccio/monorepo/commit/995a27c))
|
||||
* update @verdaccio/file-locking@1.0.0 ([9bd36f0](https://github.com/verdaccio/monorepo/commit/9bd36f0))
|
||||
* update lodash types ([184466c](https://github.com/verdaccio/monorepo/commit/184466c))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* token support with level.js ([#168](https://github.com/verdaccio/monorepo/issues/168)) ([ca877ff](https://github.com/verdaccio/monorepo/commit/ca877ff))
|
||||
* **build:** standardize build ([33fe090](https://github.com/verdaccio/monorepo/commit/33fe090))
|
||||
* change new db name to verdaccio ([#83](https://github.com/verdaccio/monorepo/issues/83)) ([edfca9f](https://github.com/verdaccio/monorepo/commit/edfca9f))
|
||||
* drop node v6 support ([664f288](https://github.com/verdaccio/monorepo/commit/664f288))
|
||||
* implement search ([2e2bb32](https://github.com/verdaccio/monorepo/commit/2e2bb32))
|
||||
* migrate to typescript ([c439d25](https://github.com/verdaccio/monorepo/commit/c439d25))
|
||||
* update database method with callbacks ([ef202a9](https://github.com/verdaccio/monorepo/commit/ef202a9))
|
||||
* update minor dependencies ([007b026](https://github.com/verdaccio/monorepo/commit/007b026))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## [2.3.0](https://github.com/verdaccio/local-storage/compare/v2.2.1...v2.3.0) (2019-08-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* restore closure ([8ec27f2](https://github.com/verdaccio/local-storage/commit/8ec27f2))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add logging for each method ([3c915c7](https://github.com/verdaccio/local-storage/commit/3c915c7))
|
||||
* token support with level.js ([#168](https://github.com/verdaccio/local-storage/issues/168)) ([16727bd](https://github.com/verdaccio/local-storage/commit/16727bd))
|
||||
|
||||
### [2.2.1](https://github.com/verdaccio/local-storage/compare/v2.2.0...v2.2.1) (2019-06-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** error on types for fs callback ([774d808](https://github.com/verdaccio/local-storage/commit/774d808))
|
||||
|
||||
|
||||
|
||||
## [2.2.0](https://github.com/verdaccio/local-storage/compare/v2.1.0...v2.2.0) (2019-06-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **build:** standardize build ([eba832e](https://github.com/verdaccio/local-storage/commit/eba832e))
|
||||
|
||||
|
||||
|
||||
# [2.1.0](https://github.com/verdaccio/local-storage/compare/v2.0.0...v2.1.0) (2019-03-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove uncessary async ([23a09f3](https://github.com/verdaccio/local-storage/commit/23a09f3))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* drop node v6 support ([ef548e0](https://github.com/verdaccio/local-storage/commit/ef548e0))
|
||||
|
||||
|
||||
|
||||
# [2.0.0](https://github.com/verdaccio/local-storage/compare/v2.0.0-beta.3...v2.0.0) (2019-03-29)
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-beta.3"></a>
|
||||
# [2.0.0-beta.3](https://github.com/verdaccio/local-storage/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2019-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update [@verdaccio](https://github.com/verdaccio)/file-locking@1.0.0 ([587245d](https://github.com/verdaccio/local-storage/commit/587245d))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-beta.2"></a>
|
||||
# [2.0.0-beta.2](https://github.com/verdaccio/local-storage/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2019-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* avoid open write stream if resource exist [#1191](https://github.com/verdaccio/local-storage/issues/1191) ([b13904a](https://github.com/verdaccio/local-storage/commit/b13904a))
|
||||
* package.json to reduce vulnerabilities ([97e9dc3](https://github.com/verdaccio/local-storage/commit/97e9dc3))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-beta.1"></a>
|
||||
# [2.0.0-beta.1](https://github.com/verdaccio/local-storage/compare/v2.0.0-beta.0...v2.0.0-beta.1) (2019-02-03)
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-beta.0"></a>
|
||||
# [2.0.0-beta.0](https://github.com/verdaccio/local-storage/compare/v1.2.0...v2.0.0-beta.0) (2019-02-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency lodash to v4.17.11 ([682616a](https://github.com/verdaccio/local-storage/commit/682616a))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* custom storage location ([b1423cd](https://github.com/verdaccio/local-storage/commit/b1423cd))
|
||||
* migrate to typescript ([fe8344b](https://github.com/verdaccio/local-storage/commit/fe8344b))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* we change from boolean value to string within the config file
|
||||
|
||||
|
||||
|
||||
<a name="1.2.0"></a>
|
||||
# [1.2.0](https://github.com/verdaccio/local-storage/compare/v1.1.3...v1.2.0) (2018-08-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* change new db name to verdaccio ([#83](https://github.com/verdaccio/local-storage/issues/83)) ([143977d](https://github.com/verdaccio/local-storage/commit/143977d))
|
||||
|
||||
|
||||
|
||||
<a name="1.1.3"></a>
|
||||
## [1.1.3](https://github.com/verdaccio/local-storage/compare/v1.1.2...v1.1.3) (2018-07-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove unused parameters ([3ce374a](https://github.com/verdaccio/local-storage/commit/3ce374a))
|
||||
|
||||
|
||||
|
||||
<a name="1.1.2"></a>
|
||||
## [1.1.2](https://github.com/verdaccio/local-storage/compare/v1.1.1...v1.1.2) (2018-06-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* return time as milliseconds ([c98be85](https://github.com/verdaccio/local-storage/commit/c98be85))
|
||||
|
||||
|
||||
|
||||
<a name="1.1.1"></a>
|
||||
## [1.1.1](https://github.com/verdaccio/local-storage/compare/v1.1.0...v1.1.1) (2018-06-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check whether path exist before return result ([cb5d4ef](https://github.com/verdaccio/local-storage/commit/cb5d4ef))
|
||||
|
||||
|
||||
|
||||
<a name="1.1.0"></a>
|
||||
# [1.1.0](https://github.com/verdaccio/local-storage/compare/v1.0.3...v1.1.0) (2018-06-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency async to v2.6.1 ([487b095](https://github.com/verdaccio/local-storage/commit/487b095))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* implement search ([f884a24](https://github.com/verdaccio/local-storage/commit/f884a24))
|
||||
|
||||
|
||||
|
||||
<a name="0.2.0"></a>
|
||||
# [0.2.0](https://github.com/verdaccio/local-storage/compare/v0.1.4...v0.2.0) (2018-01-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* update minor dependencies ([92daa81](https://github.com/verdaccio/local-storage/commit/92daa81))
|
||||
|
||||
|
||||
|
||||
<a name="0.1.4"></a>
|
||||
## [0.1.4](https://github.com/verdaccio/local-storage/compare/v0.1.3...v0.1.4) (2018-01-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove temp file whether is emtpy and fails ([593e162](https://github.com/verdaccio/local-storage/commit/593e162))
|
||||
* unit test ([2573f30](https://github.com/verdaccio/local-storage/commit/2573f30))
|
21
packages/core/local-storage/LICENSE
Normal file
21
packages/core/local-storage/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Verdaccio
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
49
packages/core/local-storage/README.md
Normal file
49
packages/core/local-storage/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# @verdaccio/local-storage
|
||||
|
||||
📦 File system storage plugin for verdaccio
|
||||
|
||||
[![verdaccio (latest)](https://img.shields.io/npm/v/@verdaccio/local-storage/latest.svg)](https://www.npmjs.com/package/@verdaccio/local-storage)
|
||||
[![CircleCI](https://circleci.com/gh/verdaccio/local-storage/tree/master.svg?style=svg)](https://circleci.com/gh/verdaccio/local-storage/tree/master)
|
||||
[![Known Vulnerabilities](https://snyk.io/test/github/verdaccio/local-storage/badge.svg?targetFile=package.json)](https://snyk.io/test/github/verdaccio/local-storage?targetFile=package.json)
|
||||
[![codecov](https://codecov.io/gh/verdaccio/local-storage/branch/master/graph/badge.svg)](https://codecov.io/gh/verdaccio/local-storage)
|
||||
[![backers](https://opencollective.com/verdaccio/tiers/backer/badge.svg?label=Backer&color=brightgreen)](https://opencollective.com/verdaccio)
|
||||
[![discord](https://img.shields.io/discord/388674437219745793.svg)](http://chat.verdaccio.org/)
|
||||
![MIT](https://img.shields.io/github/license/mashape/apistatus.svg)
|
||||
[![node](https://img.shields.io/node/v/@verdaccio/local-storage/latest.svg)](https://www.npmjs.com/package/@verdaccio/local-storage)
|
||||
|
||||
> This package is already built-in in verdaccio
|
||||
|
||||
```
|
||||
npm install @verdaccio/local-storage
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
### LocalDatabase
|
||||
|
||||
The main object that handle a JSON database the private packages.
|
||||
|
||||
#### Constructor
|
||||
|
||||
```
|
||||
new LocalDatabase(config, logger);
|
||||
```
|
||||
|
||||
* **config**: A verdaccio configuration instance.
|
||||
* **logger**: A logger instance
|
||||
|
||||
### LocalFS
|
||||
|
||||
A class that handle an package instance in the File System
|
||||
|
||||
```
|
||||
new LocalFS(packageStoragePath, logger);
|
||||
```
|
||||
|
||||
|
||||
|
||||
## License
|
||||
Verdaccio is [MIT licensed](https://github.com/verdaccio/local-storage/blob/master/LICENSE).
|
||||
|
||||
|
||||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fverdaccio%2Flocal-storage.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fverdaccio%2Flocal-storage?ref=badge_large)
|
@ -0,0 +1 @@
|
||||
{}
|
BIN
packages/core/local-storage/_storage/new-readme-0.0.0.tgz
Normal file
BIN
packages/core/local-storage/_storage/new-readme-0.0.0.tgz
Normal file
Binary file not shown.
3
packages/core/local-storage/jest.config.js
Normal file
3
packages/core/local-storage/jest.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
const config = require('../../../jest/config');
|
||||
|
||||
module.exports = Object.assign({}, config, {});
|
60
packages/core/local-storage/package.json
Normal file
60
packages/core/local-storage/package.json
Normal file
@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "@verdaccio/local-storage",
|
||||
"version": "10.0.0-beta",
|
||||
"description": "Local storage implementation",
|
||||
"keywords": [
|
||||
"plugin",
|
||||
"verdaccio",
|
||||
"storage",
|
||||
"local-storage"
|
||||
],
|
||||
"author": "Juan Picado <juanpicado19@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://verdaccio.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/verdaccio/monorepo",
|
||||
"directory": "plugins/local-storage"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/verdaccio/monorepo/issues"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"main": "./build/index.js",
|
||||
"types": "./build/index.d.ts",
|
||||
"files": [
|
||||
"build/"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@verdaccio/commons-api": "^9.7.1",
|
||||
"@verdaccio/file-locking": "workspace:*",
|
||||
"@verdaccio/streams": "workspace:*",
|
||||
"async": "^3.2.0",
|
||||
"level": "5.0.1",
|
||||
"lodash": "^4.17.19",
|
||||
"mkdirp": "^0.5.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/minimatch": "^3.0.3",
|
||||
"@verdaccio/types": "workspace:*",
|
||||
"minimatch": "^3.0.4",
|
||||
"rmdir-sync": "^1.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"type-check": "tsc --noEmit",
|
||||
"build:types": "tsc --emitDeclarationOnly --declaration true",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"build": "pnpm run build:js && pnpm run build:types"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/verdaccio"
|
||||
}
|
||||
}
|
5
packages/core/local-storage/src/index.ts
Normal file
5
packages/core/local-storage/src/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import LocalDatabase from './local-database';
|
||||
|
||||
export { LocalDatabase };
|
||||
|
||||
export default LocalDatabase;
|
434
packages/core/local-storage/src/local-database.ts
Normal file
434
packages/core/local-storage/src/local-database.ts
Normal file
@ -0,0 +1,434 @@
|
||||
import fs from 'fs';
|
||||
import Path from 'path';
|
||||
import stream from 'stream';
|
||||
|
||||
import _ from 'lodash';
|
||||
import async from 'async';
|
||||
import mkdirp from 'mkdirp';
|
||||
import { Callback, Config, IPackageStorage, IPluginStorage, LocalStorage, Logger, StorageList, Token, TokenFilter } from '@verdaccio/types';
|
||||
import level from 'level';
|
||||
import { getInternalError } from '@verdaccio/commons-api/lib';
|
||||
|
||||
import LocalDriver, { noSuchFile } from './local-fs';
|
||||
import { loadPrivatePackages } from './pkg-utils';
|
||||
|
||||
const DEPRECATED_DB_NAME = '.sinopia-db.json';
|
||||
const DB_NAME = '.verdaccio-db.json';
|
||||
const TOKEN_DB_NAME = '.token-db';
|
||||
|
||||
interface Level {
|
||||
put(key: string, token, fn?: Function): void;
|
||||
|
||||
get(key: string, fn?: Function): void;
|
||||
|
||||
del(key: string, fn?: Function): void;
|
||||
|
||||
createReadStream(options?: object): stream.Readable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle local database.
|
||||
*/
|
||||
class LocalDatabase implements IPluginStorage<{}> {
|
||||
public path: string;
|
||||
public logger: Logger;
|
||||
public data: LocalStorage;
|
||||
public config: Config;
|
||||
public locked: boolean;
|
||||
public tokenDb;
|
||||
|
||||
/**
|
||||
* Load an parse the local json database.
|
||||
* @param {*} path the database path
|
||||
*/
|
||||
public constructor(config: Config, logger: Logger) {
|
||||
this.config = config;
|
||||
this.path = this._buildStoragePath(config);
|
||||
this.logger = logger;
|
||||
this.locked = false;
|
||||
this.data = this._fetchLocalPackages();
|
||||
|
||||
this.logger.trace({ config: this.config }, '[local-storage]: configuration: @{config}');
|
||||
|
||||
this._sync();
|
||||
}
|
||||
|
||||
public getSecret(): Promise<string> {
|
||||
return Promise.resolve(this.data.secret);
|
||||
}
|
||||
|
||||
public setSecret(secret: string): Promise<Error | null> {
|
||||
return new Promise((resolve): void => {
|
||||
this.data.secret = secret;
|
||||
|
||||
resolve(this._sync());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new element.
|
||||
* @param {*} name
|
||||
* @return {Error|*}
|
||||
*/
|
||||
public add(name: string, cb: Callback): void {
|
||||
if (this.data.list.indexOf(name) === -1) {
|
||||
this.data.list.push(name);
|
||||
|
||||
this.logger.debug({ name }, '[local-storage]: the private package @{name} has been added');
|
||||
cb(this._sync());
|
||||
} else {
|
||||
cb(null);
|
||||
}
|
||||
}
|
||||
|
||||
public search(onPackage: Callback, onEnd: Callback, validateName: (name: string) => boolean): void {
|
||||
const storages = this._getCustomPackageLocalStorages();
|
||||
this.logger.trace(`local-storage: [search]: ${JSON.stringify(storages)}`);
|
||||
const base = Path.dirname(this.config.self_path);
|
||||
const self = this;
|
||||
const storageKeys = Object.keys(storages);
|
||||
this.logger.trace(`local-storage: [search] base: ${base} keys ${storageKeys}`);
|
||||
|
||||
async.eachSeries(
|
||||
storageKeys,
|
||||
function (storage, cb) {
|
||||
const position = storageKeys.indexOf(storage);
|
||||
const base2 = Path.join(position !== 0 ? storageKeys[0] : '');
|
||||
const storagePath: string = Path.resolve(base, base2, storage);
|
||||
self.logger.trace({ storagePath, storage }, 'local-storage: [search] search path: @{storagePath} : @{storage}');
|
||||
fs.readdir(storagePath, (err, files) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
async.eachSeries(
|
||||
files,
|
||||
function (file, cb) {
|
||||
self.logger.trace({ file }, 'local-storage: [search] search file path: @{file}');
|
||||
if (storageKeys.includes(file)) {
|
||||
return cb();
|
||||
}
|
||||
|
||||
if (file.match(/^@/)) {
|
||||
// scoped
|
||||
const fileLocation = Path.resolve(base, storage, file);
|
||||
self.logger.trace({ fileLocation }, 'local-storage: [search] search scoped file location: @{fileLocation}');
|
||||
fs.readdir(fileLocation, function (err, files) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
async.eachSeries(
|
||||
files,
|
||||
(file2, cb) => {
|
||||
if (validateName(file2)) {
|
||||
const packagePath = Path.resolve(base, storage, file, file2);
|
||||
|
||||
fs.stat(packagePath, (err, stats) => {
|
||||
if (_.isNil(err) === false) {
|
||||
return cb(err);
|
||||
}
|
||||
const item = {
|
||||
name: `${file}/${file2}`,
|
||||
path: packagePath,
|
||||
time: stats.mtime.getTime(),
|
||||
};
|
||||
onPackage(item, cb);
|
||||
});
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
},
|
||||
cb
|
||||
);
|
||||
});
|
||||
} else if (validateName(file)) {
|
||||
const base2 = Path.join(position !== 0 ? storageKeys[0] : '');
|
||||
const packagePath = Path.resolve(base, base2, storage, file);
|
||||
self.logger.trace({ packagePath }, 'local-storage: [search] search file location: @{packagePath}');
|
||||
fs.stat(packagePath, (err, stats) => {
|
||||
if (_.isNil(err) === false) {
|
||||
return cb(err);
|
||||
}
|
||||
onPackage(
|
||||
{
|
||||
name: file,
|
||||
path: packagePath,
|
||||
time: self._getTime(stats.mtime.getTime(), stats.mtime),
|
||||
},
|
||||
cb
|
||||
);
|
||||
});
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
},
|
||||
cb
|
||||
);
|
||||
});
|
||||
},
|
||||
// @ts-ignore
|
||||
onEnd
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element from the database.
|
||||
* @param {*} name
|
||||
* @return {Error|*}
|
||||
*/
|
||||
public remove(name: string, cb: Callback): void {
|
||||
this.get((err, data) => {
|
||||
if (err) {
|
||||
cb(getInternalError('error remove private package'));
|
||||
this.logger.error({ err }, '[local-storage/remove]: remove the private package has failed @{err}');
|
||||
}
|
||||
|
||||
const pkgName = data.indexOf(name);
|
||||
if (pkgName !== -1) {
|
||||
this.data.list.splice(pkgName, 1);
|
||||
|
||||
this.logger.trace({ name }, 'local-storage: [remove] package @{name} has been removed');
|
||||
}
|
||||
|
||||
cb(this._sync());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all database elements.
|
||||
* @return {Array}
|
||||
*/
|
||||
public get(cb: Callback): void {
|
||||
const list = this.data.list;
|
||||
const totalItems = this.data.list.length;
|
||||
|
||||
cb(null, list);
|
||||
|
||||
this.logger.trace({ totalItems }, 'local-storage: [get] full list of packages (@{totalItems}) has been fetched');
|
||||
}
|
||||
|
||||
public getPackageStorage(packageName: string): IPackageStorage {
|
||||
const packageAccess = this.config.getMatchedPackagesSpec(packageName);
|
||||
|
||||
const packagePath: string = this._getLocalStoragePath(packageAccess ? packageAccess.storage : undefined);
|
||||
this.logger.trace({ packagePath }, '[local-storage/getPackageStorage]: storage selected: @{packagePath}');
|
||||
|
||||
if (_.isString(packagePath) === false) {
|
||||
this.logger.debug({ name: packageName }, 'this package has no storage defined: @{name}');
|
||||
return;
|
||||
}
|
||||
|
||||
const packageStoragePath: string = Path.join(Path.resolve(Path.dirname(this.config.self_path || ''), packagePath), packageName);
|
||||
|
||||
this.logger.trace({ packageStoragePath }, '[local-storage/getPackageStorage]: storage path: @{packageStoragePath}');
|
||||
|
||||
return new LocalDriver(packageStoragePath, this.logger);
|
||||
}
|
||||
|
||||
public clean(): void {
|
||||
this._sync();
|
||||
}
|
||||
|
||||
public saveToken(token: Token): Promise<void> {
|
||||
const key = this._getTokenKey(token);
|
||||
const db = this.getTokenDb();
|
||||
|
||||
return new Promise((resolve, reject): void => {
|
||||
db.put(key, token, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public deleteToken(user: string, tokenKey: string): Promise<void> {
|
||||
const key = this._compoundTokenKey(user, tokenKey);
|
||||
const db = this.getTokenDb();
|
||||
return new Promise((resolve, reject): void => {
|
||||
db.del(key, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public readTokens(filter: TokenFilter): Promise<Token[]> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
const tokens: Token[] = [];
|
||||
const key = filter.user + ':';
|
||||
const db = this.getTokenDb();
|
||||
const stream = db.createReadStream({
|
||||
gte: key,
|
||||
lte: String.fromCharCode(key.charCodeAt(0) + 1),
|
||||
});
|
||||
|
||||
stream.on('data', (data) => {
|
||||
tokens.push(data.value);
|
||||
});
|
||||
|
||||
stream.once('end', () => resolve(tokens));
|
||||
|
||||
stream.once('error', (err) => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
private _getTime(time: number, mtime: Date): number | Date {
|
||||
return time ? time : mtime;
|
||||
}
|
||||
|
||||
private _getCustomPackageLocalStorages(): object {
|
||||
const storages = {};
|
||||
|
||||
// add custom storage if exist
|
||||
if (this.config.storage) {
|
||||
storages[this.config.storage] = true;
|
||||
}
|
||||
|
||||
const { packages } = this.config;
|
||||
|
||||
if (packages) {
|
||||
const listPackagesConf = Object.keys(packages || {});
|
||||
|
||||
listPackagesConf.map((pkg) => {
|
||||
const storage = packages[pkg].storage;
|
||||
if (storage) {
|
||||
storages[storage] = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return storages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncronize {create} database whether does not exist.
|
||||
* @return {Error|*}
|
||||
*/
|
||||
private _sync(): Error | null {
|
||||
this.logger.debug('[local-storage/_sync]: init sync database');
|
||||
|
||||
if (this.locked) {
|
||||
this.logger.error('Database is locked, please check error message printed during startup to prevent data loss.');
|
||||
return new Error('Verdaccio database is locked, please contact your administrator to checkout logs during verdaccio startup.');
|
||||
}
|
||||
// Uses sync to prevent ugly race condition
|
||||
try {
|
||||
// https://www.npmjs.com/package/mkdirp#mkdirpsyncdir-opts
|
||||
const folderName = Path.dirname(this.path);
|
||||
mkdirp.sync(folderName);
|
||||
this.logger.debug({ folderName }, '[local-storage/_sync]: folder @{folderName} created succeed');
|
||||
} catch (err) {
|
||||
// perhaps a logger instance?
|
||||
this.logger.debug({ err }, '[local-storage/_sync/mkdirp.sync]: sync failed @{err}');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
fs.writeFileSync(this.path, JSON.stringify(this.data));
|
||||
this.logger.debug('[local-storage/_sync/writeFileSync]: sync write succeed');
|
||||
|
||||
return null;
|
||||
} catch (err) {
|
||||
this.logger.debug({ err }, '[local-storage/_sync/writeFileSync]: sync failed @{err}');
|
||||
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the right local storage location.
|
||||
* @param {String} path
|
||||
* @return {String}
|
||||
* @private
|
||||
*/
|
||||
private _getLocalStoragePath(storage: string | void): string {
|
||||
const globalConfigStorage = this.config ? this.config.storage : undefined;
|
||||
if (_.isNil(globalConfigStorage)) {
|
||||
throw new Error('global storage is required for this plugin');
|
||||
} else {
|
||||
if (_.isNil(storage) === false && _.isString(storage)) {
|
||||
return Path.join(globalConfigStorage as string, storage as string);
|
||||
}
|
||||
|
||||
return globalConfigStorage as string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the local database path.
|
||||
* @param {Object} config
|
||||
* @return {string|String|*}
|
||||
* @private
|
||||
*/
|
||||
private _buildStoragePath(config: Config): string {
|
||||
const sinopiadbPath: string = this._dbGenPath(DEPRECATED_DB_NAME, config);
|
||||
try {
|
||||
fs.accessSync(sinopiadbPath, fs.constants.F_OK);
|
||||
return sinopiadbPath;
|
||||
} catch (err) {
|
||||
if (err.code === noSuchFile) {
|
||||
return this._dbGenPath(DB_NAME, config);
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private _dbGenPath(dbName: string, config: Config): string {
|
||||
return Path.join(Path.resolve(Path.dirname(config.self_path || ''), config.storage as string, dbName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch local packages.
|
||||
* @private
|
||||
* @return {Object}
|
||||
*/
|
||||
private _fetchLocalPackages(): LocalStorage {
|
||||
const list: StorageList = [];
|
||||
const emptyDatabase = { list, secret: '' };
|
||||
|
||||
try {
|
||||
const db = loadPrivatePackages(this.path, this.logger);
|
||||
|
||||
return db;
|
||||
} catch (err) {
|
||||
// readFileSync is platform specific, macOS, Linux and Windows thrown an error
|
||||
// Only recreate if file not found to prevent data loss
|
||||
if (err.code !== noSuchFile) {
|
||||
this.locked = true;
|
||||
this.logger.error('Failed to read package database file, please check the error printed below:\n', `File Path: ${this.path}\n\n ${err.message}`);
|
||||
}
|
||||
|
||||
return emptyDatabase;
|
||||
}
|
||||
}
|
||||
|
||||
private getTokenDb(): Level {
|
||||
if (!this.tokenDb) {
|
||||
this.tokenDb = level(this._dbGenPath(TOKEN_DB_NAME, this.config), {
|
||||
valueEncoding: 'json',
|
||||
});
|
||||
}
|
||||
|
||||
return this.tokenDb;
|
||||
}
|
||||
|
||||
private _getTokenKey(token: Token): string {
|
||||
const { user, key } = token;
|
||||
return this._compoundTokenKey(user, key);
|
||||
}
|
||||
|
||||
private _compoundTokenKey(user: string, key: string): string {
|
||||
return `${user}:${key}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default LocalDatabase;
|
384
packages/core/local-storage/src/local-fs.ts
Normal file
384
packages/core/local-storage/src/local-fs.ts
Normal file
@ -0,0 +1,384 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import _ from 'lodash';
|
||||
import mkdirp from 'mkdirp';
|
||||
import { UploadTarball, ReadTarball } from '@verdaccio/streams';
|
||||
import { unlockFile, readFile } from '@verdaccio/file-locking';
|
||||
import { Callback, Logger, Package, ILocalPackageManager, IUploadTarball } from '@verdaccio/types';
|
||||
import { getCode, getInternalError, getNotFound, VerdaccioError } from '@verdaccio/commons-api/lib';
|
||||
|
||||
export const fileExist = 'EEXISTS';
|
||||
export const noSuchFile = 'ENOENT';
|
||||
export const resourceNotAvailable = 'EAGAIN';
|
||||
export const pkgFileName = 'package.json';
|
||||
|
||||
export const fSError = function (message: string, code = 409): VerdaccioError {
|
||||
const err: VerdaccioError = getCode(code, message);
|
||||
// FIXME: we should return http-status codes here instead, future improvement
|
||||
// @ts-ignore
|
||||
err.code = message;
|
||||
|
||||
return err;
|
||||
};
|
||||
|
||||
const tempFile = function (str): string {
|
||||
return `${str}.tmp${String(Math.random()).substr(2)}`;
|
||||
};
|
||||
|
||||
const renameTmp = function (src, dst, _cb): void {
|
||||
const cb = (err): void => {
|
||||
if (err) {
|
||||
fs.unlink(src, () => {});
|
||||
}
|
||||
_cb(err);
|
||||
};
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
return fs.rename(src, dst, cb);
|
||||
}
|
||||
|
||||
// windows can't remove opened file,
|
||||
// but it seem to be able to rename it
|
||||
const tmp = tempFile(dst);
|
||||
fs.rename(dst, tmp, function (err) {
|
||||
fs.rename(src, dst, cb);
|
||||
if (!err) {
|
||||
fs.unlink(tmp, () => {});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export type ILocalFSPackageManager = ILocalPackageManager & { path: string };
|
||||
|
||||
export default class LocalFS implements ILocalFSPackageManager {
|
||||
public path: string;
|
||||
public logger: Logger;
|
||||
|
||||
public constructor(path: string, logger: Logger) {
|
||||
this.path = path;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows to update the package thread-safely
|
||||
Algorithm:
|
||||
1. lock package.json for writing
|
||||
2. read package.json
|
||||
3. updateFn(pkg, cb), and wait for cb
|
||||
4. write package.json.tmp
|
||||
5. move package.json.tmp package.json
|
||||
6. callback(err?)
|
||||
* @param {*} name
|
||||
* @param {*} updateHandler
|
||||
* @param {*} onWrite
|
||||
* @param {*} transformPackage
|
||||
* @param {*} onEnd
|
||||
*/
|
||||
public updatePackage(name: string, updateHandler: Callback, onWrite: Callback, transformPackage: Function, onEnd: Callback): void {
|
||||
this._lockAndReadJSON(pkgFileName, (err, json) => {
|
||||
let locked = false;
|
||||
const self = this;
|
||||
// callback that cleans up lock first
|
||||
const unLockCallback = function (lockError: Error): void {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
const _args = arguments;
|
||||
|
||||
if (locked) {
|
||||
self._unlockJSON(pkgFileName, () => {
|
||||
// ignore any error from the unlock
|
||||
if (lockError !== null) {
|
||||
self.logger.trace(
|
||||
{
|
||||
name,
|
||||
lockError,
|
||||
},
|
||||
'[local-storage/updatePackage/unLockCallback] file: @{name} lock has failed lockError: @{lockError}'
|
||||
);
|
||||
}
|
||||
|
||||
onEnd.apply(lockError, _args);
|
||||
});
|
||||
} else {
|
||||
self.logger.trace({ name }, '[local-storage/updatePackage/unLockCallback] file: @{name} has been updated');
|
||||
|
||||
onEnd(..._args);
|
||||
}
|
||||
};
|
||||
|
||||
if (!err) {
|
||||
locked = true;
|
||||
this.logger.trace(
|
||||
{
|
||||
name,
|
||||
},
|
||||
'[local-storage/updatePackage] file: @{name} has been locked'
|
||||
);
|
||||
}
|
||||
|
||||
if (_.isNil(err) === false) {
|
||||
if (err.code === resourceNotAvailable) {
|
||||
return unLockCallback(getInternalError('resource temporarily unavailable'));
|
||||
} else if (err.code === noSuchFile) {
|
||||
return unLockCallback(getNotFound());
|
||||
} else {
|
||||
return unLockCallback(err);
|
||||
}
|
||||
}
|
||||
|
||||
updateHandler(json, (err) => {
|
||||
if (err) {
|
||||
return unLockCallback(err);
|
||||
}
|
||||
|
||||
onWrite(name, transformPackage(json), unLockCallback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public deletePackage(packageName: string, callback: (err: NodeJS.ErrnoException | null) => void): void {
|
||||
this.logger.debug({ packageName }, '[local-storage/deletePackage] delete a package @{packageName}');
|
||||
|
||||
return fs.unlink(this._getStorage(packageName), callback);
|
||||
}
|
||||
|
||||
public removePackage(callback: (err: NodeJS.ErrnoException | null) => void): void {
|
||||
this.logger.debug({ packageName: this.path }, '[local-storage/removePackage] remove a package: @{packageName}');
|
||||
|
||||
fs.rmdir(this._getStorage('.'), callback);
|
||||
}
|
||||
|
||||
public createPackage(name: string, value: Package, cb: Callback): void {
|
||||
this.logger.debug({ packageName: name }, '[local-storage/createPackage] create a package: @{packageName}');
|
||||
|
||||
this._createFile(this._getStorage(pkgFileName), this._convertToString(value), cb);
|
||||
}
|
||||
|
||||
public savePackage(name: string, value: Package, cb: Callback): void {
|
||||
this.logger.debug({ packageName: name }, '[local-storage/savePackage] save a package: @{packageName}');
|
||||
|
||||
this._writeFile(this._getStorage(pkgFileName), this._convertToString(value), cb);
|
||||
}
|
||||
|
||||
public readPackage(name: string, cb: Callback): void {
|
||||
this.logger.debug({ packageName: name }, '[local-storage/readPackage] read a package: @{packageName}');
|
||||
|
||||
this._readStorageFile(this._getStorage(pkgFileName)).then(
|
||||
(res) => {
|
||||
try {
|
||||
const data: any = JSON.parse(res.toString('utf8'));
|
||||
|
||||
this.logger.trace({ packageName: name }, '[local-storage/readPackage/_readStorageFile] read a package succeed: @{packageName}');
|
||||
cb(null, data);
|
||||
} catch (err) {
|
||||
this.logger.trace({ err }, '[local-storage/readPackage/_readStorageFile] error on read a package: @{err}');
|
||||
cb(err);
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
this.logger.trace({ err }, '[local-storage/readPackage/_readStorageFile] error on read a package: @{err}');
|
||||
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public writeTarball(name: string): IUploadTarball {
|
||||
const uploadStream = new UploadTarball({});
|
||||
this.logger.debug({ packageName: name }, '[local-storage/writeTarball] write a tarball for package: @{packageName}');
|
||||
|
||||
let _ended = 0;
|
||||
uploadStream.on('end', function () {
|
||||
_ended = 1;
|
||||
});
|
||||
|
||||
const pathName: string = this._getStorage(name);
|
||||
|
||||
fs.access(pathName, (fileNotFound) => {
|
||||
const exists = !fileNotFound;
|
||||
if (exists) {
|
||||
uploadStream.emit('error', fSError(fileExist));
|
||||
} else {
|
||||
const temporalName = path.join(this.path, `${name}.tmp-${String(Math.random()).replace(/^0\./, '')}`);
|
||||
const file = fs.createWriteStream(temporalName);
|
||||
const removeTempFile = (): void => fs.unlink(temporalName, () => {});
|
||||
let opened = false;
|
||||
uploadStream.pipe(file);
|
||||
|
||||
uploadStream.done = function (): void {
|
||||
const onend = function (): void {
|
||||
file.on('close', function () {
|
||||
renameTmp(temporalName, pathName, function (err) {
|
||||
if (err) {
|
||||
uploadStream.emit('error', err);
|
||||
} else {
|
||||
uploadStream.emit('success');
|
||||
}
|
||||
});
|
||||
});
|
||||
file.end();
|
||||
};
|
||||
if (_ended) {
|
||||
onend();
|
||||
} else {
|
||||
uploadStream.on('end', onend);
|
||||
}
|
||||
};
|
||||
|
||||
uploadStream.abort = function (): void {
|
||||
if (opened) {
|
||||
opened = false;
|
||||
file.on('close', function () {
|
||||
removeTempFile();
|
||||
});
|
||||
} else {
|
||||
// if the file does not recieve any byte never is opened and has to be removed anyway.
|
||||
removeTempFile();
|
||||
}
|
||||
file.end();
|
||||
};
|
||||
|
||||
file.on('open', function () {
|
||||
opened = true;
|
||||
// re-emitting open because it's handled in storage.js
|
||||
uploadStream.emit('open');
|
||||
});
|
||||
|
||||
file.on('error', function (err) {
|
||||
uploadStream.emit('error', err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return uploadStream;
|
||||
}
|
||||
|
||||
public readTarball(name: string): ReadTarball {
|
||||
const pathName: string = this._getStorage(name);
|
||||
this.logger.debug({ packageName: name }, '[local-storage/readTarball] read a tarball for package: @{packageName}');
|
||||
|
||||
const readTarballStream = new ReadTarball({});
|
||||
|
||||
const readStream = fs.createReadStream(pathName);
|
||||
|
||||
readStream.on('error', function (err) {
|
||||
readTarballStream.emit('error', err);
|
||||
});
|
||||
|
||||
readStream.on('open', function (fd) {
|
||||
fs.fstat(fd, function (err, stats) {
|
||||
if (_.isNil(err) === false) {
|
||||
return readTarballStream.emit('error', err);
|
||||
}
|
||||
readTarballStream.emit('content-length', stats.size);
|
||||
readTarballStream.emit('open');
|
||||
readStream.pipe(readTarballStream);
|
||||
});
|
||||
});
|
||||
|
||||
readTarballStream.abort = function (): void {
|
||||
readStream.close();
|
||||
};
|
||||
|
||||
return readTarballStream;
|
||||
}
|
||||
|
||||
private _createFile(name: string, contents: any, callback: Function): void {
|
||||
this.logger.trace({ name }, '[local-storage/_createFile] create a new file: @{name}');
|
||||
|
||||
fs.open(name, 'wx', (err) => {
|
||||
if (err) {
|
||||
// native EEXIST used here to check exception on fs.open
|
||||
if (err.code === 'EEXIST') {
|
||||
this.logger.trace({ name }, '[local-storage/_createFile] file cannot be created, it already exists: @{name}');
|
||||
return callback(fSError(fileExist));
|
||||
}
|
||||
}
|
||||
|
||||
this._writeFile(name, contents, callback);
|
||||
});
|
||||
}
|
||||
|
||||
private _readStorageFile(name: string): Promise<any> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
this.logger.trace({ name }, '[local-storage/_readStorageFile] read a file: @{name}');
|
||||
|
||||
fs.readFile(name, (err, data) => {
|
||||
if (err) {
|
||||
this.logger.trace({ err }, '[local-storage/_readStorageFile] error on read the file: @{name}');
|
||||
reject(err);
|
||||
} else {
|
||||
this.logger.trace({ name }, '[local-storage/_readStorageFile] read file succeed: @{name}');
|
||||
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _convertToString(value: Package): string {
|
||||
return JSON.stringify(value, null, '\t');
|
||||
}
|
||||
|
||||
private _getStorage(fileName = ''): string {
|
||||
const storagePath: string = path.join(this.path, fileName);
|
||||
|
||||
return storagePath;
|
||||
}
|
||||
|
||||
private _writeFile(dest: string, data: string, cb: Callback): void {
|
||||
const createTempFile = (cb): void => {
|
||||
const tempFilePath = tempFile(dest);
|
||||
|
||||
fs.writeFile(tempFilePath, data, (err) => {
|
||||
if (err) {
|
||||
this.logger.trace({ name: dest }, '[local-storage/_writeFile] new file: @{name} has been created');
|
||||
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
this.logger.trace({ name: dest }, '[local-storage/_writeFile] creating a new file: @{name}');
|
||||
renameTmp(tempFilePath, dest, cb);
|
||||
});
|
||||
};
|
||||
|
||||
createTempFile((err) => {
|
||||
if (err && err.code === noSuchFile) {
|
||||
mkdirp(path.dirname(dest), function (err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
createTempFile(cb);
|
||||
});
|
||||
} else {
|
||||
cb(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _lockAndReadJSON(name: string, cb: Function): void {
|
||||
const fileName: string = this._getStorage(name);
|
||||
|
||||
readFile(
|
||||
fileName,
|
||||
{
|
||||
lock: true,
|
||||
parse: true,
|
||||
},
|
||||
(err, res) => {
|
||||
if (err) {
|
||||
this.logger.trace({ name }, '[local-storage/_lockAndReadJSON] read new file: @{name} has failed');
|
||||
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
this.logger.trace({ name }, '[local-storage/_lockAndReadJSON] file: @{name} read');
|
||||
return cb(null, res);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _unlockJSON(name: string, cb: Function): void {
|
||||
unlockFile(this._getStorage(name), cb);
|
||||
}
|
||||
}
|
29
packages/core/local-storage/src/pkg-utils.ts
Normal file
29
packages/core/local-storage/src/pkg-utils.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import fs from 'fs';
|
||||
|
||||
import _ from 'lodash';
|
||||
import { LocalStorage, StorageList, Logger } from '@verdaccio/types';
|
||||
|
||||
export function loadPrivatePackages(path: string, logger: Logger): LocalStorage {
|
||||
const list: StorageList = [];
|
||||
const emptyDatabase = { list, secret: '' };
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
|
||||
if (_.isNil(data)) {
|
||||
// readFileSync is platform specific, FreeBSD might return null
|
||||
return emptyDatabase;
|
||||
}
|
||||
|
||||
let db;
|
||||
try {
|
||||
db = JSON.parse(data);
|
||||
} catch (err) {
|
||||
logger.error(`Package database file corrupted (invalid JSON), please check the error printed below.\nFile Path: ${path}`, err);
|
||||
throw Error('Package database file corrupted (invalid JSON)');
|
||||
}
|
||||
|
||||
if (_.isEmpty(db)) {
|
||||
return emptyDatabase;
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
75
packages/core/local-storage/src/utils.ts
Normal file
75
packages/core/local-storage/src/utils.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
export function getFileStats(packagePath: string): Promise<fs.Stats> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
fs.stat(packagePath, (err, stats) => {
|
||||
if (_.isNil(err) === false) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(stats);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function readDirectory(packagePath: string): Promise<string[]> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
fs.readdir(packagePath, (err, scopedPackages) => {
|
||||
if (_.isNil(err) === false) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(scopedPackages);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function hasScope(file: string): boolean {
|
||||
return file.match(/^@/) !== null;
|
||||
}
|
||||
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
export async function findPackages(storagePath: string, validationHandler: Function): Promise<{ name: string; path: string }[]> {
|
||||
const listPackages: { name: string; path: string }[] = [];
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const scopePath = path.resolve(storagePath);
|
||||
const storageDirs = await readDirectory(scopePath);
|
||||
for (const directory of storageDirs) {
|
||||
// we check whether has 2nd level
|
||||
if (hasScope(directory)) {
|
||||
// we read directory multiple
|
||||
const scopeDirectory = path.resolve(storagePath, directory);
|
||||
const scopedPackages = await readDirectory(scopeDirectory);
|
||||
for (const scopedDirName of scopedPackages) {
|
||||
if (validationHandler(scopedDirName)) {
|
||||
// we build the complete scope path
|
||||
const scopePath = path.resolve(storagePath, directory, scopedDirName);
|
||||
// list content of such directory
|
||||
listPackages.push({
|
||||
name: `${directory}/${scopedDirName}`,
|
||||
path: scopePath,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// otherwise we read as single level
|
||||
if (validationHandler(directory)) {
|
||||
const scopePath = path.resolve(storagePath, directory);
|
||||
listPackages.push({
|
||||
name: directory,
|
||||
path: scopePath,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
|
||||
resolve(listPackages);
|
||||
});
|
||||
}
|
||||
/* eslint-enable no-async-promise-executor */
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"list": [
|
||||
"webpack",
|
||||
],
|
||||
"secret": "8aabe5a0174be5cb3d5a92c01869101f11864d631a8ec21d0bf16e37ad657e92"
|
||||
}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"list": [
|
||||
"webpack",
|
||||
"pnpm",
|
||||
"yarn",
|
||||
"react-redux",
|
||||
"verdaccio",
|
||||
"tslint",
|
||||
"@test/pkg1",
|
||||
"@verdaccio/types",
|
||||
"@test/view",
|
||||
"@my-co/my-package",
|
||||
"verdaccio-authentication-plugin-sample",
|
||||
"@verdaccio/streams",
|
||||
"@verdaccio/file-locking",
|
||||
"bar",
|
||||
"foo"
|
||||
],
|
||||
"secret": "8aabe5a0174be5cb3d5a92c01869101f11864d631a8ec21d0bf16e37ad657e92"
|
||||
}
|
0
packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_second/pkg1/.gitkeep
Normal file
0
packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_second/pkg1/.gitkeep
Normal file
0
packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_second/pkg2/.gitkeep
Normal file
0
packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_second/pkg2/.gitkeep
Normal file
0
packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_test/pkg1/.gitkeep
Normal file
0
packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_test/pkg1/.gitkeep
Normal file
0
packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_test/pkg2/.gitkeep
Normal file
0
packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_test/pkg2/.gitkeep
Normal file
50
packages/core/local-storage/tests/__fixtures__/pkg.js
Normal file
50
packages/core/local-storage/tests/__fixtures__/pkg.js
Normal file
@ -0,0 +1,50 @@
|
||||
const json = {
|
||||
_id: '@scope/pk1-test',
|
||||
name: '@scope/pk1-test',
|
||||
description: '',
|
||||
'dist-tags': {
|
||||
latest: '1.0.6',
|
||||
},
|
||||
versions: {
|
||||
'1.0.6': {
|
||||
name: '@scope/pk1-test',
|
||||
version: '1.0.6',
|
||||
description: '',
|
||||
main: 'index.js',
|
||||
scripts: {
|
||||
test: 'echo "Error: no test specified" && exit 1',
|
||||
},
|
||||
keywords: [],
|
||||
author: {
|
||||
name: 'Juan Picado',
|
||||
email: 'juan@jotadeveloper.com',
|
||||
},
|
||||
license: 'ISC',
|
||||
dependencies: {
|
||||
verdaccio: '^2.7.2',
|
||||
},
|
||||
readme: '# test',
|
||||
readmeFilename: 'README.md',
|
||||
_id: '@scope/pk1-test@1.0.6',
|
||||
_npmVersion: '5.5.1',
|
||||
_nodeVersion: '8.7.0',
|
||||
_npmUser: {},
|
||||
dist: {
|
||||
integrity: 'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
|
||||
shasum: '2c03764f651a9f016ca0b7620421457b619151b9',
|
||||
tarball: 'http://localhost:5555/@scope/pk1-test/-/@scope/pk1-test-1.0.6.tgz',
|
||||
},
|
||||
},
|
||||
},
|
||||
readme: '# test',
|
||||
_attachments: {
|
||||
'@scope/pk1-test-1.0.6.tgz': {
|
||||
content_type: 'application/octet-stream',
|
||||
data:
|
||||
'H4sIAAAAAAAAE+2W32vbMBDH85y/QnjQp9qxLEeBMsbGlocNBmN7bFdQ5WuqxJaEpGQdo//79KPeQsnIw5KUDX/9IOvurLuz/DHSjK/YAiY6jcXSKjk6sMqypHWNdtmD6hlBI0wqQmo8nVbVqMR4OsNoVB66kF1aW8eML+Vv10m9oF/jP6IfY4QyyTrILlD2eqkcm+gVzpdrJrPz4NuAsULJ4MZFWdBkbcByI7R79CRjx0ScCdnAvf+SkjUFWu8IubzBgXUhDPidQlfZ3BhlLpBUKDiQ1cDFrYDmKkNnZwjuhUM4808+xNVW8P2bMk1Y7vJrtLC1u1MmLPjBF40+Cc4ahV6GDmI/DWygVRpMwVX3KtXUCg7Sxp7ff3nbt6TBFy65gK1iffsN41yoEHtdFbOiisWMH8bPvXUH0SP3k+KG3UBr+DFy7OGfEJr4x5iWVeS/pLQe+D+FIv/agIWI6GX66kFuIhT+1gDjrp/4d7WAvAwEJPh0u14IufWkM0zaW2W6nLfM2lybgJ4LTJ0/jWiAK8OcMjt8MW3OlfQppcuhhQ6k+2OgkK2Q8DssFPi/IHpU9fz3/+xj5NjDf8QFE39VmE4JDfzPCBn4P4X6/f88f/Pu47zomiPk2Lv/dOv8h+P/34/D/p9CL+Kp67mrGDRo0KBBp9ZPsETQegASAAA=',
|
||||
length: 512,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default json;
|
@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "readme-test",
|
||||
"versions": {
|
||||
"0.0.0": {
|
||||
"name": "test-readme",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": ""
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"_id": "test-readme@0.0.0",
|
||||
"dist": {
|
||||
"shasum": "8ee7331cbc641581b1a8cecd9d38d744a8feb863",
|
||||
"tarball": "http://localhost:1234/test-readme/-/test-readme-0.0.0.tgz"
|
||||
},
|
||||
"_from": ".",
|
||||
"_npmVersion": "1.3.1",
|
||||
"_npmUser": {
|
||||
"name": "alex",
|
||||
"email": "alex@kocharin.ru"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "juan",
|
||||
"email": "juanpicado19@gmail.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dist-tags": {
|
||||
"foo": "0.0.0",
|
||||
"latest": "0.0.0"
|
||||
},
|
||||
"time": {
|
||||
"modified": "2017-10-06T20:30:38.721Z",
|
||||
"created": "2017-10-06T20:30:38.721Z",
|
||||
"0.0.0": "2017-10-06T20:30:38.721Z"
|
||||
},
|
||||
"_distfiles": {},
|
||||
"_attachments": {
|
||||
"test-readme-0.0.0.tgz": {
|
||||
"shasum": "8ee7331cbc641581b1a8cecd9d38d744a8feb863",
|
||||
"version": "0.0.0"
|
||||
}
|
||||
},
|
||||
"_uplinks": {},
|
||||
"_rev": "5-d647003b88ff08a0",
|
||||
"readme": "this is a readme"
|
||||
}
|
Binary file not shown.
89
packages/core/local-storage/tests/__mocks__/Config.ts
Normal file
89
packages/core/local-storage/tests/__mocks__/Config.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import minimatch from 'minimatch';
|
||||
|
||||
// FUTURE: we should use the same is on verdaccio
|
||||
export function getMatchedPackagesSpec(pkgName: string, packages: object): object | undefined {
|
||||
for (const i in packages) {
|
||||
if (minimatch.makeRe(i).exec(pkgName)) {
|
||||
return packages[i];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
export default class Config {
|
||||
public constructor() {
|
||||
this.storage = './test-storage';
|
||||
|
||||
this.listen = 'http://localhost:1443/';
|
||||
|
||||
this.auth = {
|
||||
htpasswd: {
|
||||
file: './htpasswd',
|
||||
max_users: 1000,
|
||||
},
|
||||
};
|
||||
|
||||
this.uplinks = {
|
||||
npmjs: {
|
||||
url: 'https://registry.npmjs.org',
|
||||
cache: true,
|
||||
},
|
||||
};
|
||||
|
||||
this.packages = {
|
||||
'@*/*': {
|
||||
access: ['$all'],
|
||||
publish: ['$authenticated'],
|
||||
proxy: [],
|
||||
},
|
||||
'local-private-custom-storage': {
|
||||
access: ['$all'],
|
||||
publish: ['$authenticated'],
|
||||
storage: 'private_folder',
|
||||
},
|
||||
'*': {
|
||||
access: ['$all'],
|
||||
publish: ['$authenticated'],
|
||||
proxy: ['npmjs'],
|
||||
},
|
||||
'**': {
|
||||
access: [],
|
||||
publish: [],
|
||||
proxy: [],
|
||||
},
|
||||
};
|
||||
|
||||
this.logs = [
|
||||
{
|
||||
type: 'stdout',
|
||||
format: 'pretty',
|
||||
level: 35,
|
||||
},
|
||||
];
|
||||
|
||||
this.self_path = './tests/__fixtures__/config.yaml';
|
||||
|
||||
this.https = {
|
||||
enable: false,
|
||||
};
|
||||
|
||||
this.user_agent = 'verdaccio/3.0.0-alpha.7';
|
||||
|
||||
this.users = {};
|
||||
|
||||
this.server_id = 'severMockId';
|
||||
|
||||
this.getMatchedPackagesSpec = (pkgName) => getMatchedPackagesSpec(pkgName, this.packages);
|
||||
|
||||
this.checkSecretKey = (secret) => {
|
||||
if (!secret) {
|
||||
const newSecret = 'superNewSecret';
|
||||
|
||||
this.secret = newSecret;
|
||||
|
||||
return newSecret;
|
||||
}
|
||||
return secret;
|
||||
};
|
||||
}
|
||||
}
|
14
packages/core/local-storage/tests/__mocks__/Logger.ts
Normal file
14
packages/core/local-storage/tests/__mocks__/Logger.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Logger } from '@verdaccio/types';
|
||||
|
||||
const logger: Logger = {
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
// fatal: jest.fn(),
|
||||
info: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
child: jest.fn(),
|
||||
http: jest.fn(),
|
||||
trace: jest.fn(),
|
||||
};
|
||||
|
||||
export default logger;
|
273
packages/core/local-storage/tests/local-database.test.ts
Normal file
273
packages/core/local-storage/tests/local-database.test.ts
Normal file
@ -0,0 +1,273 @@
|
||||
/* eslint-disable jest/no-mocks-import */
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { assign } from 'lodash';
|
||||
import { ILocalData, PluginOptions, Token } from '@verdaccio/types';
|
||||
|
||||
import LocalDatabase from '../src/local-database';
|
||||
import { ILocalFSPackageManager } from '../src/local-fs';
|
||||
import * as pkgUtils from '../src/pkg-utils';
|
||||
|
||||
// FIXME: remove this mocks imports
|
||||
import Config from './__mocks__/Config';
|
||||
import logger from './__mocks__/Logger';
|
||||
|
||||
const optionsPlugin: PluginOptions<{}> = {
|
||||
logger,
|
||||
config: new Config(),
|
||||
};
|
||||
|
||||
let locaDatabase: ILocalData<{}>;
|
||||
let loadPrivatePackages;
|
||||
|
||||
describe('Local Database', () => {
|
||||
beforeEach(() => {
|
||||
const writeMock = jest.spyOn(fs, 'writeFileSync').mockImplementation();
|
||||
loadPrivatePackages = jest.spyOn(pkgUtils, 'loadPrivatePackages').mockReturnValue({ list: [], secret: '' });
|
||||
locaDatabase = new LocalDatabase(optionsPlugin.config, optionsPlugin.logger);
|
||||
(locaDatabase as LocalDatabase).clean();
|
||||
writeMock.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should create an instance', () => {
|
||||
expect(optionsPlugin.logger.error).not.toHaveBeenCalled();
|
||||
expect(locaDatabase).toBeDefined();
|
||||
});
|
||||
|
||||
test('should display log error if fails on load database', () => {
|
||||
loadPrivatePackages.mockImplementation(() => {
|
||||
throw Error();
|
||||
});
|
||||
|
||||
new LocalDatabase(optionsPlugin.config, optionsPlugin.logger);
|
||||
|
||||
expect(optionsPlugin.logger.error).toHaveBeenCalled();
|
||||
expect(optionsPlugin.logger.error).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
describe('should create set secret', () => {
|
||||
test('should create get secret', async () => {
|
||||
const secretKey = await locaDatabase.getSecret();
|
||||
|
||||
expect(secretKey).toBeDefined();
|
||||
expect(typeof secretKey === 'string').toBeTruthy();
|
||||
});
|
||||
|
||||
test('should create set secret', async () => {
|
||||
await locaDatabase.setSecret(optionsPlugin.config.checkSecretKey(''));
|
||||
|
||||
expect(optionsPlugin.config.secret).toBeDefined();
|
||||
expect(typeof optionsPlugin.config.secret === 'string').toBeTruthy();
|
||||
|
||||
const fetchedSecretKey = await locaDatabase.getSecret();
|
||||
expect(optionsPlugin.config.secret).toBe(fetchedSecretKey);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPackageStorage', () => {
|
||||
test('should get default storage', () => {
|
||||
const pkgName = 'someRandomePackage';
|
||||
const storage = locaDatabase.getPackageStorage(pkgName);
|
||||
expect(storage).toBeDefined();
|
||||
|
||||
if (storage) {
|
||||
const storagePath = path.normalize((storage as ILocalFSPackageManager).path).toLowerCase();
|
||||
expect(storagePath).toBe(path.normalize(path.join(__dirname, '__fixtures__', optionsPlugin.config.storage || '', pkgName)).toLowerCase());
|
||||
}
|
||||
});
|
||||
|
||||
test('should use custom storage', () => {
|
||||
const pkgName = 'local-private-custom-storage';
|
||||
const storage = locaDatabase.getPackageStorage(pkgName);
|
||||
|
||||
expect(storage).toBeDefined();
|
||||
|
||||
if (storage) {
|
||||
const storagePath = path.normalize((storage as ILocalFSPackageManager).path).toLowerCase();
|
||||
expect(storagePath).toBe(
|
||||
path.normalize(path.join(__dirname, '__fixtures__', optionsPlugin.config.storage || '', 'private_folder', pkgName)).toLowerCase()
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Database CRUD', () => {
|
||||
test('should add an item to database', (done) => {
|
||||
const pgkName = 'jquery';
|
||||
locaDatabase.get((err, data) => {
|
||||
expect(err).toBeNull();
|
||||
expect(data).toHaveLength(0);
|
||||
|
||||
locaDatabase.add(pgkName, (err) => {
|
||||
expect(err).toBeNull();
|
||||
locaDatabase.get((err, data) => {
|
||||
expect(err).toBeNull();
|
||||
expect(data).toHaveLength(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should remove an item to database', (done) => {
|
||||
const pgkName = 'jquery';
|
||||
locaDatabase.get((err, data) => {
|
||||
expect(err).toBeNull();
|
||||
expect(data).toHaveLength(0);
|
||||
locaDatabase.add(pgkName, (err) => {
|
||||
expect(err).toBeNull();
|
||||
locaDatabase.remove(pgkName, (err) => {
|
||||
expect(err).toBeNull();
|
||||
locaDatabase.get((err, data) => {
|
||||
expect(err).toBeNull();
|
||||
expect(data).toHaveLength(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
const onPackageMock = jest.fn((item, cb) => cb());
|
||||
const validatorMock = jest.fn(() => true);
|
||||
const callSearch = (db, numberTimesCalled, cb): void => {
|
||||
db.search(
|
||||
onPackageMock,
|
||||
function onEnd() {
|
||||
expect(onPackageMock).toHaveBeenCalledTimes(numberTimesCalled);
|
||||
expect(validatorMock).toHaveBeenCalledTimes(numberTimesCalled);
|
||||
cb();
|
||||
},
|
||||
validatorMock
|
||||
);
|
||||
};
|
||||
|
||||
test('should find scoped packages', (done) => {
|
||||
const scopedPackages = ['@pkg1/test'];
|
||||
const stats = { mtime: new Date() };
|
||||
jest.spyOn(fs, 'stat').mockImplementation((_, cb) => cb(null, stats as fs.Stats));
|
||||
jest.spyOn(fs, 'readdir').mockImplementation((storePath, cb) => cb(null, storePath.match('test-storage') ? scopedPackages : []));
|
||||
|
||||
callSearch(locaDatabase, 1, done);
|
||||
});
|
||||
|
||||
test('should find non scoped packages', (done) => {
|
||||
const nonScopedPackages = ['pkg1', 'pkg2'];
|
||||
const stats = { mtime: new Date() };
|
||||
jest.spyOn(fs, 'stat').mockImplementation((_, cb) => cb(null, stats as fs.Stats));
|
||||
jest.spyOn(fs, 'readdir').mockImplementation((storePath, cb) => cb(null, storePath.match('test-storage') ? nonScopedPackages : []));
|
||||
|
||||
const db = new LocalDatabase(
|
||||
assign({}, optionsPlugin.config, {
|
||||
// clean up this, it creates noise
|
||||
packages: {},
|
||||
}),
|
||||
optionsPlugin.logger
|
||||
);
|
||||
|
||||
callSearch(db, 2, done);
|
||||
});
|
||||
|
||||
test('should fails on read the storage', (done) => {
|
||||
const spyInstance = jest.spyOn(fs, 'readdir').mockImplementation((_, cb) => cb(Error('fails'), null));
|
||||
|
||||
const db = new LocalDatabase(
|
||||
assign({}, optionsPlugin.config, {
|
||||
// clean up this, it creates noise
|
||||
packages: {},
|
||||
}),
|
||||
optionsPlugin.logger
|
||||
);
|
||||
|
||||
callSearch(db, 0, done);
|
||||
spyInstance.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('token', () => {
|
||||
let token: Token;
|
||||
|
||||
beforeEach(() => {
|
||||
(locaDatabase as LocalDatabase).tokenDb = {
|
||||
put: jest.fn().mockImplementation((key, value, cb) => cb()),
|
||||
del: jest.fn().mockImplementation((key, cb) => cb()),
|
||||
createReadStream: jest.fn(),
|
||||
};
|
||||
|
||||
token = {
|
||||
user: 'someUser',
|
||||
viewToken: 'viewToken',
|
||||
key: 'someHash',
|
||||
readonly: true,
|
||||
createdTimestamp: new Date().getTime(),
|
||||
};
|
||||
});
|
||||
|
||||
test('should save token', async (done) => {
|
||||
const db = (locaDatabase as LocalDatabase).tokenDb;
|
||||
|
||||
await locaDatabase.saveToken(token);
|
||||
|
||||
expect(db.put).toHaveBeenCalledWith('someUser:someHash', token, expect.anything());
|
||||
done();
|
||||
});
|
||||
|
||||
test('should delete token', async (done) => {
|
||||
const db = (locaDatabase as LocalDatabase).tokenDb;
|
||||
|
||||
await locaDatabase.deleteToken('someUser', 'someHash');
|
||||
|
||||
expect(db.del).toHaveBeenCalledWith('someUser:someHash', expect.anything());
|
||||
done();
|
||||
});
|
||||
|
||||
test('should get tokens', async () => {
|
||||
const db = (locaDatabase as LocalDatabase).tokenDb;
|
||||
const events = { on: {}, once: {} };
|
||||
const stream = {
|
||||
on: (event, cb): void => {
|
||||
events.on[event] = cb;
|
||||
},
|
||||
once: (event, cb): void => {
|
||||
events.once[event] = cb;
|
||||
},
|
||||
};
|
||||
db.createReadStream.mockImplementation(() => stream);
|
||||
setTimeout(() => events.on['data']({ value: token }));
|
||||
setTimeout(() => events.once['end']());
|
||||
|
||||
const tokens = await locaDatabase.readTokens({ user: 'someUser' });
|
||||
|
||||
expect(db.createReadStream).toHaveBeenCalledWith({
|
||||
gte: 'someUser:',
|
||||
lte: 't',
|
||||
});
|
||||
expect(tokens).toHaveLength(1);
|
||||
expect(tokens[0]).toBe(token);
|
||||
});
|
||||
|
||||
test('should fail getting tokens if something goes wrong', async () => {
|
||||
const db = (locaDatabase as LocalDatabase).tokenDb;
|
||||
const events = { on: {}, once: {} };
|
||||
const stream = {
|
||||
on: (event, cb): void => {
|
||||
events.on[event] = cb;
|
||||
},
|
||||
once: (event, cb): void => {
|
||||
events.once[event] = cb;
|
||||
},
|
||||
};
|
||||
db.createReadStream.mockImplementation(() => stream);
|
||||
setTimeout(() => events.once['error'](new Error('Unexpected error!')));
|
||||
|
||||
await expect(locaDatabase.readTokens({ user: 'someUser' })).rejects.toThrow('Unexpected error!');
|
||||
});
|
||||
});
|
||||
});
|
344
packages/core/local-storage/tests/local-fs.test.ts
Normal file
344
packages/core/local-storage/tests/local-fs.test.ts
Normal file
@ -0,0 +1,344 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
import mkdirp from 'mkdirp';
|
||||
import rm from 'rmdir-sync';
|
||||
import { Logger, ILocalPackageManager, Package } from '@verdaccio/types';
|
||||
|
||||
import LocalDriver, { fileExist, fSError, noSuchFile, resourceNotAvailable } from '../src/local-fs';
|
||||
|
||||
import pkg from './__fixtures__/pkg';
|
||||
|
||||
let localTempStorage: string;
|
||||
const pkgFileName = 'package.json';
|
||||
|
||||
const logger: Logger = {
|
||||
error: () => jest.fn(),
|
||||
info: () => jest.fn(),
|
||||
debug: () => jest.fn(),
|
||||
warn: () => jest.fn(),
|
||||
child: () => jest.fn(),
|
||||
http: () => jest.fn(),
|
||||
trace: () => jest.fn(),
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
localTempStorage = path.join('./_storage');
|
||||
rm(localTempStorage);
|
||||
});
|
||||
|
||||
describe('Local FS test', () => {
|
||||
describe('savePackage() group', () => {
|
||||
test('savePackage()', (done) => {
|
||||
const data = {};
|
||||
const localFs = new LocalDriver(path.join(localTempStorage, 'first-package'), logger);
|
||||
|
||||
localFs.savePackage('pkg.1.0.0.tar.gz', data as Package, (err) => {
|
||||
expect(err).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('readPackage() group', () => {
|
||||
test('readPackage() success', (done) => {
|
||||
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger);
|
||||
|
||||
localFs.readPackage(pkgFileName, (err) => {
|
||||
expect(err).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('readPackage() fails', (done) => {
|
||||
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-testt'), logger);
|
||||
|
||||
localFs.readPackage(pkgFileName, (err) => {
|
||||
expect(err).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('readPackage() fails corrupt', (done) => {
|
||||
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test-corrupt'), logger);
|
||||
|
||||
localFs.readPackage('corrupt.js', (err) => {
|
||||
expect(err).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPackage() group', () => {
|
||||
test('createPackage()', (done) => {
|
||||
const localFs = new LocalDriver(path.join(localTempStorage, 'createPackage'), logger);
|
||||
|
||||
localFs.createPackage(path.join(localTempStorage, 'package5'), pkg, (err) => {
|
||||
expect(err).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('createPackage() fails by fileExist', (done) => {
|
||||
const localFs = new LocalDriver(path.join(localTempStorage, 'createPackage'), logger);
|
||||
|
||||
localFs.createPackage(path.join(localTempStorage, 'package5'), pkg, (err) => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(err.code).toBe(fileExist);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('deletePackage() group', () => {
|
||||
test('deletePackage()', (done) => {
|
||||
const localFs = new LocalDriver(path.join(localTempStorage, 'createPackage'), logger);
|
||||
|
||||
// verdaccio removes the package.json instead the package name
|
||||
localFs.deletePackage('package.json', (err) => {
|
||||
expect(err).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removePackage() group', () => {
|
||||
beforeEach(() => {
|
||||
mkdirp.sync(path.join(localTempStorage, '_toDelete'));
|
||||
});
|
||||
|
||||
test('removePackage() success', (done) => {
|
||||
const localFs: ILocalPackageManager = new LocalDriver(path.join(localTempStorage, '_toDelete'), logger);
|
||||
localFs.removePackage((error) => {
|
||||
expect(error).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('removePackage() fails', (done) => {
|
||||
const localFs: ILocalPackageManager = new LocalDriver(path.join(localTempStorage, '_toDelete_fake'), logger);
|
||||
localFs.removePackage((error) => {
|
||||
expect(error).toBeTruthy();
|
||||
expect(error.code).toBe('ENOENT');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('readTarball() group', () => {
|
||||
test('readTarball() success', (done) => {
|
||||
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger);
|
||||
const readTarballStream = localFs.readTarball('test-readme-0.0.0.tgz');
|
||||
|
||||
readTarballStream.on('error', function (err) {
|
||||
expect(err).toBeNull();
|
||||
});
|
||||
|
||||
readTarballStream.on('content-length', function (content) {
|
||||
expect(content).toBe(352);
|
||||
});
|
||||
|
||||
readTarballStream.on('end', function () {
|
||||
done();
|
||||
});
|
||||
|
||||
readTarballStream.on('data', function (data) {
|
||||
expect(data).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('readTarball() fails', (done) => {
|
||||
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger);
|
||||
const readTarballStream = localFs.readTarball('file-does-not-exist-0.0.0.tgz');
|
||||
|
||||
readTarballStream.on('error', function (err) {
|
||||
expect(err).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('writeTarball() group', () => {
|
||||
beforeEach(() => {
|
||||
const writeTarballFolder: string = path.join(localTempStorage, '_writeTarball');
|
||||
rm(writeTarballFolder);
|
||||
mkdirp.sync(writeTarballFolder);
|
||||
});
|
||||
|
||||
test('writeTarball() success', (done) => {
|
||||
const newFileName = 'new-readme-0.0.0.tgz';
|
||||
const readmeStorage: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger);
|
||||
const writeStorage: ILocalPackageManager = new LocalDriver(path.join(__dirname, '../_storage'), logger);
|
||||
const readTarballStream = readmeStorage.readTarball('test-readme-0.0.0.tgz');
|
||||
const writeTarballStream = writeStorage.writeTarball(newFileName);
|
||||
|
||||
writeTarballStream.on('error', function (err) {
|
||||
expect(err).toBeNull();
|
||||
done();
|
||||
});
|
||||
|
||||
writeTarballStream.on('success', function () {
|
||||
const fileLocation: string = path.join(__dirname, '../_storage', newFileName);
|
||||
|
||||
expect(fs.existsSync(fileLocation)).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
readTarballStream.on('end', function () {
|
||||
writeTarballStream.done();
|
||||
});
|
||||
|
||||
writeTarballStream.on('end', function () {
|
||||
done();
|
||||
});
|
||||
|
||||
writeTarballStream.on('data', function (data) {
|
||||
expect(data).toBeDefined();
|
||||
});
|
||||
|
||||
readTarballStream.on('error', function (err) {
|
||||
expect(err).toBeNull();
|
||||
done();
|
||||
});
|
||||
|
||||
readTarballStream.pipe(writeTarballStream);
|
||||
});
|
||||
|
||||
test('writeTarball() abort', (done) => {
|
||||
const newFileLocationFolder: string = path.join(localTempStorage, '_writeTarball');
|
||||
const newFileName = 'new-readme-abort-0.0.0.tgz';
|
||||
const readmeStorage: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger);
|
||||
const writeStorage: ILocalPackageManager = new LocalDriver(newFileLocationFolder, logger);
|
||||
const readTarballStream = readmeStorage.readTarball('test-readme-0.0.0.tgz');
|
||||
const writeTarballStream = writeStorage.writeTarball(newFileName);
|
||||
|
||||
writeTarballStream.on('error', function (err) {
|
||||
expect(err).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
|
||||
writeTarballStream.on('data', function (data) {
|
||||
expect(data).toBeDefined();
|
||||
writeTarballStream.abort();
|
||||
});
|
||||
|
||||
readTarballStream.pipe(writeTarballStream);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updatePackage() group', () => {
|
||||
const updateHandler = jest.fn((name, cb) => {
|
||||
cb();
|
||||
});
|
||||
const onWrite = jest.fn((name, data, cb) => {
|
||||
cb();
|
||||
});
|
||||
const transform = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
test('updatePackage() success', (done) => {
|
||||
jest.doMock('@verdaccio/file-locking', () => {
|
||||
return {
|
||||
readFile: (name, _options, cb): any => cb(null, { name }),
|
||||
unlockFile: (_something, cb): any => cb(null),
|
||||
};
|
||||
});
|
||||
|
||||
const LocalDriver = require('../src/local-fs').default;
|
||||
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger);
|
||||
|
||||
localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, () => {
|
||||
expect(transform).toHaveBeenCalledTimes(1);
|
||||
expect(updateHandler).toHaveBeenCalledTimes(1);
|
||||
expect(onWrite).toHaveBeenCalledTimes(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('updatePackage() failures handler', () => {
|
||||
test('updatePackage() whether locking fails', (done) => {
|
||||
jest.doMock('@verdaccio/file-locking', () => {
|
||||
return {
|
||||
readFile: (name, _options, cb): any => cb(Error('whateverError'), { name }),
|
||||
unlockFile: (_something, cb): any => cb(null),
|
||||
};
|
||||
});
|
||||
require('../src/local-fs').default;
|
||||
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger);
|
||||
|
||||
localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, (err) => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(transform).toHaveBeenCalledTimes(0);
|
||||
expect(updateHandler).toHaveBeenCalledTimes(0);
|
||||
expect(onWrite).toHaveBeenCalledTimes(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('updatePackage() unlock a missing package', (done) => {
|
||||
jest.doMock('@verdaccio/file-locking', () => {
|
||||
return {
|
||||
readFile: (name, _options, cb): any => cb(fSError(noSuchFile, 404), { name }),
|
||||
unlockFile: (_something, cb): any => cb(null),
|
||||
};
|
||||
});
|
||||
const LocalDriver = require('../src/local-fs').default;
|
||||
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger);
|
||||
|
||||
localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, (err) => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(transform).toHaveBeenCalledTimes(0);
|
||||
expect(updateHandler).toHaveBeenCalledTimes(0);
|
||||
expect(onWrite).toHaveBeenCalledTimes(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('updatePackage() unlock a resource non available', (done) => {
|
||||
jest.doMock('@verdaccio/file-locking', () => {
|
||||
return {
|
||||
readFile: (name, _options, cb): any => cb(fSError(resourceNotAvailable, 503), { name }),
|
||||
unlockFile: (_something, cb): any => cb(null),
|
||||
};
|
||||
});
|
||||
const LocalDriver = require('../src/local-fs').default;
|
||||
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger);
|
||||
|
||||
localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, (err) => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(transform).toHaveBeenCalledTimes(0);
|
||||
expect(updateHandler).toHaveBeenCalledTimes(0);
|
||||
expect(onWrite).toHaveBeenCalledTimes(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('updatePackage() if updateHandler fails', (done) => {
|
||||
jest.doMock('@verdaccio/file-locking', () => {
|
||||
return {
|
||||
readFile: (name, _options, cb): any => cb(null, { name }),
|
||||
unlockFile: (_something, cb): any => cb(null),
|
||||
};
|
||||
});
|
||||
|
||||
const LocalDriver = require('../src/local-fs').default;
|
||||
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger);
|
||||
const updateHandler = jest.fn((_name, cb) => {
|
||||
cb(fSError('something wrong', 500));
|
||||
});
|
||||
|
||||
localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, (err) => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(transform).toHaveBeenCalledTimes(0);
|
||||
expect(updateHandler).toHaveBeenCalledTimes(1);
|
||||
expect(onWrite).toHaveBeenCalledTimes(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
74
packages/core/local-storage/tests/utils.test.ts
Normal file
74
packages/core/local-storage/tests/utils.test.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
import { findPackages } from '../src/utils';
|
||||
import { loadPrivatePackages } from '../src/pkg-utils';
|
||||
import { noSuchFile } from '../src/local-fs';
|
||||
|
||||
// FIXME: remove this mocks imports
|
||||
// eslint-disable-next-line jest/no-mocks-import
|
||||
import logger from './__mocks__/Logger';
|
||||
|
||||
describe('Utitlies', () => {
|
||||
const loadDb = (name): string => path.join(__dirname, '__fixtures__/databases', `${name}.json`);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
test('should load private packages', () => {
|
||||
const database = loadDb('ok');
|
||||
const db = loadPrivatePackages(database, logger);
|
||||
|
||||
expect(db.list).toHaveLength(15);
|
||||
});
|
||||
|
||||
test('should load and empty private packages if database file is valid and empty', () => {
|
||||
const database = loadDb('empty');
|
||||
const db = loadPrivatePackages(database, logger);
|
||||
|
||||
expect(db.list).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should fails on load private packages', () => {
|
||||
const database = loadDb('corrupted');
|
||||
|
||||
expect(() => {
|
||||
loadPrivatePackages(database, logger);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('should handle null read values and return empty database', () => {
|
||||
const spy = jest.spyOn(fs, 'readFileSync');
|
||||
spy.mockReturnValue(null);
|
||||
|
||||
const database = loadDb('ok');
|
||||
const db = loadPrivatePackages(database, logger);
|
||||
|
||||
expect(db.list).toHaveLength(0);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
describe('find packages', () => {
|
||||
test('should fails on wrong storage path', async () => {
|
||||
try {
|
||||
await findPackages(
|
||||
'./no_such_folder_fake',
|
||||
jest.fn(() => true)
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e.code).toEqual(noSuchFile);
|
||||
}
|
||||
});
|
||||
|
||||
test('should fetch all packages from valid storage', async () => {
|
||||
const storage = path.join(__dirname, '__fixtures__/findPackages');
|
||||
const validator = jest.fn((file) => file.indexOf('.') === -1);
|
||||
const pkgs = await findPackages(storage, validator);
|
||||
// the result is 7 due number of packages on "findPackages" directory
|
||||
expect(pkgs).toHaveLength(5);
|
||||
expect(validator).toHaveBeenCalledTimes(8);
|
||||
});
|
||||
});
|
||||
});
|
9
packages/core/local-storage/tsconfig.json
Normal file
9
packages/core/local-storage/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
3
packages/core/readme/.babelrc
Normal file
3
packages/core/readme/.babelrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../../.babelrc"
|
||||
}
|
1
packages/core/readme/.gitignore
vendored
Normal file
1
packages/core/readme/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
lib/
|
332
packages/core/readme/CHANGELOG.md
Normal file
332
packages/core/readme/CHANGELOG.md
Normal file
@ -0,0 +1,332 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [9.7.3](https://github.com/verdaccio/monorepo/compare/v9.7.2...v9.7.3) (2020-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update marked / request security vulnerability ([#378](https://github.com/verdaccio/monorepo/issues/378)) ([4188e08](https://github.com/verdaccio/monorepo/commit/4188e088f42d0f6e090c948b869312ba1f30cd79))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.7.2](https://github.com/verdaccio/monorepo/compare/v9.7.1...v9.7.2) (2020-07-20)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.7.1](https://github.com/verdaccio/monorepo/compare/v9.7.0...v9.7.1) (2020-07-10)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.7.0](https://github.com/verdaccio/monorepo/compare/v9.6.1...v9.7.0) (2020-06-24)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.6.1](https://github.com/verdaccio/monorepo/compare/v9.6.0...v9.6.1) (2020-06-07)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.5.1](https://github.com/verdaccio/monorepo/compare/v9.5.0...v9.5.1) (2020-06-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* restore Node v8 support ([#361](https://github.com/verdaccio/monorepo/issues/361)) ([9be55a1](https://github.com/verdaccio/monorepo/commit/9be55a1deebe954e8eef9edc59af9fd16e29daed))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.5.0](https://github.com/verdaccio/monorepo/compare/v9.4.1...v9.5.0) (2020-05-02)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.4.0](https://github.com/verdaccio/monorepo/compare/v9.3.4...v9.4.0) (2020-03-21)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.3](https://github.com/verdaccio/monorepo/compare/v9.3.2...v9.3.3) (2020-03-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update jsdom@16.2.1 ([#340](https://github.com/verdaccio/monorepo/issues/340)) ([6060769](https://github.com/verdaccio/monorepo/commit/6060769d52f796337dda9f1a54f149c5fb22ca17))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.2](https://github.com/verdaccio/monorepo/compare/v9.3.1...v9.3.2) (2020-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security dependency jsdom@16.2.0 update ([#338](https://github.com/verdaccio/monorepo/issues/338)) ([0599f3e](https://github.com/verdaccio/monorepo/commit/0599f3e16fd1de993494943e2e7464d10b62d6be))
|
||||
* update dependencies ([#332](https://github.com/verdaccio/monorepo/issues/332)) ([b6165ae](https://github.com/verdaccio/monorepo/commit/b6165aea9b7e4012477081eae68bfa7159c58f56))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.1](https://github.com/verdaccio/monorepo/compare/v9.3.0...v9.3.1) (2020-02-23)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.3.0](https://github.com/verdaccio/monorepo/compare/v9.2.0...v9.3.0) (2020-01-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [9.0.0](https://github.com/verdaccio/monorepo/compare/v8.5.3...v9.0.0) (2020-01-07)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.5.2](https://github.com/verdaccio/monorepo/compare/v8.5.1...v8.5.2) (2019-12-25)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.5.1](https://github.com/verdaccio/monorepo/compare/v8.5.0...v8.5.1) (2019-12-24)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.5.0](https://github.com/verdaccio/monorepo/compare/v8.4.2...v8.5.0) (2019-12-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.4.2](https://github.com/verdaccio/monorepo/compare/v8.4.1...v8.4.2) (2019-11-23)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.4.1](https://github.com/verdaccio/monorepo/compare/v8.4.0...v8.4.1) (2019-11-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.4.0](https://github.com/verdaccio/monorepo/compare/v8.3.0...v8.4.0) (2019-11-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.3.0](https://github.com/verdaccio/monorepo/compare/v8.2.0...v8.3.0) (2019-10-27)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.2.0](https://github.com/verdaccio/monorepo/compare/v8.2.0-next.0...v8.2.0) (2019-10-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* core/readme/package.json to reduce vulnerabilities ([#216](https://github.com/verdaccio/monorepo/issues/216)) ([40299ab](https://github.com/verdaccio/monorepo/commit/40299ab))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.2.0-next.0](https://github.com/verdaccio/monorepo/compare/v8.1.4...v8.2.0-next.0) (2019-10-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixed lint errors ([5e677f7](https://github.com/verdaccio/monorepo/commit/5e677f7))
|
||||
* fixed lint errors ([c80e915](https://github.com/verdaccio/monorepo/commit/c80e915))
|
||||
* quotes should be single ([ae9aa44](https://github.com/verdaccio/monorepo/commit/ae9aa44))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.1.2](https://github.com/verdaccio/monorepo/compare/v8.1.1...v8.1.2) (2019-09-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **readme:** security vulnerabilities in marked dep ([ee604b1](https://github.com/verdaccio/monorepo/commit/ee604b1))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.1.1](https://github.com/verdaccio/monorepo/compare/v8.1.0...v8.1.1) (2019-09-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **security:** Cross-site Scripting (XSS) for readme ([7b53e1b](https://github.com/verdaccio/monorepo/commit/7b53e1b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.1.0](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.1...v8.1.0) (2019-09-07)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.0.1-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.0...v8.0.1-next.1) (2019-08-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.0.1-next.0](https://github.com/verdaccio/monorepo/compare/v8.0.0...v8.0.1-next.0) (2019-08-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.4...v8.0.0) (2019-08-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.4](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.3...v8.0.0-next.4) (2019-08-18)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.2](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.1...v8.0.0-next.2) (2019-08-03)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.0...v8.0.0-next.1) (2019-08-01)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/readme
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [8.0.0-next.0](https://github.com/verdaccio/monorepo/compare/v2.0.0...v8.0.0-next.0) (2019-08-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **readme:** import readme package ([f4bbf3a](https://github.com/verdaccio/monorepo/commit/f4bbf3a))
|
||||
* **readme:** modernize project ([0d8f963](https://github.com/verdaccio/monorepo/commit/0d8f963))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [1.0.4](https://github.com/verdaccio/readme/compare/v1.0.3...v1.0.4) (2019-06-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update dependencies ([3316ccf](https://github.com/verdaccio/readme/commit/3316ccf))
|
||||
|
||||
|
||||
|
||||
### [1.0.3](https://github.com/verdaccio/readme/compare/v1.0.2...v1.0.3) (2019-05-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** lib folder as main ([e1ac882](https://github.com/verdaccio/readme/commit/e1ac882))
|
||||
|
||||
|
||||
|
||||
### [1.0.2](https://github.com/verdaccio/readme/compare/v1.0.1...v1.0.2) (2019-05-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** remove publish script ([9b36d5f](https://github.com/verdaccio/readme/commit/9b36d5f))
|
||||
|
||||
|
||||
|
||||
### 1.0.1 (2019-05-15)
|
||||
|
||||
|
||||
### Tests
|
||||
|
||||
* add basic test ([774a54d](https://github.com/verdaccio/readme/commit/774a54d))
|
||||
* add image test ([8c4639e](https://github.com/verdaccio/readme/commit/8c4639e))
|
||||
* add xss scenarios ([81e43e8](https://github.com/verdaccio/readme/commit/81e43e8))
|
||||
* add xss scenarios ([b211b97](https://github.com/verdaccio/readme/commit/b211b97))
|
21
packages/core/readme/LICENSE
Normal file
21
packages/core/readme/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Verdaccio
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
21
packages/core/readme/README.md
Normal file
21
packages/core/readme/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# @verdaccio/readme
|
||||
|
||||
📃 Readme markdown parser
|
||||
|
||||
[![verdaccio (latest)](https://img.shields.io/npm/v/@verdaccio/readme/latest.svg)](https://www.npmjs.com/package/@verdaccio/readme)
|
||||
[![CircleCI](https://circleci.com/gh/verdaccio/readme/tree/master.svg?style=svg)](https://circleci.com/gh/verdaccio/readme/tree/master)
|
||||
[![Known Vulnerabilities](https://snyk.io/test/github/verdaccio/readme/badge.svg?targetFile=package.json)](https://snyk.io/test/github/verdaccio/readme?targetFile=package.json)
|
||||
[![codecov](https://codecov.io/gh/verdaccio/readme/branch/master/graph/badge.svg)](https://codecov.io/gh/verdaccio/readme)
|
||||
[![backers](https://opencollective.com/verdaccio/tiers/backer/badge.svg?label=Backer&color=brightgreen)](https://opencollective.com/verdaccio)
|
||||
[![discord](https://img.shields.io/discord/388674437219745793.svg)](http://chat.verdaccio.org/)
|
||||
![MIT](https://img.shields.io/github/license/mashape/apistatus.svg)
|
||||
[![node](https://img.shields.io/node/v/@verdaccio/readme/latest.svg)](https://www.npmjs.com/package/@verdaccio/readme)
|
||||
|
||||
> This package is already built-in in verdaccio
|
||||
|
||||
```
|
||||
npm install @verdaccio/readme
|
||||
```
|
||||
|
||||
## License
|
||||
Verdaccio is [MIT licensed](https://github.com/verdaccio/readme/blob/master/LICENSE).
|
3
packages/core/readme/jest.config.js
Normal file
3
packages/core/readme/jest.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
const config = require('../../../jest/config');
|
||||
|
||||
module.exports = Object.assign({}, config, {});
|
52
packages/core/readme/package.json
Normal file
52
packages/core/readme/package.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@verdaccio/readme",
|
||||
"version": "10.0.0-beta",
|
||||
"description": "Readme markdown parser",
|
||||
"keywords": [
|
||||
"verdaccio",
|
||||
"readme",
|
||||
"markdown"
|
||||
],
|
||||
"author": {
|
||||
"name": "Juan Picado",
|
||||
"email": "juanpicado19@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://verdaccio.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/verdaccio/monorepo",
|
||||
"directory": "core/readme"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/verdaccio/monorepo/issues"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"main": "./build/index.js",
|
||||
"types": "./build/index.d.ts",
|
||||
"files": [
|
||||
"build"
|
||||
],
|
||||
"dependencies": {
|
||||
"dompurify": "2.0.8",
|
||||
"jsdom": "15.2.1",
|
||||
"marked": "1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@verdaccio/types": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"type-check": "tsc --noEmit",
|
||||
"build:types": "tsc --emitDeclarationOnly --declaration true",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"build": "pnpm run build:js && pnpm run build:types"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/verdaccio"
|
||||
}
|
||||
}
|
17
packages/core/readme/src/index.ts
Normal file
17
packages/core/readme/src/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import marked from 'marked';
|
||||
import createDOMPurify from 'dompurify';
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
const DOMPurify = createDOMPurify(new JSDOM('').window);
|
||||
|
||||
export default function parseReadme(readme: string): string | void {
|
||||
if (readme) {
|
||||
return DOMPurify.sanitize(
|
||||
marked(readme, {
|
||||
sanitize: false,
|
||||
}).trim()
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
# mix html and XSS markdown
|
||||
|
||||
[Basic](javascript:alert('Basic'))
|
||||
|
||||
<a href="https://github.com/webpack/webpack"><img width="200" height="200" src="https://webpack.js.org/assets/icon-square-big.svg"></a>
|
201
packages/core/readme/tests/readme.spec.ts
Normal file
201
packages/core/readme/tests/readme.spec.ts
Normal file
@ -0,0 +1,201 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import parseReadme from '../src';
|
||||
|
||||
function readReadme(project: string, fileName = 'readme.md'): Promise<string> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
fs.readFile(path.join(__dirname, 'partials', project, fileName), 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return resolve(data.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clean(text: string): string {
|
||||
return text.replace(/\n|\r/g, '').trim();
|
||||
}
|
||||
|
||||
describe('readme', () => {
|
||||
test('should handle empty readme', () => {
|
||||
expect(parseReadme('')).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should handle single string readme', () => {
|
||||
expect(parseReadme('this is a readme')).toEqual('<p>this is a readme</p>');
|
||||
});
|
||||
|
||||
test('should handle wrong text', () => {
|
||||
expect(parseReadme(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('basic parsing', () => {
|
||||
test('should parse basic', () => {
|
||||
expect(parseReadme('# hi')).toEqual(`<h1 id=\"hi\">hi</h1>`);
|
||||
});
|
||||
|
||||
test('should parse basic / js alert', () => {
|
||||
expect(parseReadme("[Basic](javascript:alert('Basic'))")).toEqual('<p><a>Basic</a></p>');
|
||||
});
|
||||
|
||||
test('should parse basic / local storage', () => {
|
||||
expect(parseReadme('[Local Storage](javascript:alert(JSON.stringify(localStorage)))')).toEqual('<p><a>Local Storage</a></p>');
|
||||
});
|
||||
|
||||
test('should parse basic / case insensitive', () => {
|
||||
expect(parseReadme("[CaseInsensitive](JaVaScRiPt:alert('CaseInsensitive'))")).toEqual('<p><a>CaseInsensitive</a></p>');
|
||||
});
|
||||
|
||||
test('should parse basic / url', () => {
|
||||
expect(parseReadme("[URL](javascript://www.google.com%0Aalert('URL'))")).toEqual('<p><a>URL</a></p>');
|
||||
});
|
||||
|
||||
test('should parse basic / in quotes', () => {
|
||||
expect(parseReadme('[In Quotes](\'javascript:alert("InQuotes")\')')).toEqual('<p><a href="\'javascript:alert(%22InQuotes%22)\'">In Quotes</a></p>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should parse images', () => {
|
||||
test('in quotes', () => {
|
||||
expect(parseReadme('![Escape SRC - onload](https://www.example.com/image.png"onload="alert(\'ImageOnLoad\'))')).toEqual(
|
||||
'<p><img alt="Escape SRC - onload" src="https://www.example.com/image.png%22onload=%22alert(\'ImageOnLoad\')"></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test('in image error', () => {
|
||||
expect(parseReadme('![Escape SRC - onerror]("onerror="alert(\'ImageOnError\'))')).toEqual(
|
||||
'<p><img alt="Escape SRC - onerror" src="%22onerror=%22alert(\'ImageOnError\')"></p>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should test fuzzing', () => {
|
||||
test('xss / document cookie', () => {
|
||||
expect(parseReadme('[XSS](javascript:prompt(document.cookie))')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / white space cookie', () => {
|
||||
expect(parseReadme('[XSS](j a v a s c r i p t:prompt(document.cookie))')).toEqual(
|
||||
'<p>[XSS](j a v a s c r i p t:prompt(document.cookie))</p>'
|
||||
);
|
||||
});
|
||||
|
||||
test('xss / data test/html', () => {
|
||||
expect(parseReadme('[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / data test/html encoded', () => {
|
||||
expect(parseReadme('[XSS](javascript:alert('XSS'))')).toEqual(
|
||||
'<p><a href="&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29">XSS</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test('xss / js prompt', () => {
|
||||
expect(parseReadme('[XSS]: (javascript:prompt(document.cookie))')).toEqual('');
|
||||
});
|
||||
|
||||
test('xss / js window error alert', () => {
|
||||
expect(parseReadme('[XSS](javascript:window.onerror=alert;throw%20document.cookie)')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / js window encoded prompt', () => {
|
||||
expect(parseReadme('[XSS](javascript://%0d%0aprompt(1))')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / js window encoded prompt multiple statement', () => {
|
||||
expect(parseReadme('[XSS](javascript://%0d%0aprompt(1);com)')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / js window encoded window error alert multiple statement', () => {
|
||||
expect(parseReadme('[XSS](javascript:window.onerror=alert;throw%20document.cookie)')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / js window encoded window error alert throw error', () => {
|
||||
expect(parseReadme('[XSS](javascript://%0d%0awindow.onerror=alert;throw%20document.cookie)')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / js window encoded data text/html base 64', () => {
|
||||
expect(parseReadme('[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / js vbscript alert', () => {
|
||||
expect(parseReadme('[XSS](vbscript:alert(document.domain))')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
describe('xss / js alert this', () => {
|
||||
test('xss / js case #1', () => {
|
||||
expect(parseReadme('[XSS](javascript:this;alert(1))')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / js case #2', () => {
|
||||
expect(parseReadme('[XSS](javascript:this;alert(1))')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / js case #3', () => {
|
||||
expect(parseReadme('[XSS](javascript:this;alert(1))')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / js case #4', () => {
|
||||
expect(parseReadme('[XSS](Javascript:alert(1))')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / js case #5', () => {
|
||||
expect(parseReadme('[XSS](Javas%26%2399;ript:alert(1))')).toEqual('<p><a href="Javas%26%2399;ript:alert(1)">XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / js case #6', () => {
|
||||
expect(parseReadme('[XSS](javascript:alert(1))')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
});
|
||||
|
||||
test('xss / js confirm', () => {
|
||||
expect(parseReadme('[XSS](javascript:confirm(1)')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
describe('xss / js url', () => {
|
||||
test('xss / case #1', () => {
|
||||
expect(parseReadme('[XSS](javascript://www.google.com%0Aprompt(1))')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / case #2', () => {
|
||||
expect(parseReadme('[XSS](javascript://%0d%0aconfirm(1);com)')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / case #3', () => {
|
||||
expect(parseReadme('[XSS](javascript:window.onerror=confirm;throw%201)')).toEqual('<p><a>XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / case #4', () => {
|
||||
expect(parseReadme('[XSS](<28>javascript:alert(document.domain))')).toEqual('<p><a href="%EF%BF%BDjavascript:alert(document.domain)">XSS</a></p>');
|
||||
});
|
||||
|
||||
test('xss / case #5', () => {
|
||||
expect(parseReadme('![XSS](javascript:prompt(document.cookie))\\')).toEqual('<p><img alt="XSS">\\</p>');
|
||||
});
|
||||
|
||||
test('xss / case #6', () => {
|
||||
expect(parseReadme('![XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)\\')).toEqual(
|
||||
'<p><img alt="XSS" src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">\\</p>'
|
||||
);
|
||||
});
|
||||
|
||||
// FIXME: requires proper parsing
|
||||
test.skip('xss / case #7', () => {
|
||||
expect(parseReadme(`![XSS'"\`onerror=prompt(document.cookie)](x)\\`)).toEqual('<p>![XSS\'\\"`onerror=prompt(document.cookie)](x)\\\\</p>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mix readmes / markdown', () => {
|
||||
test('should parse marked', async () => {
|
||||
const readme: string = await readReadme('mixed-html-mk');
|
||||
|
||||
expect(clean(parseReadme(readme) as string)).toEqual(
|
||||
`<h1 id=\"mix-html-and-xss-markdown\">mix html and XSS markdown</h1><p><a>Basic</a></p><p> <a href=\"https://github.com/webpack/webpack\"><img src=\"https://webpack.js.org/assets/icon-square-big.svg\" height=\"200\" width=\"200\"></a></p>`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
9
packages/core/readme/tsconfig.json
Normal file
9
packages/core/readme/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
3
packages/core/streams/.babelrc
Normal file
3
packages/core/streams/.babelrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../../.babelrc"
|
||||
}
|
6
packages/core/streams/.eslintignore
Normal file
6
packages/core/streams/.eslintignore
Normal file
@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
coverage/
|
||||
lib/
|
||||
.nyc_output
|
||||
tests-report/
|
||||
build/
|
5
packages/core/streams/.eslintrc.json
Normal file
5
packages/core/streams/.eslintrc.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"rules": {
|
||||
"@typescript-eslint/no-use-before-define": "off"
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user