Skip to content

Commit ca877ff

Browse files
juangabreilsergiohgz
authored andcommitted
feat: token support with level.js (#168)
* chore: fix linting * chore: fix tests * chore: implement token storage interface * chore: rename token db, return proper value doing query to localdb
1 parent 32b9d7e commit ca877ff

File tree

8 files changed

+1676
-1336
lines changed

8 files changed

+1676
-1336
lines changed

plugins/local-storage/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"@verdaccio/file-locking": "1.0.3",
2323
"@verdaccio/streams": "2.0.0",
2424
"async": "3.1.0",
25+
"level": "^5.0.1",
2526
"lodash": "4.17.11",
2627
"mkdirp": "0.5.1"
2728
},
@@ -33,12 +34,12 @@
3334
"@types/minimatch": "3.0.3",
3435
"@types/node": "12.0.12",
3536
"@typescript-eslint/eslint-plugin": "1.11.0",
36-
"@verdaccio/babel-preset": "0.2.1",
37+
"@verdaccio/babel-preset": "1.0.0",
3738
"@verdaccio/eslint-config": "0.0.1",
38-
"@verdaccio/types": "5.2.2",
39-
"codecov": "3.5.0",
39+
"@verdaccio/types": "6.2.0",
40+
"codecov": "3.2.0",
4041
"cross-env": "5.2.0",
41-
"eslint": "5.15.3",
42+
"eslint": "5.16.0",
4243
"husky": "0.14.3",
4344
"jest": "24.8.0",
4445
"minimatch": "3.0.4",

plugins/local-storage/src/local-database.ts

Lines changed: 161 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,39 @@ import Path from 'path';
44
// $FlowFixMe
55
import async from 'async';
66
import mkdirp from 'mkdirp';
7+
import stream from 'stream';
78

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

11-
import { IPackageStorage, IPluginStorage, StorageList, LocalStorage, Logger, Config, Callback } from '@verdaccio/types';
12-
import { getInternalError } from "@verdaccio/commons-api/lib";
12+
import {
13+
Callback,
14+
Config,
15+
IPackageStorage,
16+
IPluginStorage,
17+
LocalStorage,
18+
Logger,
19+
StorageList,
20+
Token,
21+
TokenFilter
22+
} from '@verdaccio/types';
23+
24+
import level from 'level';
25+
import { getInternalError } from '@verdaccio/commons-api/lib';
1326

1427
const DEPRECATED_DB_NAME = '.sinopia-db.json';
1528
const DB_NAME = '.verdaccio-db.json';
29+
const TOKEN_DB_NAME = '.token-db';
30+
31+
interface Level {
32+
put(key: string, token, fn?: Function): void;
33+
34+
get(key: string, fn?: Function): void;
35+
36+
del(key: string, fn?: Function): void;
37+
38+
createReadStream(options?: object): stream.Readable;
39+
}
1640

1741
/**
1842
* Handle local database.
@@ -23,6 +47,7 @@ class LocalDatabase implements IPluginStorage<{}> {
2347
public data: LocalStorage;
2448
public config: Config;
2549
public locked: boolean;
50+
public tokenDb;
2651

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

43-
getSecret(): Promise<string> {
68+
public getSecret(): Promise<string> {
4469
return Promise.resolve(this.data.secret);
4570
}
4671

47-
setSecret(secret: string): Promise<string> {
72+
public setSecret(secret: string): Promise<Error | null> {
4873
return new Promise(resolve => {
4974
this.data.secret = secret;
5075

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

@@ -68,7 +93,7 @@ class LocalDatabase implements IPluginStorage<{}> {
6893
}
6994
}
7095

71-
public search(onPackage: Callback, onEnd: Callback, validateName: any): void {
96+
public search(onPackage: Callback, onEnd: Callback, validateName: (name: string) => boolean): void {
7297
const storages = this._getCustomPackageLocalStorages();
7398
this.logger.trace(`local-storage: [search]: ${JSON.stringify(storages)}`);
7499
const base = Path.dirname(this.config.self_path);
@@ -158,40 +183,12 @@ class LocalDatabase implements IPluginStorage<{}> {
158183
);
159184
}
160185

161-
private _getTime(time: number, mtime: Date) {
162-
return time ? time : mtime;
163-
}
164-
165-
private _getCustomPackageLocalStorages() {
166-
const storages = {};
167-
168-
// add custom storage if exist
169-
if (this.config.storage) {
170-
storages[this.config.storage] = true;
171-
}
172-
173-
const { packages } = this.config;
174-
175-
if (packages) {
176-
const listPackagesConf = Object.keys(packages || {});
177-
178-
listPackagesConf.map(pkg => {
179-
const storage = packages[pkg].storage;
180-
if (storage) {
181-
storages[storage] = false;
182-
}
183-
});
184-
}
185-
186-
return storages;
187-
}
188-
189186
/**
190187
* Remove an element from the database.
191188
* @param {*} name
192189
* @return {Error|*}
193190
*/
194-
public remove(name: string, cb: Callback) {
191+
public remove(name: string, cb: Callback): void {
195192
this.get((err, data) => {
196193
if (err) {
197194
cb(getInternalError('error remove private package'));
@@ -213,20 +210,119 @@ class LocalDatabase implements IPluginStorage<{}> {
213210
* Return all database elements.
214211
* @return {Array}
215212
*/
216-
public get(cb: Callback) {
213+
public get(cb: Callback): void {
217214
const list = this.data.list;
218215
const totalItems = this.data.list.length;
219216

220217
cb(null, list);
221218

222-
this.logger.trace({ totalItems} ,'local-storage: [get] full list of packages (@{totalItems}) has been fetched');
219+
this.logger.trace({ totalItems }, 'local-storage: [get] full list of packages (@{totalItems}) has been fetched');
220+
}
221+
222+
public getPackageStorage(packageName: string): IPackageStorage {
223+
const packageAccess = this.config.getMatchedPackagesSpec(packageName);
224+
225+
const packagePath: string = this._getLocalStoragePath(packageAccess ? packageAccess.storage : undefined);
226+
this.logger.trace({ packagePath }, '[local-storage/getPackageStorage]: storage selected: @{packagePath}');
227+
228+
if (_.isString(packagePath) === false) {
229+
this.logger.debug({ name: packageName }, 'this package has no storage defined: @{name}');
230+
return;
231+
}
232+
233+
const packageStoragePath: string = Path.join(Path.resolve(Path.dirname(this.config.self_path || ''), packagePath), packageName);
234+
235+
this.logger.trace({ packageStoragePath }, '[local-storage/getPackageStorage]: storage path: @{packageStoragePath}');
236+
237+
return new LocalDriver(packageStoragePath, this.logger);
238+
}
239+
240+
public clean(): void {
241+
this._sync();
242+
}
243+
244+
public saveToken(token: Token): Promise<void> {
245+
const key = this._getTokenKey(token);
246+
const db = this.getTokenDb();
247+
248+
return new Promise((resolve, reject) => {
249+
db.put(key, token, err => {
250+
if (err) {
251+
reject(err);
252+
return;
253+
}
254+
resolve();
255+
});
256+
});
257+
}
258+
259+
public deleteToken(user: string, tokenKey: string): Promise<void> {
260+
const key = this._compoundTokenKey(user, tokenKey);
261+
const db = this.getTokenDb();
262+
return new Promise((resolve, reject) => {
263+
db.del(key, err => {
264+
if (err) {
265+
reject(err);
266+
return;
267+
}
268+
resolve();
269+
});
270+
});
271+
}
272+
273+
public readTokens(filter: TokenFilter): Promise<Token[]> {
274+
return new Promise((resolve, reject) => {
275+
const tokens: Token[] = [];
276+
const key = filter.user + ':';
277+
const db = this.getTokenDb();
278+
const stream = db.createReadStream({
279+
gte: key,
280+
lte: String.fromCharCode(key.charCodeAt(0) + 1)
281+
});
282+
283+
stream.on('data', data => {
284+
tokens.push(data.value);
285+
});
286+
287+
stream.once('end', () => resolve(tokens));
288+
289+
stream.once('error', err => reject(err));
290+
});
291+
}
292+
293+
private _getTime(time: number, mtime: Date): number | Date {
294+
return time ? time : mtime;
295+
}
296+
297+
private _getCustomPackageLocalStorages(): object {
298+
const storages = {};
299+
300+
// add custom storage if exist
301+
if (this.config.storage) {
302+
storages[this.config.storage] = true;
303+
}
304+
305+
const { packages } = this.config;
306+
307+
if (packages) {
308+
const listPackagesConf = Object.keys(packages || {});
309+
310+
listPackagesConf.map(pkg => {
311+
const storage = packages[pkg].storage;
312+
if (storage) {
313+
storages[storage] = false;
314+
}
315+
});
316+
}
317+
318+
return storages;
223319
}
224320

225321
/**
226322
* Syncronize {create} database whether does not exist.
227323
* @return {Error|*}
228324
*/
229-
private _sync() {
325+
private _sync(): Error | null {
230326
this.logger.debug('[local-storage/_sync]: init sync database');
231327

232328
if (this.locked) {
@@ -258,24 +354,6 @@ class LocalDatabase implements IPluginStorage<{}> {
258354
}
259355
}
260356

261-
public getPackageStorage(packageName: string): IPackageStorage {
262-
const packageAccess = this.config.getMatchedPackagesSpec(packageName);
263-
264-
const packagePath: string = this._getLocalStoragePath(packageAccess ? packageAccess.storage : undefined);
265-
this.logger.trace({ packagePath }, '[local-storage/getPackageStorage]: storage selected: @{packagePath}');
266-
267-
if (_.isString(packagePath) === false) {
268-
this.logger.debug({ name: packageName }, 'this package has no storage defined: @{name}');
269-
return;
270-
}
271-
272-
const packageStoragePath: string = Path.join(Path.resolve(Path.dirname(this.config.self_path || ''), packagePath), packageName);
273-
274-
this.logger.trace({ packageStoragePath }, '[local-storage/getPackageStorage]: storage path: @{packageStoragePath}');
275-
276-
return new LocalDriver(packageStoragePath, this.logger);
277-
}
278-
279357
/**
280358
* Verify the right local storage location.
281359
* @param {String} path
@@ -301,23 +379,22 @@ class LocalDatabase implements IPluginStorage<{}> {
301379
* @return {string|String|*}
302380
* @private
303381
*/
304-
private _buildStoragePath(config: Config) {
305-
const dbGenPath = function(dbName: string) {
306-
return Path.join(Path.resolve(Path.dirname(config.self_path || ''), config.storage as string, dbName));
307-
};
308-
309-
const sinopiadbPath: string = dbGenPath(DEPRECATED_DB_NAME);
382+
private _buildStoragePath(config: Config): string {
383+
const sinopiadbPath: string = this._dbGenPath(DEPRECATED_DB_NAME, config);
310384
try {
311385
fs.accessSync(sinopiadbPath, fs.constants.F_OK);
312386
return sinopiadbPath;
313387
} catch (err) {
314-
if(err.code === noSuchFile) {
315-
return dbGenPath(DB_NAME);
388+
if (err.code === noSuchFile) {
389+
return this._dbGenPath(DB_NAME, config);
316390
}
317391

318-
throw err
392+
throw err;
319393
}
394+
}
320395

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

323400
/**
@@ -344,6 +421,25 @@ class LocalDatabase implements IPluginStorage<{}> {
344421
return emptyDatabase;
345422
}
346423
}
424+
425+
private getTokenDb(): Level {
426+
if (!this.tokenDb) {
427+
this.tokenDb = level(this._dbGenPath(TOKEN_DB_NAME, this.config), {
428+
valueEncoding: 'json'
429+
});
430+
}
431+
432+
return this.tokenDb;
433+
}
434+
435+
private _getTokenKey(token: Token): string {
436+
const { user, key } = token;
437+
return this._compoundTokenKey(user, key);
438+
}
439+
440+
private _compoundTokenKey(user: string, key: string): string {
441+
return `${user}:${key}`;
442+
}
347443
}
348444

349445
export default LocalDatabase;

plugins/local-storage/src/utils.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import fs from 'fs';
22
import _ from 'lodash';
33
import path from 'path';
44

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

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

28-
function hasScope(file: string) {
29-
return file.match(/^@/);
28+
function hasScope(file: string): boolean {
29+
return file.match(/^@/) !== null;
3030
}
3131

32-
export async function findPackages(storagePath: string, validationHandler: Function) {
33-
const listPackages: any[] = [];
32+
export async function findPackages(storagePath: string, validationHandler: Function): Promise<{ name: string; path: string }[]> {
33+
const listPackages: { name: string; path: string }[] = [];
3434
return new Promise(async (resolve, reject) => {
3535
try {
3636
const scopePath = path.resolve(storagePath);

0 commit comments

Comments
 (0)