0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-03-25 02:32:52 -05:00

refactor: config module, experiments renamed to flags ()

* refactor: config security refactor

* chore: add changeset

* chore: rename self_path to config_path on test

* chore: fix test

* chore: remove self_path on init
This commit is contained in:
Juan Picado 2020-11-08 15:20:02 +01:00
parent 1d0fc016d8
commit 10aeb4f134
61 changed files with 600 additions and 563 deletions

View file

@ -0,0 +1,47 @@
---
'@verdaccio/api': major
'@verdaccio/auth': major
'@verdaccio/cli': major
'@verdaccio/dev-commons': major
'@verdaccio/config': major
'@verdaccio/commons-api': major
'@verdaccio/file-locking': major
'verdaccio-htpasswd': major
'@verdaccio/local-storage': major
'@verdaccio/readme': major
'@verdaccio/streams': major
'@verdaccio/types': major
'@verdaccio/hooks': major
'@verdaccio/loaders': major
'@verdaccio/logger': major
'@verdaccio/logger-prettify': major
'@verdaccio/middleware': major
'@verdaccio/mock': major
'@verdaccio/node-api': major
'@verdaccio/active-directory': major
'verdaccio-audit': major
'verdaccio-auth-memory': major
'verdaccio-aws-s3-storage': major
'verdaccio-google-cloud': major
'verdaccio-memory': major
'@verdaccio/proxy': major
'@verdaccio/server': major
'@verdaccio/store': major
'@verdaccio/dev-types': major
'@verdaccio/utils': major
'verdaccio': major
'@verdaccio/web': major
'@verdaccio/website': major
---
feat!: experiments config renamed to flags
- The `experiments` configuration is renamed to `flags`. The functionality is exactly the same.
```js
flags: token: false;
search: false;
```
- The `self_path` property from the config file is being removed in favor of `config_file` full path.
- Refactor `config` module, better types and utilities

View file

@ -35,7 +35,6 @@ import { getMatchedPackagesSpec } from '@verdaccio/config';
import {
getMiddlewareCredentials,
getSecurity,
getDefaultPlugins,
verifyJWTPayload,
parseAuthTokenHeader,
@ -292,6 +291,7 @@ class Auth implements IAuth {
debug('allow unpublish for %o plugin does not implement allow_unpublish', packageName);
continue;
} else {
// @ts-ignore
plugin.allow_unpublish!(user, pkg, (err, ok: boolean): void => {
if (err) {
debug(
@ -335,7 +335,7 @@ class Auth implements IAuth {
(function next(): void {
const plugin = plugins.shift();
if (_.isNil(plugin) || isFunction(plugin.allow_publish) === false) {
if (isFunction(plugin?.allow_publish) === false) {
debug('allow publish for %o plugin does not implement allow_publish', packageName);
return next();
}
@ -343,6 +343,7 @@ class Auth implements IAuth {
// @ts-ignore
plugin.allow_publish(
user,
// @ts-ignore
pkg,
// @ts-ignore
(err: VerdaccioError, ok: boolean): void => {
@ -406,9 +407,7 @@ class Auth implements IAuth {
debug('api middleware auth heather is not valid');
return next(getBadRequest(API_ERROR.BAD_AUTH_HEADER));
}
const security: Security = getSecurity(this.config);
const { secret } = this.config;
const { secret, security } = this.config;
if (isAESLegacy(security)) {
debug('api middleware using legacy auth token');
@ -554,6 +553,7 @@ class Auth implements IAuth {
public async jwtEncrypt(user: RemoteUser, signOptions: JWTSignOptions): Promise<string> {
const { real_groups, name, groups } = user;
debug('jwt encrypt %o', name);
const realGroupsValidated = _.isNil(real_groups) ? [] : real_groups;
const groupedGroups = _.isNil(groups) ? real_groups : groups.concat(realGroupsValidated);
const payload: RemoteUser = {

View file

@ -1,16 +1,21 @@
import _ from 'lodash';
import buildDebug from 'debug';
import { Callback, Config, IPluginAuth, RemoteUser, Security } from '@verdaccio/types';
import {
Callback,
Config,
IPluginAuth,
RemoteUser,
Security,
AuthPackageAllow,
} from '@verdaccio/types';
import { HTTP_STATUS, TOKEN_BASIC, TOKEN_BEARER, API_ERROR } from '@verdaccio/dev-commons';
import { getForbidden, getUnauthorized, getConflict, getCode } from '@verdaccio/commons-api';
import {
AllowAction,
AllowActionCallback,
AuthPackageAllow,
convertPayloadToBase64,
createAnonymousRemoteUser,
defaultSecurity,
} from '@verdaccio/utils';
import { TokenEncryption, AESPayload } from './auth';
import { aesDecrypt } from './legacy-token';
@ -100,15 +105,16 @@ export async function getApiToken(
remoteUser: RemoteUser,
aesPassword: string
): Promise<string | void> {
const security: Security = getSecurity(config);
debug('get api token');
const { security } = config;
if (isAESLegacy(security)) {
debug('security legacy enabled');
// fallback all goes to AES encryption
return await new Promise((resolve): void => {
resolve(auth.aesEncrypt(buildUser(remoteUser.name as string, aesPassword)));
});
}
// i am wiling to use here _.isNil but flow does not like it yet.
const { jwt } = security.api;
if (jwt?.sign) {
@ -119,14 +125,6 @@ export async function getApiToken(
});
}
export function getSecurity(config: Config): Security {
if (_.isNil(config.security) === false) {
return _.merge(defaultSecurity, config.security);
}
return defaultSecurity;
}
export const expireReasons: string[] = ['JsonWebTokenError', 'TokenExpiredError'];
export function verifyJWTPayload(token: string, secret: string): RemoteUser {

View file

@ -26,7 +26,6 @@ import {
getMiddlewareCredentials,
getApiToken,
verifyJWTPayload,
getSecurity,
aesDecrypt,
verifyPayload,
signPayload,
@ -94,10 +93,8 @@ describe('Auth utilities', () => {
};
const verifyAES = (token: string, user: string, password: string, secret: string) => {
const payload = aesDecrypt(token, secret).toString(
// @ts-ignore
CHARACTER_ENCODING.UTF8
);
// @ts-ignore
const payload = aesDecrypt(token, secret).toString(CHARACTER_ENCODING.UTF8);
const content = payload.split(':');
expect(content[0]).toBe(user);
@ -339,7 +336,7 @@ describe('Auth utilities', () => {
'jwtEncrypt'
);
const config: Config = getConfig('security-legacy', secret);
const security: Security = getSecurity(config);
const security: Security = config.security;
const credentials = getMiddlewareCredentials(security, secret, `Bearer ${token}`);
expect(credentials).toBeDefined();
// @ts-ignore
@ -355,7 +352,7 @@ describe('Auth utilities', () => {
// basic authentication need send user as base64
const token = buildUserBuffer(user, pass).toString('base64');
const config: Config = getConfig('security-legacy', secret);
const security: Security = getSecurity(config);
const security: Security = config.security;
const credentials = getMiddlewareCredentials(security, secret, `Basic ${token}`);
expect(credentials).toBeDefined();
// @ts-ignore
@ -375,7 +372,7 @@ describe('Auth utilities', () => {
'jwtEncrypt'
);
const config: Config = getConfig('security-legacy', secret);
const security: Security = getSecurity(config);
const security: Security = config.security;
const credentials = getMiddlewareCredentials(
security,
'b2df428b9929d3ace7c598bbf4e496_BAD_TOKEN',
@ -395,7 +392,7 @@ describe('Auth utilities', () => {
'jwtEncrypt'
);
const config: Config = getConfig('security-legacy', secret);
const security: Security = getSecurity(config);
const security: Security = config.security;
const credentials = getMiddlewareCredentials(
security,
secret,
@ -409,7 +406,7 @@ describe('Auth utilities', () => {
const config: Config = getConfig('security-legacy', secret);
const auth: IAuth = new Auth(config);
const token = auth.aesEncrypt(null);
const security: Security = getSecurity(config);
const security: Security = config.security;
const credentials = getMiddlewareCredentials(
security,
secret,
@ -438,7 +435,7 @@ describe('Auth utilities', () => {
describe('should get JWT credentials', () => {
test('should return anonymous whether token is corrupted', () => {
const config: Config = getConfig('security-jwt', '12345');
const security: Security = getSecurity(config);
const security: Security = config.security;
const credentials = getMiddlewareCredentials(
security,
'12345',
@ -456,7 +453,7 @@ describe('Auth utilities', () => {
test('should return anonymous whether token and scheme are corrupted', () => {
const config: Config = getConfig('security-jwt', '12345');
const security: Security = getSecurity(config);
const security: Security = config.security;
const credentials = getMiddlewareCredentials(
security,
'12345',
@ -478,7 +475,7 @@ describe('Auth utilities', () => {
'jwtEncrypt',
'aesEncrypt'
);
const security: Security = getSecurity(config);
const security: Security = config.security;
const credentials = getMiddlewareCredentials(
security,
secret,

View file

@ -1,5 +1,4 @@
import path from 'path';
import { ConfigRuntime } from '@verdaccio/types';
import { findConfigFile, parseConfigFile } from '@verdaccio/config';
import { startVerdaccio, listenDefaultCallback } from '@verdaccio/node-api';
@ -10,22 +9,14 @@ export default function initProgram(commander, pkgVersion, pkgName) {
// const initLogger = createLogger();
const cliListener = commander.listen;
let configPathLocation;
let verdaccioConfiguration;
let verdaccioConfiguration: ConfigRuntime;
try {
configPathLocation = findConfigFile(commander.config);
verdaccioConfiguration = parseConfigFile(configPathLocation);
const { web, https, self_path } = verdaccioConfiguration;
const { web, https } = verdaccioConfiguration;
process.title = web?.title || DEFAULT_PROCESS_NAME;
// FIXME: self_path is only being used by @verdaccio/storage, not really useful
// and might be removed soon
if (!self_path) {
verdaccioConfiguration = Object.assign({}, verdaccioConfiguration, {
self_path: path.resolve(configPathLocation),
});
}
if (!https) {
verdaccioConfiguration = Object.assign({}, verdaccioConfiguration, {
https: { enable: false },

View file

@ -1,3 +1 @@
const config = require('../../jest/config');
module.exports = Object.assign({}, config, {});
module.exports = require('../../jest/config');

View file

@ -0,0 +1,11 @@
import assert from 'assert';
import _ from 'lodash';
const pkgVersion = require('../package.json').version;
const pkgName = require('../package.json').name;
export function getUserAgent(): string {
assert(_.isString(pkgName));
assert(_.isString(pkgVersion));
return `${pkgName}/${pkgVersion}`;
}

View file

@ -87,14 +87,12 @@ logs:
# { type: file, path: verdaccio.log, level: http}
# FIXME: this should be documented
# More info about log rotation https://github.com/pinojs/pino/blob/master/docs/help.md#log-rotation
#experiments:
# # support for npm token command
# token: false
# # support for the new v1 search endpoint, functional by incomplete read more on ticket 1732
# search: false
# # disable writing body size to logs, read more on ticket 1912
# bytesin_off: false
flags:
# support for npm token command
token: false
# support for the new v1 search endpoint, functional by incomplete read more on ticket 1732
search: false
# This affect the web and api (not developed yet)
#i18n:
#web: en-US
i18n:
web: en-US

View file

@ -86,12 +86,11 @@ logs:
# FIXME: this should be documented
# More info about log rotation https://github.com/pinojs/pino/blob/master/docs/help.md#log-rotation
#experiments:
# # support for npm token command
# token: false
# # support for the new v1 search endpoint, functional by incomplete read more on ticket 1732
# search: false
flags:
# support for npm token command
token: false
# support for the new v1 search endpoint, functional by incomplete read more on ticket 1732
search: false
# This affect the web and api (not developed yet)
#i18n:
#web: en-US

View file

@ -1,10 +0,0 @@
## This file is deprecated and the content does not exist anymore
## we highly recommend either visit
## https://verdaccio.org/docs/en/configuration
## or read the local file
## https://github.com/verdaccio/website/tree/master/docs/config.md
## contribute with translations
## You can contribute translating documentation through the crowdin platform
## https://crowdin.com/project/verdaccio

View file

@ -1,151 +0,0 @@
import assert from 'assert';
import _ from 'lodash';
import minimatch from 'minimatch';
import { PackageList, UpLinksConfList, PackageAccess } from '@verdaccio/types';
import { ErrorCode } from '@verdaccio/utils';
import { MatchedPackage } from './config';
export type PackageAccessAddOn = PackageAccess & {
// FIXME: should be published on @verdaccio/types
unpublish?: string[];
};
export interface LegacyPackageList {
[key: string]: PackageAccessAddOn;
}
const BLACKLIST = {
all: true,
anonymous: true,
undefined: true,
owner: true,
none: true,
};
/**
* Normalize user list.
* @return {Array}
*/
export function normalizeUserList(groupsList: any): any {
const result: any[] = [];
if (_.isNil(groupsList)) {
return result;
}
// if it's a string, split it to array
if (_.isString(groupsList)) {
const groupsArray = groupsList.split(/\s+/);
result.push(groupsArray);
} else if (Array.isArray(groupsList)) {
result.push(groupsList);
} else {
throw ErrorCode.getInternalError(
'CONFIG: bad package acl (array or string expected): ' + JSON.stringify(groupsList)
);
}
return _.flatten(result);
}
export function uplinkSanityCheck(
uplinks: UpLinksConfList,
users: any = BLACKLIST
): UpLinksConfList {
const newUplinks = _.clone(uplinks);
let newUsers = _.clone(users);
for (const uplink in newUplinks) {
if (Object.prototype.hasOwnProperty.call(newUplinks, uplink)) {
if (_.isNil(newUplinks[uplink].cache)) {
newUplinks[uplink].cache = true;
}
newUsers = sanityCheckNames(uplink, newUsers);
}
}
return newUplinks;
}
export function sanityCheckNames(item: string, users: any): any {
assert(
item !== 'all' &&
item !== 'owner' &&
item !== 'anonymous' &&
item !== 'undefined' &&
item !== 'none',
'CONFIG: reserved uplink name: ' + item
);
assert(!item.match(/\s/), 'CONFIG: invalid uplink name: ' + item);
assert(_.isNil(users[item]), 'CONFIG: duplicate uplink name: ' + item);
users[item] = true;
return users;
}
export function sanityCheckUplinksProps(configUpLinks: UpLinksConfList): UpLinksConfList {
const uplinks = _.clone(configUpLinks);
for (const uplink in uplinks) {
if (Object.prototype.hasOwnProperty.call(uplinks, uplink)) {
assert(uplinks[uplink].url, 'CONFIG: no url for uplink: ' + uplink);
assert(_.isString(uplinks[uplink].url), 'CONFIG: wrong url format for uplink: ' + uplink);
uplinks[uplink].url = uplinks[uplink].url.replace(/\/$/, '');
}
}
return uplinks;
}
/**
* Check whether an uplink can proxy
*/
export function hasProxyTo(pkg: string, upLink: string, packages: PackageList): boolean {
const matchedPkg: MatchedPackage = getMatchedPackagesSpec(pkg, packages);
const proxyList = typeof matchedPkg !== 'undefined' ? matchedPkg.proxy : [];
if (proxyList) {
return proxyList.some((curr) => upLink === curr);
}
return false;
}
export function getMatchedPackagesSpec(pkgName: string, packages: PackageList): MatchedPackage {
for (const i in packages) {
if (minimatch.makeRe(i).exec(pkgName)) {
return packages[i];
}
}
return;
}
export function normalisePackageAccess(packages: LegacyPackageList): LegacyPackageList {
const normalizedPkgs: LegacyPackageList = { ...packages };
// add a default rule for all packages to make writing plugins easier
if (_.isNil(normalizedPkgs['**'])) {
normalizedPkgs['**'] = {
access: [],
publish: [],
proxy: [],
};
}
for (const pkg in packages) {
if (Object.prototype.hasOwnProperty.call(packages, pkg)) {
const packageAccess = packages[pkg];
const isInvalid = _.isObject(packageAccess) && _.isArray(packageAccess) === false;
assert(isInvalid, `CONFIG: bad "'${pkg}'" package description (object expected)`);
normalizedPkgs[pkg].access = normalizeUserList(packageAccess.access);
normalizedPkgs[pkg].publish = normalizeUserList(packageAccess.publish);
normalizedPkgs[pkg].proxy = normalizeUserList(packageAccess.proxy);
// if unpublish is not defined, we set to false to fallback in publish access
normalizedPkgs[pkg].unpublish = _.isUndefined(packageAccess.unpublish)
? false
: normalizeUserList(packageAccess.unpublish);
}
}
return normalizedPkgs;
}

View file

@ -2,28 +2,28 @@ import assert from 'assert';
import _ from 'lodash';
import buildDebug from 'debug';
import { generateRandomHexString, getUserAgent, isObject } from '@verdaccio/utils';
import { generateRandomHexString, isObject } from '@verdaccio/utils';
import { APP_ERROR } from '@verdaccio/dev-commons';
import { PackageList, Config as AppConfig, Security, PackageAccess } from '@verdaccio/types';
import { generateRandomSecretKey } from './token';
import {
getMatchedPackagesSpec,
normalisePackageAccess,
sanityCheckUplinksProps,
uplinkSanityCheck,
} from './config-utils';
PackageList,
Config as AppConfig,
ConfigRuntime,
Security,
PackageAccess,
AuthConf,
} from '@verdaccio/types';
import { generateRandomSecretKey } from './token';
import { getMatchedPackagesSpec, normalisePackageAccess } from './package-access';
import { sanityCheckUplinksProps, uplinkSanityCheck } from './uplinks';
import { defaultSecurity } from './security';
import { getUserAgent } from './agent';
const strategicConfigProps = ['uplinks', 'packages'];
const allowedEnvConfig = ['http_proxy', 'https_proxy', 'no_proxy'];
export type MatchedPackage = PackageAccess | void;
export interface StartUpConfig {
storage: string;
plugins?: string;
self_path: string;
}
const debug = buildDebug('verdaccio:config');
/**
@ -31,23 +31,24 @@ const debug = buildDebug('verdaccio:config');
*/
class Config implements AppConfig {
public user_agent: string;
// @ts-ignore
public secret: string;
public uplinks: any;
public packages: PackageList;
public users: any;
public auth: AuthConf;
public server_id: string;
public self_path: string;
public config_path: string;
public storage: string | void;
public plugins: string | void;
// @ts-ignore
public security: Security;
// @ts-ignore
public secret: string;
public constructor(config: StartUpConfig) {
public constructor(config: ConfigRuntime) {
const self = this;
this.self_path = config.self_path;
this.storage = config.storage;
this.config_path = config.config_path;
this.plugins = config.plugins;
this.security = _.merge(defaultSecurity, config.security);
for (const configProp in config) {
if (self[configProp] == null) {

View file

@ -1,5 +1,7 @@
export * from './config';
export * from './config-path';
export * from './token';
export * from './config-utils';
export * from './package-access';
export * from './parse';
export * from './uplinks';
export * from './security';

View file

@ -0,0 +1,72 @@
import assert from 'assert';
import _ from 'lodash';
import minimatch from 'minimatch';
import { PackageList, PackageAccess } from '@verdaccio/types';
import { ErrorCode } from '@verdaccio/utils';
import { MatchedPackage } from './config';
export interface LegacyPackageList {
[key: string]: PackageAccess;
}
export function normalizeUserList(groupsList: any): any {
const result: any[] = [];
if (_.isNil(groupsList)) {
return result;
}
// if it's a string, split it to array
if (_.isString(groupsList)) {
const groupsArray = groupsList.split(/\s+/);
result.push(groupsArray);
} else if (Array.isArray(groupsList)) {
result.push(groupsList);
} else {
throw ErrorCode.getInternalError(
'CONFIG: bad package acl (array or string expected): ' + JSON.stringify(groupsList)
);
}
return _.flatten(result);
}
export function getMatchedPackagesSpec(pkgName: string, packages: PackageList): MatchedPackage {
for (const i in packages) {
if (minimatch.makeRe(i).exec(pkgName)) {
return packages[i];
}
}
return;
}
export function normalisePackageAccess(packages: LegacyPackageList): LegacyPackageList {
const normalizedPkgs: LegacyPackageList = { ...packages };
if (_.isNil(normalizedPkgs['**'])) {
normalizedPkgs['**'] = {
access: [],
publish: [],
unpublish: [],
proxy: [],
};
}
for (const pkg in packages) {
if (Object.prototype.hasOwnProperty.call(packages, pkg)) {
const packageAccess = packages[pkg];
const isInvalid = _.isObject(packageAccess) && _.isArray(packageAccess) === false;
assert(isInvalid, `CONFIG: bad "'${pkg}'" package description (object expected)`);
normalizedPkgs[pkg].access = normalizeUserList(packageAccess.access);
normalizedPkgs[pkg].publish = normalizeUserList(packageAccess.publish);
normalizedPkgs[pkg].proxy = normalizeUserList(packageAccess.proxy);
// if unpublish is not defined, we set to false to fallback in publish access
normalizedPkgs[pkg].unpublish = _.isUndefined(packageAccess.unpublish)
? false
: normalizeUserList(packageAccess.unpublish);
}
}
return normalizedPkgs;
}

View file

@ -1,14 +1,21 @@
import fs from 'fs';
import YAML from 'js-yaml';
import { APP_ERROR, CHARACTER_ENCODING } from '@verdaccio/dev-commons';
import { APP_ERROR } from '@verdaccio/dev-commons';
import { ConfigRuntime, ConfigYaml } from '@verdaccio/types';
export function parseConfigFile(configPath: string): any {
export function parseConfigFile(configPath: string): ConfigRuntime {
try {
if (/\.ya?ml$/i.test(configPath)) {
// @ts-ignore
return YAML.safeLoad(fs.readFileSync(configPath, CHARACTER_ENCODING.UTF8));
const yamlConfig = YAML.safeLoad(fs.readFileSync(configPath, 'utf8')) as ConfigYaml;
return Object.assign({}, yamlConfig, {
config_path: configPath,
});
}
return require(configPath);
const jsonConfig = require(configPath) as ConfigYaml;
return Object.assign({}, jsonConfig, {
config_path: configPath,
});
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
e.message = APP_ERROR.CONFIG_NOT_VALID;

View file

@ -0,0 +1,20 @@
import { APITokenOptions, JWTOptions, Security } from '@verdaccio/types';
export const TIME_EXPIRATION_7D = '7d';
const defaultWebTokenOptions: JWTOptions = {
sign: {
// The expiration token for the website is 7 days
expiresIn: TIME_EXPIRATION_7D,
},
verify: {},
};
const defaultApiTokenConf: APITokenOptions = {
legacy: true,
};
export const defaultSecurity: Security = {
web: defaultWebTokenOptions,
api: defaultApiTokenConf,
};

View file

@ -0,0 +1,76 @@
import assert from 'assert';
import { PackageList, UpLinksConfList } from '@verdaccio/types';
import _ from 'lodash';
import { getMatchedPackagesSpec } from './package-access';
import { MatchedPackage } from './config';
const BLACKLIST = {
all: true,
anonymous: true,
undefined: true,
owner: true,
none: true,
};
export function uplinkSanityCheck(
uplinks: UpLinksConfList,
users: any = BLACKLIST
): UpLinksConfList {
const newUplinks = _.clone(uplinks);
let newUsers = _.clone(users);
for (const uplink in newUplinks) {
if (Object.prototype.hasOwnProperty.call(newUplinks, uplink)) {
if (_.isNil(newUplinks[uplink].cache)) {
newUplinks[uplink].cache = true;
}
newUsers = sanityCheckNames(uplink, newUsers);
}
}
return newUplinks;
}
export function sanityCheckUplinksProps(configUpLinks: UpLinksConfList): UpLinksConfList {
const uplinks = _.clone(configUpLinks);
for (const uplink in uplinks) {
if (Object.prototype.hasOwnProperty.call(uplinks, uplink)) {
assert(uplinks[uplink].url, 'CONFIG: no url for uplink: ' + uplink);
assert(_.isString(uplinks[uplink].url), 'CONFIG: wrong url format for uplink: ' + uplink);
uplinks[uplink].url = uplinks[uplink].url.replace(/\/$/, '');
}
}
return uplinks;
}
/**
* Check whether an uplink can proxy
*/
export function hasProxyTo(pkg: string, upLink: string, packages: PackageList): boolean {
const matchedPkg: MatchedPackage = getMatchedPackagesSpec(pkg, packages);
const proxyList = typeof matchedPkg !== 'undefined' ? matchedPkg.proxy : [];
if (proxyList) {
return proxyList.some((curr) => upLink === curr);
}
return false;
}
export function sanityCheckNames(item: string, users: any): any {
assert(
item !== 'all' &&
item !== 'owner' &&
item !== 'anonymous' &&
item !== 'undefined' &&
item !== 'none',
'CONFIG: reserved uplink name: ' + item
);
assert(!item.match(/\s/), 'CONFIG: invalid uplink name: ' + item);
assert(_.isNil(users[item]), 'CONFIG: duplicate uplink name: ' + item);
users[item] = true;
return users;
}

View file

@ -0,0 +1,51 @@
import path from 'path';
import { parseConfigFile } from '../src';
describe('Package access utilities', () => {
const parseConfigurationFile = (conf) => {
const { name, ext } = path.parse(conf);
const format = ext.startsWith('.') ? ext.substring(1) : 'yaml';
return path.join(__dirname, `./partials/config/${format}/${name}.${format}`);
};
describe('JSON format', () => {
test('parse default.json', () => {
const config = parseConfigFile(parseConfigurationFile('default.json'));
expect(config.storage).toBeDefined();
});
test('parse invalid.json', () => {
expect(function () {
parseConfigFile(parseConfigurationFile('invalid.json'));
}).toThrow(/Error/);
});
test('parse not-exists.json', () => {
expect(function () {
parseConfigFile(parseConfigurationFile('not-exists.json'));
}).toThrow(/Error/);
});
});
describe('JavaScript format', () => {
test('parse default.js', () => {
const config = parseConfigFile(parseConfigurationFile('default.js'));
expect(config.storage).toBeDefined();
});
test('parse invalid.js', () => {
expect(function () {
parseConfigFile(parseConfigurationFile('invalid.js'));
}).toThrow(/Error/);
});
test('parse not-exists.js', () => {
expect(function () {
parseConfigFile(parseConfigurationFile('not-exists.js'));
}).toThrow(/Error/);
});
});
});

View file

@ -3,7 +3,7 @@ import _ from 'lodash';
import { DEFAULT_REGISTRY, DEFAULT_UPLINK, ROLES, WEB_TITLE } from '@verdaccio/dev-commons';
import { Config, parseConfigFile } from '../src';
import { Config, defaultSecurity, parseConfigFile } from '../src';
const resolveConf = (conf) => {
const { name, ext } = path.parse(conf);
@ -62,33 +62,23 @@ const checkDefaultConfPackages = (config) => {
expect(config.url_prefix).toBeUndefined();
expect(config.experiments).toBeUndefined();
expect(config.security).toBeUndefined();
expect(config.security).toEqual(defaultSecurity);
};
describe('Config file', () => {
beforeAll(function () {
/* eslint no-invalid-this: 0 */
// @ts-ignore
this.config = new Config(parseConfigFile(resolveConf('default')));
describe('check basic content parsed file', () => {
test('parse default.yaml', () => {
const config = new Config(parseConfigFile(resolveConf('default')));
checkDefaultUplink(config);
expect(config.storage).toBe('./storage');
expect(config.auth.htpasswd.file).toBe('./htpasswd');
checkDefaultConfPackages(config);
});
describe('Config file', () => {
test('parse docker.yaml', () => {
const config = new Config(parseConfigFile(resolveConf('docker')));
checkDefaultUplink(config);
expect(config.storage).toBe('/verdaccio/storage/data');
// @ts-ignore
expect(config.auth.htpasswd.file).toBe('/verdaccio/storage/htpasswd');
checkDefaultConfPackages(config);
});
test('parse default.yaml', () => {
const config = new Config(parseConfigFile(resolveConf('default')));
checkDefaultUplink(config);
expect(config.storage).toBe('./storage');
// @ts-ignore
expect(config.auth.htpasswd.file).toBe('./htpasswd');
checkDefaultConfPackages(config);
});
test('parse docker.yaml', () => {
const config = new Config(parseConfigFile(resolveConf('docker')));
checkDefaultUplink(config);
expect(config.storage).toBe('/verdaccio/storage/data');
expect(config.auth.htpasswd.file).toBe('/verdaccio/storage/htpasswd');
checkDefaultConfPackages(config);
});
});

View file

@ -3,17 +3,10 @@ import _ from 'lodash';
import { PACKAGE_ACCESS } from '@verdaccio/dev-commons';
import { spliceURL } from '@verdaccio/utils';
import {
getMatchedPackagesSpec,
hasProxyTo,
normalisePackageAccess,
sanityCheckUplinksProps,
uplinkSanityCheck,
} from '../src/config-utils';
import { getMatchedPackagesSpec, normalisePackageAccess } from '../src/package-access';
import { parseConfigFile } from '../src';
describe('Config Utilities', () => {
describe('Package access utilities', () => {
const parseConfigurationFile = (conf) => {
const { name, ext } = path.parse(conf);
const format = ext.startsWith('.') ? ext.substring(1) : 'yaml';
@ -21,38 +14,6 @@ describe('Config Utilities', () => {
return path.join(__dirname, `./partials/config/${format}/${name}.${format}`);
};
describe('uplinkSanityCheck', () => {
test('should test basic conversion', () => {
const uplinks = uplinkSanityCheck(
parseConfigFile(parseConfigurationFile('uplink-basic')).uplinks
);
expect(Object.keys(uplinks)).toContain('server1');
expect(Object.keys(uplinks)).toContain('server2');
});
test('should throw error on blacklisted uplink name', () => {
const { uplinks } = parseConfigFile(parseConfigurationFile('uplink-wrong'));
expect(() => {
uplinkSanityCheck(uplinks);
}).toThrow('CONFIG: reserved uplink name: anonymous');
});
});
describe('sanityCheckUplinksProps', () => {
test('should fails if url prop is missing', () => {
const { uplinks } = parseConfigFile(parseConfigurationFile('uplink-wrong'));
expect(() => {
sanityCheckUplinksProps(uplinks);
}).toThrow('CONFIG: no url for uplink: none-url');
});
test('should bypass an empty uplink list', () => {
// @ts-ignore
expect(sanityCheckUplinksProps([])).toHaveLength(0);
});
});
describe('normalisePackageAccess', () => {
test('should test basic conversion', () => {
const { packages } = parseConfigFile(parseConfigurationFile('pkgs-basic'));
@ -207,113 +168,4 @@ describe('Config Utilities', () => {
expect(getMatchedPackagesSpec('@scope/vue', packages)).toBeUndefined();
});
});
describe('hasProxyTo', () => {
test('should test basic config', () => {
const packages = normalisePackageAccess(
parseConfigFile(parseConfigurationFile('pkgs-basic')).packages
);
// react
expect(hasProxyTo('react', 'facebook', packages)).toBeFalsy();
expect(hasProxyTo('react', 'google', packages)).toBeFalsy();
// vue
expect(hasProxyTo('vue', 'google', packages)).toBeFalsy();
expect(hasProxyTo('vue', 'fake', packages)).toBeFalsy();
expect(hasProxyTo('vue', 'npmjs', packages)).toBeTruthy();
// angular
expect(hasProxyTo('angular', 'google', packages)).toBeFalsy();
expect(hasProxyTo('angular', 'facebook', packages)).toBeFalsy();
expect(hasProxyTo('angular', 'npmjs', packages)).toBeTruthy();
});
test('should test resolve based on custom package access', () => {
const packages = normalisePackageAccess(
parseConfigFile(parseConfigurationFile('pkgs-custom')).packages
);
// react
expect(hasProxyTo('react', 'facebook', packages)).toBeTruthy();
expect(hasProxyTo('react', 'google', packages)).toBeFalsy();
// vue
expect(hasProxyTo('vue', 'google', packages)).toBeFalsy();
expect(hasProxyTo('vue', 'fake', packages)).toBeFalsy();
expect(hasProxyTo('vue', 'npmjs', packages)).toBeTruthy();
// angular
expect(hasProxyTo('angular', 'google', packages)).toBeTruthy();
expect(hasProxyTo('angular', 'facebook', packages)).toBeFalsy();
expect(hasProxyTo('angular', 'npmjs', packages)).toBeFalsy();
});
test('should not resolve any proxy', () => {
const packages = normalisePackageAccess(
parseConfigFile(parseConfigurationFile('pkgs-empty')).packages
);
// react
expect(hasProxyTo('react', 'npmjs', packages)).toBeFalsy();
expect(hasProxyTo('react', 'npmjs', packages)).toBeFalsy();
// vue
expect(hasProxyTo('vue', 'npmjs', packages)).toBeFalsy();
expect(hasProxyTo('vue', 'npmjs', packages)).toBeFalsy();
expect(hasProxyTo('vue', 'npmjs', packages)).toBeFalsy();
// angular
expect(hasProxyTo('angular', 'npmjs', packages)).toBeFalsy();
expect(hasProxyTo('angular', 'npmjs', packages)).toBeFalsy();
expect(hasProxyTo('angular', 'npmjs', packages)).toBeFalsy();
// private
expect(hasProxyTo('private', 'fake', packages)).toBeFalsy();
});
});
describe('spliceURL', () => {
test('should splice two strings and generate a url', () => {
const url: string = spliceURL('http://domain.com', '/-/static/logo.png');
expect(url).toMatch('http://domain.com/-/static/logo.png');
});
test('should splice a empty strings and generate a url', () => {
const url: string = spliceURL('', '/-/static/logo.png');
expect(url).toMatch('/-/static/logo.png');
});
});
describe('JSON', () => {
test('parse default.json', () => {
const config = parseConfigFile(parseConfigurationFile('default.json'));
expect(config.storage).toBeDefined();
});
test('parse invalid.json', () => {
expect(function () {
parseConfigFile(parseConfigurationFile('invalid.json'));
}).toThrow(/Error/);
});
test('parse not-exists.json', () => {
expect(function () {
parseConfigFile(parseConfigurationFile('not-exists.json'));
}).toThrow(/Error/);
});
});
describe('JavaScript', () => {
test('parse default.js', () => {
const config = parseConfigFile(parseConfigurationFile('default.js'));
expect(config.storage).toBeDefined();
});
test('parse invalid.js', () => {
expect(function () {
parseConfigFile(parseConfigurationFile('invalid.js'));
}).toThrow(/Error/);
});
test('parse not-exists.js', () => {
expect(function () {
parseConfigFile(parseConfigurationFile('not-exists.js'));
}).toThrow(/Error/);
});
});
});

View file

@ -0,0 +1,100 @@
import path from 'path';
import { hasProxyTo, sanityCheckUplinksProps, uplinkSanityCheck } from '../src/uplinks';
import { normalisePackageAccess, parseConfigFile } from '../src';
describe('Uplinks Utilities', () => {
const parseConfigurationFile = (conf) => {
const { name, ext } = path.parse(conf);
const format = ext.startsWith('.') ? ext.substring(1) : 'yaml';
return path.join(__dirname, `./partials/config/${format}/${name}.${format}`);
};
describe('uplinkSanityCheck', () => {
test('should test basic conversion', () => {
const uplinks = uplinkSanityCheck(
parseConfigFile(parseConfigurationFile('uplink-basic')).uplinks
);
expect(Object.keys(uplinks)).toContain('server1');
expect(Object.keys(uplinks)).toContain('server2');
});
test('should throw error on blacklisted uplink name', () => {
const { uplinks } = parseConfigFile(parseConfigurationFile('uplink-wrong'));
expect(() => {
uplinkSanityCheck(uplinks);
}).toThrow('CONFIG: reserved uplink name: anonymous');
});
});
describe('sanityCheckUplinksProps', () => {
test('should fails if url prop is missing', () => {
const { uplinks } = parseConfigFile(parseConfigurationFile('uplink-wrong'));
expect(() => {
sanityCheckUplinksProps(uplinks);
}).toThrow('CONFIG: no url for uplink: none-url');
});
test('should bypass an empty uplink list', () => {
// @ts-ignore
expect(sanityCheckUplinksProps([])).toHaveLength(0);
});
});
describe('hasProxyTo', () => {
test('should test basic config', () => {
const packages = normalisePackageAccess(
parseConfigFile(parseConfigurationFile('pkgs-basic')).packages
);
// react
expect(hasProxyTo('react', 'facebook', packages)).toBeFalsy();
expect(hasProxyTo('react', 'google', packages)).toBeFalsy();
// vue
expect(hasProxyTo('vue', 'google', packages)).toBeFalsy();
expect(hasProxyTo('vue', 'fake', packages)).toBeFalsy();
expect(hasProxyTo('vue', 'npmjs', packages)).toBeTruthy();
// angular
expect(hasProxyTo('angular', 'google', packages)).toBeFalsy();
expect(hasProxyTo('angular', 'facebook', packages)).toBeFalsy();
expect(hasProxyTo('angular', 'npmjs', packages)).toBeTruthy();
});
test('should test resolve based on custom package access', () => {
const packages = normalisePackageAccess(
parseConfigFile(parseConfigurationFile('pkgs-custom')).packages
);
// react
expect(hasProxyTo('react', 'facebook', packages)).toBeTruthy();
expect(hasProxyTo('react', 'google', packages)).toBeFalsy();
// vue
expect(hasProxyTo('vue', 'google', packages)).toBeFalsy();
expect(hasProxyTo('vue', 'fake', packages)).toBeFalsy();
expect(hasProxyTo('vue', 'npmjs', packages)).toBeTruthy();
// angular
expect(hasProxyTo('angular', 'google', packages)).toBeTruthy();
expect(hasProxyTo('angular', 'facebook', packages)).toBeFalsy();
expect(hasProxyTo('angular', 'npmjs', packages)).toBeFalsy();
});
test('should not resolve any proxy', () => {
const packages = normalisePackageAccess(
parseConfigFile(parseConfigurationFile('pkgs-empty')).packages
);
// react
expect(hasProxyTo('react', 'npmjs', packages)).toBeFalsy();
expect(hasProxyTo('react', 'npmjs', packages)).toBeFalsy();
// vue
expect(hasProxyTo('vue', 'npmjs', packages)).toBeFalsy();
expect(hasProxyTo('vue', 'npmjs', packages)).toBeFalsy();
expect(hasProxyTo('vue', 'npmjs', packages)).toBeFalsy();
// angular
expect(hasProxyTo('angular', 'npmjs', packages)).toBeFalsy();
expect(hasProxyTo('angular', 'npmjs', packages)).toBeFalsy();
expect(hasProxyTo('angular', 'npmjs', packages)).toBeFalsy();
// private
expect(hasProxyTo('private', 'fake', packages)).toBeFalsy();
});
});
});

View file

@ -0,0 +1,15 @@
import { spliceURL } from '../src/string';
describe('spliceURL', () => {
test('should splice two strings and generate a url', () => {
const url: string = spliceURL('http://domain.com', '/-/static/logo.png');
expect(url).toMatch('http://domain.com/-/static/logo.png');
});
test('should splice a empty strings and generate a url', () => {
const url: string = spliceURL('', '/-/static/logo.png');
expect(url).toMatch('/-/static/logo.png');
});
});

View file

@ -0,0 +1 @@
test:$6xJ4qsJ2xHE2:autocreated 2020-11-08T10:29:19.160Z

View file

@ -59,7 +59,7 @@ export default class HTPasswd implements IPluginAuth<VerdaccioConfigApp> {
throw new Error('should specify "file" in config');
}
this.path = Path.resolve(Path.dirname(this.verdaccioConfig.self_path), file);
this.path = Path.resolve(Path.dirname(this.verdaccioConfig.config_path), file);
}
/**

View file

@ -38,7 +38,7 @@ export default class Config {
level: 35,
},
];
this.self_path = './tests/__fixtures__/config.yaml';
this.config_path = './tests/__fixtures__/config.yaml';
this.https = {
enable: false,
};

View file

@ -99,7 +99,7 @@ class LocalDatabase implements IPluginStorage<{}> {
): void {
const storages = this._getCustomPackageLocalStorages();
debug(`search custom local packages: %o`, JSON.stringify(storages));
const base = Path.dirname(this.config.self_path);
const base = Path.dirname(this.config.config_path);
const self = this;
const storageKeys = Object.keys(storages);
debug(`search base: %o keys: %o`, base, storageKeys);
@ -241,7 +241,7 @@ class LocalDatabase implements IPluginStorage<{}> {
}
const packageStoragePath: string = Path.join(
Path.resolve(Path.dirname(this.config.self_path || ''), packagePath),
Path.resolve(Path.dirname(this.config.config_path || ''), packagePath),
packageName
);
@ -412,7 +412,7 @@ class LocalDatabase implements IPluginStorage<{}> {
private _dbGenPath(dbName: string, config: Config): string {
return Path.join(
Path.resolve(Path.dirname(config.self_path || ''), config.storage as string, dbName)
Path.resolve(Path.dirname(config.config_path || ''), config.storage as string, dbName)
);
}

View file

@ -61,7 +61,7 @@ export default class Config {
},
];
this.self_path = './tests/__fixtures__/config.yaml';
this.config_path = './tests/__fixtures__/config.yaml';
this.https = {
enable: false,

View file

@ -187,8 +187,18 @@ declare module '@verdaccio/types' {
publish?: string[];
proxy?: string[];
access?: string[];
unpublish: string[];
}
// info passed to the auth plugin when a package is package is being published
interface AllowAccess {
name: string;
version?: string;
tag?: string;
}
interface AuthPackageAllow extends PackageAccess, AllowAccess {}
interface PackageList {
[key: string]: PackageAccess;
}
@ -323,14 +333,14 @@ declare module '@verdaccio/types' {
api: APITokenOptions;
}
interface Config {
user_agent: string;
server_id: any;
interface ConfigFlags {
token?: boolean;
search?: boolean;
}
interface ConfigYaml {
_debug?: boolean;
storage?: string | void;
plugins?: string | void;
secret: string;
self_path: string;
packages: PackageList;
uplinks: UpLinksConfList;
logs?: LoggerConf[];
@ -338,19 +348,31 @@ declare module '@verdaccio/types' {
auth?: AuthConf;
security: Security;
publish?: PublishOptions;
url_prefix?: string;
store?: any;
listen?: ListenAddress;
https?: HttpsConf;
http_proxy?: string;
plugins?: string | void;
https_proxy?: string;
no_proxy?: string;
max_body_size?: string;
// deprecated
notifications?: Notifications;
notify?: Notifications | Notifications[];
middlewares?: any;
filters?: any;
url_prefix?: string;
flags?: ConfigFlags;
}
interface ConfigRuntime extends ConfigYaml {
config_path: string;
}
interface Config extends ConfigYaml, ConfigRuntime {
user_agent: string;
server_id: string;
secret: string;
// deprecated
checkSecretKey(token: string): string;
getMatchedPackagesSpec(storage: string): PackageAccess | void;
[key: string]: any;
@ -487,12 +509,6 @@ declare module '@verdaccio/types' {
logger: Logger;
}
interface AllowAccess {
name: string;
version?: string;
tag?: string;
}
// FIXME: error should be export type `VerdaccioError = HttpError & { code: number };`
// instead of AuthError
// but this type is on @verdaccio/commons-api and cannot be used here yet (I don't know why)
@ -514,9 +530,9 @@ declare module '@verdaccio/types' {
authenticate(user: string, password: string, cb: AuthCallback): void;
adduser?(user: string, password: string, cb: AuthCallback): void;
changePassword?(user: string, password: string, newPassword: string, cb: AuthCallback): void;
allow_publish?(user: RemoteUser, pkg: T & PackageAccess, cb: AuthAccessCallback): void;
allow_publish?(user: RemoteUser, pkg: T & AuthPackageAllow, cb: AuthAccessCallback): void;
allow_access?(user: RemoteUser, pkg: T & PackageAccess, cb: AuthAccessCallback): void;
allow_unpublish?(user: RemoteUser, pkg: T & PackageAccess, cb: AuthAccessCallback): void;
allow_unpublish?(user: RemoteUser, pkg: T & AuthPackageAllow, cb: AuthAccessCallback): void;
allow_publish?(
user: RemoteUser,
pkg: AllowAccess & PackageAccess,

View file

@ -97,7 +97,7 @@ export function loadPlugin<T extends IPlugin<T>>(
// relative to config path
if (plugin === null && pluginId.match(/^\.\.?($|\/)/)) {
plugin = tryLoad(Path.resolve(Path.dirname(config.self_path), pluginId));
plugin = tryLoad(Path.resolve(Path.dirname(config.config_path), pluginId));
}
if (plugin === null) {

View file

@ -9,7 +9,7 @@ describe('plugin loader', () => {
const relativePath = path.join(__dirname, './partials/test-plugin-storage');
const buildConf = (name) => {
return {
self_path: path.join(__dirname, './'),
config_path: path.join(__dirname, './'),
max_users: 0,
auth: {
[`${relativePath}/${name}`]: {},

View file

@ -32,7 +32,7 @@ import { IServerBridge } from './types';
file: './test-profile-storage/.htpasswd'
}
},
self_path: store
config_path: store
});
app = await endPointAPI(configForTest);
mockRegistry = await mockServer(mockServerPort).init();

View file

@ -29,6 +29,7 @@
"@verdaccio/config": "workspace:5.0.0-alpha.0",
"@verdaccio/utils": "workspace:5.0.0-alpha.0",
"lodash": "^4.17.20",
"debug": "^4.2.0",
"core-js": "^3.6.5",
"selfsigned": "1.10.7"
},

View file

@ -5,8 +5,15 @@ import https from 'https';
import constants from 'constants';
import { Application } from 'express';
import { assign, isObject, isFunction } from 'lodash';
import buildDebug from 'debug';
import { Callback, ConfigWithHttps, HttpsConfKeyCert, HttpsConfPfx } from '@verdaccio/types';
import {
ConfigRuntime,
Callback,
ConfigWithHttps,
HttpsConfKeyCert,
HttpsConfPfx,
} from '@verdaccio/types';
import { API_ERROR, certPem, csrPem, keyPem } from '@verdaccio/dev-commons';
import server from '@verdaccio/server';
import { logger } from '@verdaccio/logger';
@ -14,6 +21,8 @@ import { logger } from '@verdaccio/logger';
import { getListListenAddresses, resolveConfigPath } from './cli-utils';
import { displayExperimentsInfoBox } from './experiments';
const debug = buildDebug('verdaccio:runtime');
function launchServer(
app,
addr,
@ -25,9 +34,11 @@ function launchServer(
): void {
let webServer;
if (addr.proto === 'https') {
debug('https enabled');
webServer = handleHTTPS(app, configPath, config);
} else {
// http
debug('http enabled');
webServer = http.createServer(app);
}
if (
@ -43,36 +54,25 @@ function launchServer(
callback(webServer, addr, pkgName, pkgVersion);
}
/**
* Trigger the server after configuration has been loaded.
* @param {Object} config
* @param {Object} cliArguments
* @param {String} configPath
* @param {String} pkgVersion
* @param {String} pkgName
*/
function startVerdaccio(
config: any,
async function startVerdaccio(
config: ConfigRuntime,
cliListen: string,
configPath: string,
pkgVersion: string,
pkgName: string,
callback: Callback
): void {
): Promise<void> {
if (isObject(config) === false) {
throw new Error(API_ERROR.CONFIG_BAD_FORMAT);
}
server(config).then((app): void => {
const addresses = getListListenAddresses(cliListen, config.listen);
if ('experiments' in config) {
displayExperimentsInfoBox(config.experiments);
}
const app = await server(config);
const addresses = getListListenAddresses(cliListen, config.listen);
displayExperimentsInfoBox(config.flags);
addresses.forEach((addr) =>
launchServer(app, addr, config, configPath, pkgVersion, pkgName, callback)
);
});
addresses.forEach((addr) =>
launchServer(app, addr, config, configPath, pkgVersion, pkgName, callback)
);
}
function unlinkAddressPath(addr) {

View file

@ -1,16 +1,20 @@
const logger = require('@verdaccio/logger');
import buildDebug from 'debug';
export function displayExperimentsInfoBox(experiments) {
const experimentList = Object.keys(experiments);
const debug = buildDebug('verdaccio:runtime:flags');
export function displayExperimentsInfoBox(flags) {
if (!flags) {
return;
}
const experimentList = Object.keys(flags);
if (experimentList.length >= 1) {
logger.logger.warn(
debug(
'⚠️ experiments are enabled, we recommend do not use experiments in production, ' +
'comment out this section to disable it'
);
experimentList.forEach((experiment) => {
logger.logger.warn(
` - support for ${experiment} ${experiments[experiment] ? 'is enabled' : ' is disabled'}`
);
debug(` - support for %o %o`, experiment, flags[experiment] ? 'is enabled' : ' is disabled');
});
}
}

View file

@ -38,7 +38,7 @@ export default class Config {
level: 35,
},
];
this.self_path = './src/___tests___/__fixtures__/config.yaml';
this.config_path = './src/___tests___/__fixtures__/config.yaml';
this.https = {
enable: false,
};

View file

@ -5,7 +5,7 @@ class Config implements VerdaccioConfigGoogleStorage {
keyFilename: string;
bucket: string;
kind: string;
self_path: string;
config_path: string;
secret: string;
user_agent: string;
server_id: string;
@ -18,7 +18,7 @@ class Config implements VerdaccioConfigGoogleStorage {
$value: any;
constructor() {
this.self_path = './test';
this.config_path = './test';
this.secret = '12345';
this.uplinks = {
npmjs: {

View file

@ -4,7 +4,7 @@ const config: Config = {
user_agent: 'string',
server_id: 1234,
secret: '12345',
self_path: './nowhere',
config_path: './nowhere',
uplinks: {
npmjs: {
url: 'https://registry.npmjs.org/',

View file

@ -2,7 +2,7 @@ import _ from 'lodash';
import { Application } from 'express';
import { $ResponseExtend, $RequestExtend, $NextFunctionVer } from '../../types/custom';
export default (app: Application, selfPath: string): void => {
export default (app: Application, configPath: string): void => {
// Hook for tests only
app.get('/-/_debug', function (
req: $RequestExtend,
@ -19,7 +19,7 @@ export default (app: Application, selfPath: string): void => {
pid: process.pid,
// @ts-ignore
main: process.mainModule.filename,
conf: selfPath,
conf: configPath,
mem: process.memoryUsage(),
gc: doGarbabeCollector,
});

View file

@ -13,6 +13,7 @@ import { API_ERROR, HTTP_STATUS } from '@verdaccio/dev-commons';
import { Config as AppConfig } from '@verdaccio/config';
import { webAPI, renderWebMiddleware } from '@verdaccio/web';
import { ConfigRuntime } from '@verdaccio/types';
import { IAuth, IBasicAuth } from '@verdaccio/auth';
import { IStorageHandler } from '@verdaccio/store';
@ -61,7 +62,7 @@ const defineAPI = function (config: IConfig, storage: IStorageHandler): any {
// Hook for tests only
if (config._debug) {
hookDebug(app, config.self_path);
hookDebug(app, config.config_path);
}
// register middleware plugins
@ -128,7 +129,7 @@ const defineAPI = function (config: IConfig, storage: IStorageHandler): any {
return app;
};
export default (async function (configHash: any): Promise<any> {
export default (async function (configHash: ConfigRuntime): Promise<any> {
setup(configHash.logs);
const config: IConfig = new AppConfig(_.cloneDeep(configHash));
// register middleware plugins

View file

@ -0,0 +1,10 @@
server_user_api_spec:3TdCV4iEpNFsI:autocreated 2020-11-08T10:36:37.581Z
jota_unpublish:oIj/79.KRKO6Y:autocreated 2020-11-08T10:36:37.881Z
jota_unpublish_fail:y7dutM1X0pByQ:autocreated 2020-11-08T10:36:37.934Z
jota_only_unpublish_fail:3rUkLjiXInch.:autocreated 2020-11-08T10:36:37.947Z
super_admin:yW2wIbTxWW1UA:autocreated 2020-11-08T10:36:37.953Z
any_user:VBq3LIOEN9VOY:autocreated 2020-11-08T10:36:37.971Z
jota_star:rp9KgzaAC2Uew:autocreated 2020-11-08T10:36:37.988Z
jota_deprecate:Q09PY3eVq/L1E:autocreated 2020-11-08T10:36:38.012Z
only_publish:s2pCXhzEgIupY:autocreated 2020-11-08T10:36:38.040Z
only_unpublish:+JTPy3GKmGlEE:autocreated 2020-11-08T10:36:38.046Z

View file

@ -66,7 +66,7 @@ describe('endpoint unit test', () => {
},
},
storage: store,
self_path: store,
config_path: store,
uplinks: {
npmjs: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,

View file

@ -46,7 +46,7 @@ describe('endpoint user auth JWT unit test', () => {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
},
},
self_path: store,
config_path: store,
},
'jwt.yaml',
__dirname

View file

@ -19,7 +19,7 @@ describe('api with no limited access configuration', () => {
const mockServerPort = 55530;
const configForTest = configExample(
{
self_path: store,
config_path: store,
uplinks: {
remote: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,

View file

@ -34,7 +34,7 @@ describe('endpoint user profile', () => {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
},
},
self_path: store,
config_path: store,
},
'profile.yaml',
__dirname

View file

@ -19,7 +19,7 @@ const generateStorage = async function () {
const storagePath = generateRamdonStorage();
const storageConfig = configExample(
{
self_path: storagePath,
config_path: storagePath,
storage: storagePath,
uplinks: {
npmjs: {
@ -43,7 +43,7 @@ const generateSameUplinkStorage = async function () {
console.log('-->storagePath', storagePath);
const storageConfig = configExample(
{
self_path: storagePath,
config_path: storagePath,
storage: storagePath,
packages: {
jquery: {

View file

@ -74,7 +74,7 @@ describe('endpoint unit test', () => {
const configForTest = configExample(
{
storage: store,
self_path: store,
config_path: store,
uplinks: {
npmjs: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,

View file

@ -31,7 +31,7 @@ describe('endpoint web unit test', () => {
const configForTest = configExample(
{
storage: store,
self_path: store,
config_path: store,
uplinks: {
remote: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,

View file

@ -27,7 +27,7 @@ describe('LocalStorage', () => {
const getStorage = (LocalStorageClass = LocalStorage) => {
const config: Config = new AppConfig(
configExample({
self_path: path.join('../partials/store'),
config_path: path.join('../partials/store'),
})
);

View file

@ -24,12 +24,6 @@ import { NextFunction, Request, Response } from 'express';
export type StringValue = verdaccio$StringValue;
export interface StartUpConfig {
storage: string;
plugins?: string;
self_path: string;
}
// legacy should be removed in long term
export interface LegacyPackageList {
[key: string]: PackageAccessAddOn;

View file

@ -1,12 +1,5 @@
import { ROLES, TIME_EXPIRATION_7D, DEFAULT_MIN_LIMIT_PASSWORD } from '@verdaccio/dev-commons';
import {
RemoteUser,
AllowAccess,
PackageAccess,
Security,
APITokenOptions,
JWTOptions,
} from '@verdaccio/types';
import { ROLES, DEFAULT_MIN_LIMIT_PASSWORD } from '@verdaccio/dev-commons';
import { RemoteUser, AuthPackageAllow } from '@verdaccio/types';
import { VerdaccioError } from '@verdaccio/commons-api';
export interface CookieSessionToken {
@ -80,11 +73,6 @@ export type AllowAction = (
callback: AllowActionCallback
) => void;
export interface AuthPackageAllow extends PackageAccess, AllowAccess {
// TODO: this should be on @verdaccio/types
unpublish: boolean | string[];
}
export function createSessionToken(): CookieSessionToken {
const tenHoursTime = 10 * 60 * 60 * 1000;
@ -94,23 +82,6 @@ export function createSessionToken(): CookieSessionToken {
};
}
const defaultWebTokenOptions: JWTOptions = {
sign: {
// The expiration token for the website is 7 days
expiresIn: TIME_EXPIRATION_7D,
},
verify: {},
};
const defaultApiTokenConf: APITokenOptions = {
legacy: true,
};
export const defaultSecurity: Security = {
web: defaultWebTokenOptions,
api: defaultApiTokenConf,
};
export function getAuthenticatedMessage(user: string): string {
return `you are authenticated as '${user}'`;
}

View file

@ -1,5 +1,4 @@
export * from './auth-utils';
export * from './string';
export * from './utils';
export * from './crypto-utils';
export * from './replace-lodash';

View file

@ -8,11 +8,9 @@ import { Request } from 'express';
import sanitizyReadme from '@verdaccio/readme';
import {
APP_ERROR,
DEFAULT_PORT,
DEFAULT_DOMAIN,
DEFAULT_PROTOCOL,
CHARACTER_ENCODING,
HEADERS,
DIST_TAGS,
DEFAULT_USER,
@ -32,16 +30,6 @@ import {
getCode,
} from '@verdaccio/commons-api';
// FIXME: this is fixed, should pick the package.json or official version
const pkgVersion = '5.0.0';
const pkgName = 'verdaccio';
export function getUserAgent(): string {
assert(_.isString(pkgName));
assert(_.isString(pkgVersion));
return `${pkgName}/${pkgVersion}`;
}
export function convertPayloadToBase64(payload: string): Buffer {
return Buffer.from(payload, 'base64');
}

View file

@ -2,7 +2,6 @@ import fs from 'fs';
import path from 'path';
import { DIST_TAGS, DEFAULT_USER } from '@verdaccio/dev-commons';
import {
spliceURL,
validateName,
convertDistRemoteToLocalTarballUrls,
parseReadme,
@ -325,18 +324,6 @@ describe('Utilities', () => {
});
describe('String utilities', () => {
test('should splice two strings and generate a url', () => {
const url: string = spliceURL('http://domain.com', '/-/static/logo.png');
expect(url).toMatch('http://domain.com/-/static/logo.png');
});
test('should splice a empty strings and generate a url', () => {
const url: string = spliceURL('', '/-/static/logo.png');
expect(url).toMatch('/-/static/logo.png');
});
test('should check HTTP protocol correctly', () => {
expect(isHTTPProtocol('http://domain.com/-/static/logo.png')).toBeTruthy();
expect(isHTTPProtocol('https://www.domain.com/-/static/logo.png')).toBeTruthy();

View file

@ -123,7 +123,7 @@ The _mock server_ has a static storage which is located `test/unit/partials/mock
> It is not possible yet to override the mocks configuration server.
> The `self_path` is a legacy prop that must to be set manually, this prop is being generated by the CLI, but running the test without the CLI force use to generate it manually. **This might change in the future**.
> The `config_path` is a legacy prop that must to be set manually, this prop is being generated by the CLI, but running the test without the CLI force use to generate it manually. **This might change in the future**.
> The `const mockServerPort = 55549;` mock server must be added manually, be careful and try to define a port that is not being used by another test, there is not automation here yet.

View file

@ -79,7 +79,7 @@ class ExampleAuthCustomPlugin implements IPluginAuth<{}> {
const config1: AppConfig = new Config({
storage: './storage',
self_path: '/home/sotrage',
config_path: '/home/sotrage',
});
const options: PluginOptions<{}> = {

View file

@ -41,7 +41,7 @@ export default class ExampleMiddlewarePlugin implements IPluginMiddleware<{}> {
storage.removeTarball('name', 'filename', 'revision', () => {});
const config1: AppConfig = new Config({
storage: './storage',
self_path: '/home/sotrage',
config_path: '/home/sotrage',
});
const add: IUploadTarball = storage.addTarball('name', 'filename');
storage.getTarball('name', 'filename');

View file

@ -146,7 +146,7 @@ export default ExampleStoragePlugin;
const config1: AppConfig = new Config({
storage: './storage',
self_path: '/home/sotrage',
config_path: '/home/sotrage',
});
const storage = new ExampleStoragePlugin(config1, logger.child());

View file

@ -52,7 +52,7 @@ describe('endpoint example unit test', () => {
},
},
// 6. The self_path is important be the same as the store
self_path: store,
config_path: store,
// 7. Define the location of the .htpasswd file, this is relative to self_path.
auth: {
htpasswd: {

View file

@ -6,7 +6,6 @@ import { Config, RemoteUser, JWTSignOptions } from '@verdaccio/types';
import { API_ERROR, APP_ERROR, HTTP_STATUS } from '@verdaccio/dev-commons';
import { IAuth } from '@verdaccio/auth';
import { validatePassword, ErrorCode } from '@verdaccio/utils';
import { getSecurity } from '@verdaccio/auth';
import { $NextFunctionVer } from './package';
function addUserAuthApi(route: Router, auth: IAuth, config: Config): void {
@ -22,7 +21,7 @@ function addUserAuthApi(route: Router, auth: IAuth, config: Config): void {
next(ErrorCode.getCode(errorCode, err.message));
} else {
req.remote_user = user;
const jWTSignOptions: JWTSignOptions = getSecurity(config).web.sign;
const jWTSignOptions: JWTSignOptions = config.security.web.sign;
next({
token: await auth.jwtEncrypt(user, jWTSignOptions),

2
pnpm-lock.yaml generated
View file

@ -510,6 +510,7 @@ importers:
'@verdaccio/server': 'link:../server'
'@verdaccio/utils': 'link:../utils'
core-js: 3.6.5
debug: 4.2.0
lodash: 4.17.20
selfsigned: 1.10.7
devDependencies:
@ -524,6 +525,7 @@ importers:
'@verdaccio/types': 'workspace:*'
'@verdaccio/utils': 'workspace:5.0.0-alpha.0'
core-js: ^3.6.5
debug: ^4.2.0
lodash: ^4.17.20
selfsigned: 1.10.7
packages/plugins/active-directory: