Skip to content

Commit

Permalink
feat: token support with level.js (#168)
Browse files Browse the repository at this point in the history
* chore: fix linting

* chore: fix tests

* chore: implement token storage interface

* chore: rename token db, return proper value doing query to localdb
  • Loading branch information
juangabreil authored and sergiohgz committed Aug 13, 2019
1 parent 32b9d7e commit ca877ff
Show file tree
Hide file tree
Showing 8 changed files with 1,676 additions and 1,336 deletions.
9 changes: 5 additions & 4 deletions plugins/local-storage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@verdaccio/file-locking": "1.0.3",
"@verdaccio/streams": "2.0.0",
"async": "3.1.0",
"level": "^5.0.1",
"lodash": "4.17.11",
"mkdirp": "0.5.1"
},
Expand All @@ -33,12 +34,12 @@
"@types/minimatch": "3.0.3",
"@types/node": "12.0.12",
"@typescript-eslint/eslint-plugin": "1.11.0",
"@verdaccio/babel-preset": "0.2.1",
"@verdaccio/babel-preset": "1.0.0",
"@verdaccio/eslint-config": "0.0.1",
"@verdaccio/types": "5.2.2",
"codecov": "3.5.0",
"@verdaccio/types": "6.2.0",
"codecov": "3.2.0",
"cross-env": "5.2.0",
"eslint": "5.15.3",
"eslint": "5.16.0",
"husky": "0.14.3",
"jest": "24.8.0",
"minimatch": "3.0.4",
Expand Down
226 changes: 161 additions & 65 deletions plugins/local-storage/src/local-database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,39 @@ import Path from 'path';
// $FlowFixMe
import async from 'async';
import mkdirp from 'mkdirp';
import stream from 'stream';

import LocalDriver, { noSuchFile } from './local-fs';
import { loadPrivatePackages } from './pkg-utils';

import { IPackageStorage, IPluginStorage, StorageList, LocalStorage, Logger, Config, Callback } from '@verdaccio/types';
import { getInternalError } from "@verdaccio/commons-api/lib";
import {
Callback,
Config,
IPackageStorage,
IPluginStorage,
LocalStorage,
Logger,
StorageList,
Token,
TokenFilter
} from '@verdaccio/types';

import level from 'level';
import { getInternalError } from '@verdaccio/commons-api/lib';

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.
Expand All @@ -23,6 +47,7 @@ class LocalDatabase implements IPluginStorage<{}> {
public data: LocalStorage;
public config: Config;
public locked: boolean;
public tokenDb;

/**
* Load an parse the local json database.
Expand All @@ -40,11 +65,11 @@ class LocalDatabase implements IPluginStorage<{}> {
this._sync();
}

getSecret(): Promise<string> {
public getSecret(): Promise<string> {
return Promise.resolve(this.data.secret);
}

setSecret(secret: string): Promise<string> {
public setSecret(secret: string): Promise<Error | null> {
return new Promise(resolve => {
this.data.secret = secret;

Expand All @@ -57,7 +82,7 @@ class LocalDatabase implements IPluginStorage<{}> {
* @param {*} name
* @return {Error|*}
*/
public add(name: string, cb: Callback) {
public add(name: string, cb: Callback): void {
if (this.data.list.indexOf(name) === -1) {
this.data.list.push(name);

Expand All @@ -68,7 +93,7 @@ class LocalDatabase implements IPluginStorage<{}> {
}
}

public search(onPackage: Callback, onEnd: Callback, validateName: any): void {
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);
Expand Down Expand Up @@ -158,40 +183,12 @@ class LocalDatabase implements IPluginStorage<{}> {
);
}

private _getTime(time: number, mtime: Date) {
return time ? time : mtime;
}

private _getCustomPackageLocalStorages() {
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;
}

/**
* Remove an element from the database.
* @param {*} name
* @return {Error|*}
*/
public remove(name: string, cb: Callback) {
public remove(name: string, cb: Callback): void {
this.get((err, data) => {
if (err) {
cb(getInternalError('error remove private package'));
Expand All @@ -213,20 +210,119 @@ class LocalDatabase implements IPluginStorage<{}> {
* Return all database elements.
* @return {Array}
*/
public get(cb: Callback) {
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');
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) => {
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) => {
db.del(key, err => {
if (err) {
reject(err);
return;
}
resolve();
});
});
}

public readTokens(filter: TokenFilter): Promise<Token[]> {
return new Promise((resolve, reject) => {
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() {
private _sync(): Error | null {
this.logger.debug('[local-storage/_sync]: init sync database');

if (this.locked) {
Expand Down Expand Up @@ -258,24 +354,6 @@ class LocalDatabase implements IPluginStorage<{}> {
}
}

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);
}

/**
* Verify the right local storage location.
* @param {String} path
Expand All @@ -301,23 +379,22 @@ class LocalDatabase implements IPluginStorage<{}> {
* @return {string|String|*}
* @private
*/
private _buildStoragePath(config: Config) {
const dbGenPath = function(dbName: string) {
return Path.join(Path.resolve(Path.dirname(config.self_path || ''), config.storage as string, dbName));
};

const sinopiadbPath: string = dbGenPath(DEPRECATED_DB_NAME);
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 dbGenPath(DB_NAME);
if (err.code === noSuchFile) {
return this._dbGenPath(DB_NAME, config);
}

throw err
throw err;
}
}

private _dbGenPath(dbName: string, config: Config): string {
return Path.join(Path.resolve(Path.dirname(config.self_path || ''), config.storage as string, dbName));
}

/**
Expand All @@ -344,6 +421,25 @@ class LocalDatabase implements IPluginStorage<{}> {
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;
12 changes: 6 additions & 6 deletions plugins/local-storage/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from 'fs';
import _ from 'lodash';
import path from 'path';

export function getFileStats(packagePath: string): any {
export function getFileStats(packagePath: string): Promise<fs.Stats> {
return new Promise((resolve, reject) => {
fs.stat(packagePath, (err, stats) => {
if (_.isNil(err) === false) {
Expand All @@ -13,7 +13,7 @@ export function getFileStats(packagePath: string): any {
});
}

export function readDirectory(packagePath: string): Promise<any> {
export function readDirectory(packagePath: string): Promise<string[]> {
return new Promise((resolve, reject) => {
fs.readdir(packagePath, (err, scopedPackages) => {
if (_.isNil(err) === false) {
Expand All @@ -25,12 +25,12 @@ export function readDirectory(packagePath: string): Promise<any> {
});
}

function hasScope(file: string) {
return file.match(/^@/);
function hasScope(file: string): boolean {
return file.match(/^@/) !== null;
}

export async function findPackages(storagePath: string, validationHandler: Function) {
const listPackages: any[] = [];
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);
Expand Down
Loading

0 comments on commit ca877ff

Please sign in to comment.