mirror of
https://github.com/verdaccio/verdaccio.git
synced 2025-03-25 02:32:52 -05:00
refactor: config module, experiments renamed to flags (#1996)
* 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:
parent
1d0fc016d8
commit
10aeb4f134
61 changed files with 600 additions and 563 deletions
.changeset
packages
auth
cli/src/commands
config
jest.config.js
src
test
core
htpasswd
local-storage
types
loaders
mock/src
node-api
plugins
aws-storage/tests/__mocks__
google-cloud-storage/tests/partials
memory/test/partials
server
src
test
api
jwt
package-access
profile
storage
token
web
store/test
types/src
utils
verdaccio/test
web/src/endpoint
47
.changeset/few-cooks-destroy.md
Normal file
47
.changeset/few-cooks-destroy.md
Normal 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
|
|
@ -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 = {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
const config = require('../../jest/config');
|
||||
|
||||
module.exports = Object.assign({}, config, {});
|
||||
module.exports = require('../../jest/config');
|
||||
|
|
11
packages/config/src/agent.ts
Normal file
11
packages/config/src/agent.ts
Normal 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}`;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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';
|
||||
|
|
72
packages/config/src/package-access.ts
Normal file
72
packages/config/src/package-access.ts
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
|
|
20
packages/config/src/security.ts
Normal file
20
packages/config/src/security.ts
Normal 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,
|
||||
};
|
76
packages/config/src/uplinks.ts
Normal file
76
packages/config/src/uplinks.ts
Normal 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;
|
||||
}
|
51
packages/config/test/config-parsing.spec.ts
Normal file
51
packages/config/test/config-parsing.spec.ts
Normal 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/);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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/);
|
||||
});
|
||||
});
|
||||
});
|
100
packages/config/test/uplinks.spec.ts
Normal file
100
packages/config/test/uplinks.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
15
packages/config/test/utils.spec.ts
Normal file
15
packages/config/test/utils.spec.ts
Normal 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');
|
||||
});
|
||||
});
|
1
packages/core/htpasswd/htpasswd
Normal file
1
packages/core/htpasswd/htpasswd
Normal file
|
@ -0,0 +1 @@
|
|||
test:$6xJ4qsJ2xHE2:autocreated 2020-11-08T10:29:19.160Z
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
48
packages/core/types/index.d.ts
vendored
48
packages/core/types/index.d.ts
vendored
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}`]: {},
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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/',
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
10
packages/server/test/api/htpasswd
Normal file
10
packages/server/test/api/htpasswd
Normal 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
|
|
@ -66,7 +66,7 @@ describe('endpoint unit test', () => {
|
|||
},
|
||||
},
|
||||
storage: store,
|
||||
self_path: store,
|
||||
config_path: store,
|
||||
uplinks: {
|
||||
npmjs: {
|
||||
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}`,
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('endpoint user profile', () => {
|
|||
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
|
||||
},
|
||||
},
|
||||
self_path: store,
|
||||
config_path: store,
|
||||
},
|
||||
'profile.yaml',
|
||||
__dirname
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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}`,
|
||||
|
|
|
@ -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}`,
|
||||
|
|
|
@ -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'),
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}'`;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
export * from './auth-utils';
|
||||
export * from './string';
|
||||
export * from './utils';
|
||||
export * from './crypto-utils';
|
||||
export * from './replace-lodash';
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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<{}> = {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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
2
pnpm-lock.yaml
generated
|
@ -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:
|
||||
|
|
Loading…
Add table
Reference in a new issue