mirror of
https://github.com/verdaccio/verdaccio.git
synced 2025-02-21 07:29:37 +01:00
Merge pull request #800 from verdaccio/refactor-config
refactor: config and utils
This commit is contained in:
commit
45b21fa87d
@ -31,6 +31,8 @@
|
|||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-useless-escape": 2,
|
"no-useless-escape": 2,
|
||||||
|
"react/no-deprecated": 1,
|
||||||
|
"react/jsx-no-target-blank": 1,
|
||||||
"handle-callback-err": 2,
|
"handle-callback-err": 2,
|
||||||
"no-fallthrough": 2,
|
"no-fallthrough": 2,
|
||||||
"no-new-require": 2,
|
"no-new-require": 2,
|
||||||
|
@ -21,10 +21,9 @@ ENV NODE_ENV=production
|
|||||||
RUN npm config set registry http://registry.npmjs.org/ && \
|
RUN npm config set registry http://registry.npmjs.org/ && \
|
||||||
yarn global add -s flow-bin@0.69.0 && \
|
yarn global add -s flow-bin@0.69.0 && \
|
||||||
yarn install --production=false && \
|
yarn install --production=false && \
|
||||||
yarn run lint && \
|
yarn lint && \
|
||||||
yarn run code:docker-build && \
|
yarn code:docker-build && \
|
||||||
yarn run build:webui && \
|
yarn build:webui && \
|
||||||
yarn run test:unit -- --silent true --coverage false --bail && \
|
|
||||||
yarn cache clean && \
|
yarn cache clean && \
|
||||||
yarn install --production=true --pure-lockfile
|
yarn install --production=true --pure-lockfile
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// flow-typed signature: 6e1fc0a644aa956f79029fec0709e597
|
// flow-typed signature: 4cacceffd326bb118e4a3c1b4d629e98
|
||||||
// flow-typed version: 07ebad4796/jest_v22.x.x/flow_>=v0.39.x
|
// flow-typed version: e737b9832f/jest_v23.x.x/flow_>=v0.39.x
|
||||||
|
|
||||||
type JestMockFn<TArguments: $ReadOnlyArray<*>, TReturn> = {
|
type JestMockFn<TArguments: $ReadOnlyArray<*>, TReturn> = {
|
||||||
(...args: TArguments): TReturn,
|
(...args: TArguments): TReturn,
|
||||||
@ -55,6 +55,11 @@ type JestMockFn<TArguments: $ReadOnlyArray<*>, TReturn> = {
|
|||||||
mockImplementationOnce(
|
mockImplementationOnce(
|
||||||
fn: (...args: TArguments) => TReturn
|
fn: (...args: TArguments) => TReturn
|
||||||
): JestMockFn<TArguments, TReturn>,
|
): JestMockFn<TArguments, TReturn>,
|
||||||
|
/**
|
||||||
|
* Accepts a string to use in test result output in place of "jest.fn()" to
|
||||||
|
* indicate which mock function is being referenced.
|
||||||
|
*/
|
||||||
|
mockName(name: string): JestMockFn<TArguments, TReturn>,
|
||||||
/**
|
/**
|
||||||
* Just a simple sugar function for returning `this`
|
* Just a simple sugar function for returning `this`
|
||||||
*/
|
*/
|
||||||
@ -66,7 +71,23 @@ type JestMockFn<TArguments: $ReadOnlyArray<*>, TReturn> = {
|
|||||||
/**
|
/**
|
||||||
* Sugar for only returning a value once inside your mock
|
* Sugar for only returning a value once inside your mock
|
||||||
*/
|
*/
|
||||||
mockReturnValueOnce(value: TReturn): JestMockFn<TArguments, TReturn>
|
mockReturnValueOnce(value: TReturn): JestMockFn<TArguments, TReturn>,
|
||||||
|
/**
|
||||||
|
* Sugar for jest.fn().mockImplementation(() => Promise.resolve(value))
|
||||||
|
*/
|
||||||
|
mockResolvedValue(value: TReturn): JestMockFn<TArguments, Promise<TReturn>>,
|
||||||
|
/**
|
||||||
|
* Sugar for jest.fn().mockImplementationOnce(() => Promise.resolve(value))
|
||||||
|
*/
|
||||||
|
mockResolvedValueOnce(value: TReturn): JestMockFn<TArguments, Promise<TReturn>>,
|
||||||
|
/**
|
||||||
|
* Sugar for jest.fn().mockImplementation(() => Promise.reject(value))
|
||||||
|
*/
|
||||||
|
mockRejectedValue(value: TReturn): JestMockFn<TArguments, Promise<any>>,
|
||||||
|
/**
|
||||||
|
* Sugar for jest.fn().mockImplementationOnce(() => Promise.reject(value))
|
||||||
|
*/
|
||||||
|
mockRejectedValueOnce(value: TReturn): JestMockFn<TArguments, Promise<any>>
|
||||||
};
|
};
|
||||||
|
|
||||||
type JestAsymmetricEqualityType = {
|
type JestAsymmetricEqualityType = {
|
||||||
@ -113,6 +134,12 @@ type JestPromiseType = {
|
|||||||
resolves: JestExpectType
|
resolves: JestExpectType
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jest allows functions and classes to be used as test names in test() and
|
||||||
|
* describe()
|
||||||
|
*/
|
||||||
|
type JestTestName = string | Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin: jest-enzyme
|
* Plugin: jest-enzyme
|
||||||
*/
|
*/
|
||||||
@ -120,14 +147,16 @@ type EnzymeMatchersType = {
|
|||||||
toBeChecked(): void,
|
toBeChecked(): void,
|
||||||
toBeDisabled(): void,
|
toBeDisabled(): void,
|
||||||
toBeEmpty(): void,
|
toBeEmpty(): void,
|
||||||
|
toBeEmptyRender(): void,
|
||||||
toBePresent(): void,
|
toBePresent(): void,
|
||||||
toContainReact(element: React$Element<any>): void,
|
toContainReact(element: React$Element<any>): void,
|
||||||
|
toExist(): void,
|
||||||
toHaveClassName(className: string): void,
|
toHaveClassName(className: string): void,
|
||||||
toHaveHTML(html: string): void,
|
toHaveHTML(html: string): void,
|
||||||
toHaveProp(propKey: string, propValue?: any): void,
|
toHaveProp: ((propKey: string, propValue?: any) => void) & ((props: Object) => void),
|
||||||
toHaveRef(refName: string): void,
|
toHaveRef(refName: string): void,
|
||||||
toHaveState(stateKey: string, stateValue?: any): void,
|
toHaveState: ((stateKey: string, stateValue?: any) => void) & ((state: Object) => void),
|
||||||
toHaveStyle(styleKey: string, styleValue?: any): void,
|
toHaveStyle: ((styleKey: string, styleValue?: any) => void) & ((style: Object) => void),
|
||||||
toHaveTagName(tagName: string): void,
|
toHaveTagName(tagName: string): void,
|
||||||
toHaveText(text: string): void,
|
toHaveText(text: string): void,
|
||||||
toIncludeText(text: string): void,
|
toIncludeText(text: string): void,
|
||||||
@ -136,8 +165,342 @@ type EnzymeMatchersType = {
|
|||||||
toMatchSelector(selector: string): void
|
toMatchSelector(selector: string): void
|
||||||
};
|
};
|
||||||
|
|
||||||
type JestExpectType = {
|
// DOM testing library extensions https://github.com/kentcdodds/dom-testing-library#custom-jest-matchers
|
||||||
not: JestExpectType & EnzymeMatchersType,
|
type DomTestingLibraryType = {
|
||||||
|
toBeInTheDOM(): void,
|
||||||
|
toHaveTextContent(content: string): void,
|
||||||
|
toHaveAttribute(name: string, expectedValue?: string): void
|
||||||
|
};
|
||||||
|
|
||||||
|
// Jest JQuery Matchers: https://github.com/unindented/custom-jquery-matchers
|
||||||
|
type JestJQueryMatchersType = {
|
||||||
|
toExist(): void,
|
||||||
|
toHaveLength(len: number): void,
|
||||||
|
toHaveId(id: string): void,
|
||||||
|
toHaveClass(className: string): void,
|
||||||
|
toHaveTag(tag: string): void,
|
||||||
|
toHaveAttr(key: string, val?: any): void,
|
||||||
|
toHaveProp(key: string, val?: any): void,
|
||||||
|
toHaveText(text: string | RegExp): void,
|
||||||
|
toHaveData(key: string, val?: any): void,
|
||||||
|
toHaveValue(val: any): void,
|
||||||
|
toHaveCss(css: {[key: string]: any}): void,
|
||||||
|
toBeChecked(): void,
|
||||||
|
toBeDisabled(): void,
|
||||||
|
toBeEmpty(): void,
|
||||||
|
toBeHidden(): void,
|
||||||
|
toBeSelected(): void,
|
||||||
|
toBeVisible(): void,
|
||||||
|
toBeFocused(): void,
|
||||||
|
toBeInDom(): void,
|
||||||
|
toBeMatchedBy(sel: string): void,
|
||||||
|
toHaveDescendant(sel: string): void,
|
||||||
|
toHaveDescendantWithText(sel: string, text: string | RegExp): void
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Jest Extended Matchers: https://github.com/jest-community/jest-extended
|
||||||
|
type JestExtendedMatchersType = {
|
||||||
|
/**
|
||||||
|
* Note: Currently unimplemented
|
||||||
|
* Passing assertion
|
||||||
|
*
|
||||||
|
* @param {String} message
|
||||||
|
*/
|
||||||
|
// pass(message: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: Currently unimplemented
|
||||||
|
* Failing assertion
|
||||||
|
*
|
||||||
|
* @param {String} message
|
||||||
|
*/
|
||||||
|
// fail(message: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use .toBeEmpty when checking if a String '', Array [] or Object {} is empty.
|
||||||
|
*/
|
||||||
|
toBeEmpty(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use .toBeOneOf when checking if a value is a member of a given Array.
|
||||||
|
* @param {Array.<*>} members
|
||||||
|
*/
|
||||||
|
toBeOneOf(members: any[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeNil` when checking a value is `null` or `undefined`.
|
||||||
|
*/
|
||||||
|
toBeNil(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toSatisfy` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean`.
|
||||||
|
* @param {Function} predicate
|
||||||
|
*/
|
||||||
|
toSatisfy(predicate: (n: any) => boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeArray` when checking if a value is an `Array`.
|
||||||
|
*/
|
||||||
|
toBeArray(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeArrayOfSize` when checking if a value is an `Array` of size x.
|
||||||
|
* @param {Number} x
|
||||||
|
*/
|
||||||
|
toBeArrayOfSize(x: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toIncludeAllMembers` when checking if an `Array` contains all of the same members of a given set.
|
||||||
|
* @param {Array.<*>} members
|
||||||
|
*/
|
||||||
|
toIncludeAllMembers(members: any[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toIncludeAnyMembers` when checking if an `Array` contains any of the members of a given set.
|
||||||
|
* @param {Array.<*>} members
|
||||||
|
*/
|
||||||
|
toIncludeAnyMembers(members: any[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toSatisfyAll` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean` for all values in an array.
|
||||||
|
* @param {Function} predicate
|
||||||
|
*/
|
||||||
|
toSatisfyAll(predicate: (n: any) => boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeBoolean` when checking if a value is a `Boolean`.
|
||||||
|
*/
|
||||||
|
toBeBoolean(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeTrue` when checking a value is equal (===) to `true`.
|
||||||
|
*/
|
||||||
|
toBeTrue(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeFalse` when checking a value is equal (===) to `false`.
|
||||||
|
*/
|
||||||
|
toBeFalse(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use .toBeDate when checking if a value is a Date.
|
||||||
|
*/
|
||||||
|
toBeDate(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeFunction` when checking if a value is a `Function`.
|
||||||
|
*/
|
||||||
|
toBeFunction(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toHaveBeenCalledBefore` when checking if a `Mock` was called before another `Mock`.
|
||||||
|
*
|
||||||
|
* Note: Required Jest version >22
|
||||||
|
* Note: Your mock functions will have to be asynchronous to cause the timestamps inside of Jest to occur in a differentJS event loop, otherwise the mock timestamps will all be the same
|
||||||
|
*
|
||||||
|
* @param {Mock} mock
|
||||||
|
*/
|
||||||
|
toHaveBeenCalledBefore(mock: JestMockFn<any, any>): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeNumber` when checking if a value is a `Number`.
|
||||||
|
*/
|
||||||
|
toBeNumber(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeNaN` when checking a value is `NaN`.
|
||||||
|
*/
|
||||||
|
toBeNaN(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeFinite` when checking if a value is a `Number`, not `NaN` or `Infinity`.
|
||||||
|
*/
|
||||||
|
toBeFinite(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBePositive` when checking if a value is a positive `Number`.
|
||||||
|
*/
|
||||||
|
toBePositive(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeNegative` when checking if a value is a negative `Number`.
|
||||||
|
*/
|
||||||
|
toBeNegative(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeEven` when checking if a value is an even `Number`.
|
||||||
|
*/
|
||||||
|
toBeEven(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeOdd` when checking if a value is an odd `Number`.
|
||||||
|
*/
|
||||||
|
toBeOdd(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeWithin` when checking if a number is in between the given bounds of: start (inclusive) and end (exclusive).
|
||||||
|
*
|
||||||
|
* @param {Number} start
|
||||||
|
* @param {Number} end
|
||||||
|
*/
|
||||||
|
toBeWithin(start: number, end: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeObject` when checking if a value is an `Object`.
|
||||||
|
*/
|
||||||
|
toBeObject(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toContainKey` when checking if an object contains the provided key.
|
||||||
|
*
|
||||||
|
* @param {String} key
|
||||||
|
*/
|
||||||
|
toContainKey(key: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toContainKeys` when checking if an object has all of the provided keys.
|
||||||
|
*
|
||||||
|
* @param {Array.<String>} keys
|
||||||
|
*/
|
||||||
|
toContainKeys(keys: string[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toContainAllKeys` when checking if an object only contains all of the provided keys.
|
||||||
|
*
|
||||||
|
* @param {Array.<String>} keys
|
||||||
|
*/
|
||||||
|
toContainAllKeys(keys: string[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toContainAnyKeys` when checking if an object contains at least one of the provided keys.
|
||||||
|
*
|
||||||
|
* @param {Array.<String>} keys
|
||||||
|
*/
|
||||||
|
toContainAnyKeys(keys: string[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toContainValue` when checking if an object contains the provided value.
|
||||||
|
*
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
|
toContainValue(value: any): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toContainValues` when checking if an object contains all of the provided values.
|
||||||
|
*
|
||||||
|
* @param {Array.<*>} values
|
||||||
|
*/
|
||||||
|
toContainValues(values: any[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toContainAllValues` when checking if an object only contains all of the provided values.
|
||||||
|
*
|
||||||
|
* @param {Array.<*>} values
|
||||||
|
*/
|
||||||
|
toContainAllValues(values: any[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toContainAnyValues` when checking if an object contains at least one of the provided values.
|
||||||
|
*
|
||||||
|
* @param {Array.<*>} values
|
||||||
|
*/
|
||||||
|
toContainAnyValues(values: any[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toContainEntry` when checking if an object contains the provided entry.
|
||||||
|
*
|
||||||
|
* @param {Array.<String, String>} entry
|
||||||
|
*/
|
||||||
|
toContainEntry(entry: [string, string]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toContainEntries` when checking if an object contains all of the provided entries.
|
||||||
|
*
|
||||||
|
* @param {Array.<Array.<String, String>>} entries
|
||||||
|
*/
|
||||||
|
toContainEntries(entries: [string, string][]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toContainAllEntries` when checking if an object only contains all of the provided entries.
|
||||||
|
*
|
||||||
|
* @param {Array.<Array.<String, String>>} entries
|
||||||
|
*/
|
||||||
|
toContainAllEntries(entries: [string, string][]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toContainAnyEntries` when checking if an object contains at least one of the provided entries.
|
||||||
|
*
|
||||||
|
* @param {Array.<Array.<String, String>>} entries
|
||||||
|
*/
|
||||||
|
toContainAnyEntries(entries: [string, string][]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeExtensible` when checking if an object is extensible.
|
||||||
|
*/
|
||||||
|
toBeExtensible(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeFrozen` when checking if an object is frozen.
|
||||||
|
*/
|
||||||
|
toBeFrozen(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeSealed` when checking if an object is sealed.
|
||||||
|
*/
|
||||||
|
toBeSealed(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toBeString` when checking if a value is a `String`.
|
||||||
|
*/
|
||||||
|
toBeString(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toEqualCaseInsensitive` when checking if a string is equal (===) to another ignoring the casing of both strings.
|
||||||
|
*
|
||||||
|
* @param {String} string
|
||||||
|
*/
|
||||||
|
toEqualCaseInsensitive(string: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toStartWith` when checking if a `String` starts with a given `String` prefix.
|
||||||
|
*
|
||||||
|
* @param {String} prefix
|
||||||
|
*/
|
||||||
|
toStartWith(prefix: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toEndWith` when checking if a `String` ends with a given `String` suffix.
|
||||||
|
*
|
||||||
|
* @param {String} suffix
|
||||||
|
*/
|
||||||
|
toEndWith(suffix: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toInclude` when checking if a `String` includes the given `String` substring.
|
||||||
|
*
|
||||||
|
* @param {String} substring
|
||||||
|
*/
|
||||||
|
toInclude(substring: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toIncludeRepeated` when checking if a `String` includes the given `String` substring the correct number of times.
|
||||||
|
*
|
||||||
|
* @param {String} substring
|
||||||
|
* @param {Number} times
|
||||||
|
*/
|
||||||
|
toIncludeRepeated(substring: string, times: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `.toIncludeMultiple` when checking if a `String` includes all of the given substrings.
|
||||||
|
*
|
||||||
|
* @param {Array.<String>} substring
|
||||||
|
*/
|
||||||
|
toIncludeMultiple(substring: string[]): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface JestExpectType {
|
||||||
|
not: JestExpectType & EnzymeMatchersType & DomTestingLibraryType & JestJQueryMatchersType & JestExtendedMatchersType,
|
||||||
/**
|
/**
|
||||||
* If you have a mock function, you can use .lastCalledWith to test what
|
* If you have a mock function, you can use .lastCalledWith to test what
|
||||||
* arguments it was last called with.
|
* arguments it was last called with.
|
||||||
@ -148,10 +511,6 @@ type JestExpectType = {
|
|||||||
* strict equality.
|
* strict equality.
|
||||||
*/
|
*/
|
||||||
toBe(value: any): void,
|
toBe(value: any): void,
|
||||||
/**
|
|
||||||
* Use .toHaveBeenCalled to ensure that a mock function got called.
|
|
||||||
*/
|
|
||||||
toBeCalled(): void,
|
|
||||||
/**
|
/**
|
||||||
* Use .toBeCalledWith to ensure that a mock function was called with
|
* Use .toBeCalledWith to ensure that a mock function was called with
|
||||||
* specific arguments.
|
* specific arguments.
|
||||||
@ -227,21 +586,55 @@ type JestExpectType = {
|
|||||||
* Use .toHaveBeenCalled to ensure that a mock function got called.
|
* Use .toHaveBeenCalled to ensure that a mock function got called.
|
||||||
*/
|
*/
|
||||||
toHaveBeenCalled(): void,
|
toHaveBeenCalled(): void,
|
||||||
|
toBeCalled(): void;
|
||||||
/**
|
/**
|
||||||
* Use .toHaveBeenCalledTimes to ensure that a mock function got called exact
|
* Use .toHaveBeenCalledTimes to ensure that a mock function got called exact
|
||||||
* number of times.
|
* number of times.
|
||||||
*/
|
*/
|
||||||
toHaveBeenCalledTimes(number: number): void,
|
toHaveBeenCalledTimes(number: number): void,
|
||||||
|
toBeCalledTimes(number: number): void;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
toHaveBeenNthCalledWith(nthCall: number, ...args: Array<any>): void;
|
||||||
|
nthCalledWith(nthCall: number, ...args: Array<any>): void;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
toHaveReturned(): void;
|
||||||
|
toReturn(): void;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
toHaveReturnedTimes(number: number): void;
|
||||||
|
toReturnTimes(number: number): void;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
toHaveReturnedWith(value: any): void;
|
||||||
|
toReturnWith(value: any): void;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
toHaveLastReturnedWith(value: any): void;
|
||||||
|
lastReturnedWith(value: any): void;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
toHaveNthReturnedWith(nthCall: number, value: any): void;
|
||||||
|
nthReturnedWith(nthCall: number, value: any): void;
|
||||||
/**
|
/**
|
||||||
* Use .toHaveBeenCalledWith to ensure that a mock function was called with
|
* Use .toHaveBeenCalledWith to ensure that a mock function was called with
|
||||||
* specific arguments.
|
* specific arguments.
|
||||||
*/
|
*/
|
||||||
toHaveBeenCalledWith(...args: Array<any>): void,
|
toHaveBeenCalledWith(...args: Array<any>): void,
|
||||||
|
toBeCalledWith(...args: Array<any>): void,
|
||||||
/**
|
/**
|
||||||
* Use .toHaveBeenLastCalledWith to ensure that a mock function was last called
|
* Use .toHaveBeenLastCalledWith to ensure that a mock function was last called
|
||||||
* with specific arguments.
|
* with specific arguments.
|
||||||
*/
|
*/
|
||||||
toHaveBeenLastCalledWith(...args: Array<any>): void,
|
toHaveBeenLastCalledWith(...args: Array<any>): void,
|
||||||
|
lastCalledWith(...args: Array<any>): void,
|
||||||
/**
|
/**
|
||||||
* Check that an object has a .length property and it is set to a certain
|
* Check that an object has a .length property and it is set to a certain
|
||||||
* numeric value.
|
* numeric value.
|
||||||
@ -260,9 +653,17 @@ type JestExpectType = {
|
|||||||
*/
|
*/
|
||||||
toMatchObject(object: Object | Array<Object>): void,
|
toMatchObject(object: Object | Array<Object>): void,
|
||||||
/**
|
/**
|
||||||
* This ensures that a React component matches the most recent snapshot.
|
* Use .toStrictEqual to check that a javascript object matches a subset of the properties of an object.
|
||||||
*/
|
*/
|
||||||
toMatchSnapshot(name?: string): void,
|
toStrictEqual(value: any): void,
|
||||||
|
/**
|
||||||
|
* This ensures that an Object matches the most recent snapshot.
|
||||||
|
*/
|
||||||
|
toMatchSnapshot(propertyMatchers?: {[key: string]: JestAsymmetricEqualityType}, name?: string): void,
|
||||||
|
/**
|
||||||
|
* This ensures that an Object matches the most recent snapshot.
|
||||||
|
*/
|
||||||
|
toMatchSnapshot(name: string): void,
|
||||||
/**
|
/**
|
||||||
* Use .toThrow to test that a function throws when it is called.
|
* Use .toThrow to test that a function throws when it is called.
|
||||||
* If you want to test that a specific error gets thrown, you can provide an
|
* If you want to test that a specific error gets thrown, you can provide an
|
||||||
@ -278,7 +679,7 @@ type JestExpectType = {
|
|||||||
* matching the most recent snapshot when it is called.
|
* matching the most recent snapshot when it is called.
|
||||||
*/
|
*/
|
||||||
toThrowErrorMatchingSnapshot(): void
|
toThrowErrorMatchingSnapshot(): void
|
||||||
};
|
}
|
||||||
|
|
||||||
type JestObjectType = {
|
type JestObjectType = {
|
||||||
/**
|
/**
|
||||||
@ -391,6 +792,13 @@ type JestObjectType = {
|
|||||||
* Executes only the macro task queue (i.e. all tasks queued by setTimeout()
|
* Executes only the macro task queue (i.e. all tasks queued by setTimeout()
|
||||||
* or setInterval() and setImmediate()).
|
* or setInterval() and setImmediate()).
|
||||||
*/
|
*/
|
||||||
|
advanceTimersByTime(msToRun: number): void,
|
||||||
|
/**
|
||||||
|
* Executes only the macro task queue (i.e. all tasks queued by setTimeout()
|
||||||
|
* or setInterval() and setImmediate()).
|
||||||
|
*
|
||||||
|
* Renamed to `advanceTimersByTime`.
|
||||||
|
*/
|
||||||
runTimersToTime(msToRun: number): void,
|
runTimersToTime(msToRun: number): void,
|
||||||
/**
|
/**
|
||||||
* Executes only the macro-tasks that are currently pending (i.e., only the
|
* Executes only the macro-tasks that are currently pending (i.e., only the
|
||||||
@ -424,7 +832,7 @@ type JestObjectType = {
|
|||||||
* Creates a mock function similar to jest.fn but also tracks calls to
|
* Creates a mock function similar to jest.fn but also tracks calls to
|
||||||
* object[methodName].
|
* object[methodName].
|
||||||
*/
|
*/
|
||||||
spyOn(object: Object, methodName: string): JestMockFn<any, any>,
|
spyOn(object: Object, methodName: string, accessType?: "get" | "set"): JestMockFn<any, any>,
|
||||||
/**
|
/**
|
||||||
* Set the default timeout interval for tests and before/after hooks in milliseconds.
|
* Set the default timeout interval for tests and before/after hooks in milliseconds.
|
||||||
* Note: The default timeout interval is 5 seconds if this method is not called.
|
* Note: The default timeout interval is 5 seconds if this method is not called.
|
||||||
@ -462,17 +870,17 @@ declare var describe: {
|
|||||||
/**
|
/**
|
||||||
* Creates a block that groups together several related tests in one "test suite"
|
* Creates a block that groups together several related tests in one "test suite"
|
||||||
*/
|
*/
|
||||||
(name: string, fn: () => void): void,
|
(name: JestTestName, fn: () => void): void,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only run this describe block
|
* Only run this describe block
|
||||||
*/
|
*/
|
||||||
only(name: string, fn: () => void): void,
|
only(name: JestTestName, fn: () => void): void,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skip running this describe block
|
* Skip running this describe block
|
||||||
*/
|
*/
|
||||||
skip(name: string, fn: () => void): void
|
skip(name: JestTestName, fn: () => void): void
|
||||||
};
|
};
|
||||||
|
|
||||||
/** An individual test unit */
|
/** An individual test unit */
|
||||||
@ -480,54 +888,54 @@ declare var it: {
|
|||||||
/**
|
/**
|
||||||
* An individual test unit
|
* An individual test unit
|
||||||
*
|
*
|
||||||
* @param {string} Name of Test
|
* @param {JestTestName} Name of Test
|
||||||
* @param {Function} Test
|
* @param {Function} Test
|
||||||
* @param {number} Timeout for the test, in milliseconds.
|
* @param {number} Timeout for the test, in milliseconds.
|
||||||
*/
|
*/
|
||||||
(
|
(
|
||||||
name: string,
|
name: JestTestName,
|
||||||
fn?: (done: () => void) => ?Promise<mixed>,
|
fn?: (done: () => void) => ?Promise<mixed>,
|
||||||
timeout?: number
|
timeout?: number
|
||||||
): void,
|
): void,
|
||||||
/**
|
/**
|
||||||
* Only run this test
|
* Only run this test
|
||||||
*
|
*
|
||||||
* @param {string} Name of Test
|
* @param {JestTestName} Name of Test
|
||||||
* @param {Function} Test
|
* @param {Function} Test
|
||||||
* @param {number} Timeout for the test, in milliseconds.
|
* @param {number} Timeout for the test, in milliseconds.
|
||||||
*/
|
*/
|
||||||
only(
|
only(
|
||||||
name: string,
|
name: JestTestName,
|
||||||
fn?: (done: () => void) => ?Promise<mixed>,
|
fn?: (done: () => void) => ?Promise<mixed>,
|
||||||
timeout?: number
|
timeout?: number
|
||||||
): void,
|
): void,
|
||||||
/**
|
/**
|
||||||
* Skip running this test
|
* Skip running this test
|
||||||
*
|
*
|
||||||
* @param {string} Name of Test
|
* @param {JestTestName} Name of Test
|
||||||
* @param {Function} Test
|
* @param {Function} Test
|
||||||
* @param {number} Timeout for the test, in milliseconds.
|
* @param {number} Timeout for the test, in milliseconds.
|
||||||
*/
|
*/
|
||||||
skip(
|
skip(
|
||||||
name: string,
|
name: JestTestName,
|
||||||
fn?: (done: () => void) => ?Promise<mixed>,
|
fn?: (done: () => void) => ?Promise<mixed>,
|
||||||
timeout?: number
|
timeout?: number
|
||||||
): void,
|
): void,
|
||||||
/**
|
/**
|
||||||
* Run the test concurrently
|
* Run the test concurrently
|
||||||
*
|
*
|
||||||
* @param {string} Name of Test
|
* @param {JestTestName} Name of Test
|
||||||
* @param {Function} Test
|
* @param {Function} Test
|
||||||
* @param {number} Timeout for the test, in milliseconds.
|
* @param {number} Timeout for the test, in milliseconds.
|
||||||
*/
|
*/
|
||||||
concurrent(
|
concurrent(
|
||||||
name: string,
|
name: JestTestName,
|
||||||
fn?: (done: () => void) => ?Promise<mixed>,
|
fn?: (done: () => void) => ?Promise<mixed>,
|
||||||
timeout?: number
|
timeout?: number
|
||||||
): void
|
): void
|
||||||
};
|
};
|
||||||
declare function fit(
|
declare function fit(
|
||||||
name: string,
|
name: JestTestName,
|
||||||
fn: (done: () => void) => ?Promise<mixed>,
|
fn: (done: () => void) => ?Promise<mixed>,
|
||||||
timeout?: number
|
timeout?: number
|
||||||
): void;
|
): void;
|
||||||
@ -542,23 +950,75 @@ declare var xit: typeof it;
|
|||||||
/** A disabled individual test */
|
/** A disabled individual test */
|
||||||
declare var xtest: typeof it;
|
declare var xtest: typeof it;
|
||||||
|
|
||||||
|
type JestPrettyFormatColors = {
|
||||||
|
comment: { close: string, open: string },
|
||||||
|
content: { close: string, open: string },
|
||||||
|
prop: { close: string, open: string },
|
||||||
|
tag: { close: string, open: string },
|
||||||
|
value: { close: string, open: string },
|
||||||
|
};
|
||||||
|
|
||||||
|
type JestPrettyFormatIndent = string => string;
|
||||||
|
type JestPrettyFormatRefs = Array<any>;
|
||||||
|
type JestPrettyFormatPrint = any => string;
|
||||||
|
type JestPrettyFormatStringOrNull = string | null;
|
||||||
|
|
||||||
|
type JestPrettyFormatOptions = {|
|
||||||
|
callToJSON: boolean,
|
||||||
|
edgeSpacing: string,
|
||||||
|
escapeRegex: boolean,
|
||||||
|
highlight: boolean,
|
||||||
|
indent: number,
|
||||||
|
maxDepth: number,
|
||||||
|
min: boolean,
|
||||||
|
plugins: JestPrettyFormatPlugins,
|
||||||
|
printFunctionName: boolean,
|
||||||
|
spacing: string,
|
||||||
|
theme: {|
|
||||||
|
comment: string,
|
||||||
|
content: string,
|
||||||
|
prop: string,
|
||||||
|
tag: string,
|
||||||
|
value: string,
|
||||||
|
|},
|
||||||
|
|};
|
||||||
|
|
||||||
|
type JestPrettyFormatPlugin = {
|
||||||
|
print: (
|
||||||
|
val: any,
|
||||||
|
serialize: JestPrettyFormatPrint,
|
||||||
|
indent: JestPrettyFormatIndent,
|
||||||
|
opts: JestPrettyFormatOptions,
|
||||||
|
colors: JestPrettyFormatColors,
|
||||||
|
) => string,
|
||||||
|
test: any => boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
type JestPrettyFormatPlugins = Array<JestPrettyFormatPlugin>;
|
||||||
|
|
||||||
/** The expect function is used every time you want to test a value */
|
/** The expect function is used every time you want to test a value */
|
||||||
declare var expect: {
|
declare var expect: {
|
||||||
/** The object that you want to make assertions against */
|
/** The object that you want to make assertions against */
|
||||||
(value: any): JestExpectType & JestPromiseType & EnzymeMatchersType,
|
(value: any): JestExpectType & JestPromiseType & EnzymeMatchersType & DomTestingLibraryType & JestJQueryMatchersType & JestExtendedMatchersType,
|
||||||
/** Add additional Jasmine matchers to Jest's roster */
|
/** Add additional Jasmine matchers to Jest's roster */
|
||||||
extend(matchers: { [name: string]: JestMatcher }): void,
|
extend(matchers: { [name: string]: JestMatcher }): void,
|
||||||
/** Add a module that formats application-specific data structures. */
|
/** Add a module that formats application-specific data structures. */
|
||||||
addSnapshotSerializer(serializer: (input: Object) => string): void,
|
addSnapshotSerializer(pluginModule: JestPrettyFormatPlugin): void,
|
||||||
assertions(expectedAssertions: number): void,
|
assertions(expectedAssertions: number): void,
|
||||||
hasAssertions(): void,
|
hasAssertions(): void,
|
||||||
any(value: mixed): JestAsymmetricEqualityType,
|
any(value: mixed): JestAsymmetricEqualityType,
|
||||||
anything(): void,
|
anything(): any,
|
||||||
arrayContaining(value: Array<mixed>): void,
|
arrayContaining(value: Array<mixed>): Array<mixed>,
|
||||||
objectContaining(value: Object): void,
|
objectContaining(value: Object): Object,
|
||||||
/** Matches any received string that contains the exact expected string. */
|
/** Matches any received string that contains the exact expected string. */
|
||||||
stringContaining(value: string): void,
|
stringContaining(value: string): string,
|
||||||
stringMatching(value: string | RegExp): void
|
stringMatching(value: string | RegExp): string,
|
||||||
|
not: {
|
||||||
|
arrayContaining: (value: $ReadOnlyArray<mixed>) => Array<mixed>,
|
||||||
|
objectContaining: (value: {}) => Object,
|
||||||
|
stringContaining: (value: string) => string,
|
||||||
|
stringMatching: (value: string | RegExp) => string,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO handle return type
|
// TODO handle return type
|
||||||
@ -575,14 +1035,14 @@ declare var jest: JestObjectType;
|
|||||||
declare var jasmine: {
|
declare var jasmine: {
|
||||||
DEFAULT_TIMEOUT_INTERVAL: number,
|
DEFAULT_TIMEOUT_INTERVAL: number,
|
||||||
any(value: mixed): JestAsymmetricEqualityType,
|
any(value: mixed): JestAsymmetricEqualityType,
|
||||||
anything(): void,
|
anything(): any,
|
||||||
arrayContaining(value: Array<mixed>): void,
|
arrayContaining(value: Array<mixed>): Array<mixed>,
|
||||||
clock(): JestClockType,
|
clock(): JestClockType,
|
||||||
createSpy(name: string): JestSpyType,
|
createSpy(name: string): JestSpyType,
|
||||||
createSpyObj(
|
createSpyObj(
|
||||||
baseName: string,
|
baseName: string,
|
||||||
methodNames: Array<string>
|
methodNames: Array<string>
|
||||||
): { [methodName: string]: JestSpyType },
|
): { [methodName: string]: JestSpyType },
|
||||||
objectContaining(value: Object): void,
|
objectContaining(value: Object): Object,
|
||||||
stringMatching(value: string): void
|
stringMatching(value: string): string
|
||||||
};
|
};
|
26
package.json
26
package.json
@ -52,11 +52,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "6.1.3",
|
"@commitlint/cli": "6.1.3",
|
||||||
"@commitlint/config-conventional": "6.1.3",
|
"@commitlint/config-conventional": "6.1.3",
|
||||||
"@verdaccio/types": "3.0.0",
|
"@verdaccio/types": "3.0.1",
|
||||||
"babel-cli": "6.26.0",
|
"babel-cli": "6.26.0",
|
||||||
"babel-core": "6.26.0",
|
"babel-core": "6.26.0",
|
||||||
"babel-eslint": "8.2.2",
|
"babel-eslint": "8.2.2",
|
||||||
"babel-jest": "22.4.3",
|
"babel-jest": "23.2.0",
|
||||||
"babel-loader": "7.1.4",
|
"babel-loader": "7.1.4",
|
||||||
"babel-plugin-flow-runtime": "0.17.0",
|
"babel-plugin-flow-runtime": "0.17.0",
|
||||||
"babel-plugin-syntax-dynamic-import": "6.18.0",
|
"babel-plugin-syntax-dynamic-import": "6.18.0",
|
||||||
@ -82,14 +82,14 @@
|
|||||||
"element-theme-default": "1.4.13",
|
"element-theme-default": "1.4.13",
|
||||||
"enzyme": "3.3.0",
|
"enzyme": "3.3.0",
|
||||||
"enzyme-adapter-react-16": "1.1.1",
|
"enzyme-adapter-react-16": "1.1.1",
|
||||||
"eslint": "4.18.2",
|
"eslint": "5.0.1",
|
||||||
"eslint-config-google": "0.9.1",
|
"eslint-config-google": "0.9.1",
|
||||||
"eslint-loader": "2.0.0",
|
"eslint-loader": "2.0.0",
|
||||||
"eslint-plugin-babel": "4.1.2",
|
"eslint-plugin-babel": "4.1.2",
|
||||||
"eslint-plugin-flowtype": "2.46.1",
|
"eslint-plugin-flowtype": "2.49.3",
|
||||||
"eslint-plugin-import": "2.9.0",
|
"eslint-plugin-import": "2.13.0",
|
||||||
"eslint-plugin-jest": "21.14.0",
|
"eslint-plugin-jest": "21.17.0",
|
||||||
"eslint-plugin-react": "7.7.0",
|
"eslint-plugin-react": "7.10.0",
|
||||||
"file-loader": "1.1.11",
|
"file-loader": "1.1.11",
|
||||||
"flow-bin": "0.69.0",
|
"flow-bin": "0.69.0",
|
||||||
"flow-runtime": "0.17.0",
|
"flow-runtime": "0.17.0",
|
||||||
@ -99,10 +99,10 @@
|
|||||||
"husky": "0.15.0-rc.8",
|
"husky": "0.15.0-rc.8",
|
||||||
"identity-obj-proxy": "3.0.0",
|
"identity-obj-proxy": "3.0.0",
|
||||||
"in-publish": "2.0.0",
|
"in-publish": "2.0.0",
|
||||||
"jest": "22.4.3",
|
"jest": "23.2.0",
|
||||||
"jest-environment-jsdom": "22.4.3",
|
"jest-environment-jsdom": "23.2.0",
|
||||||
"jest-environment-jsdom-global": "1.0.3",
|
"jest-environment-jsdom-global": "1.1.0",
|
||||||
"jest-environment-node": "22.4.3",
|
"jest-environment-node": "23.2.0",
|
||||||
"localstorage-memory": "1.0.2",
|
"localstorage-memory": "1.0.2",
|
||||||
"mini-css-extract-plugin": "0.4.0",
|
"mini-css-extract-plugin": "0.4.0",
|
||||||
"node-mocks-http": "1.6.7",
|
"node-mocks-http": "1.6.7",
|
||||||
@ -154,10 +154,10 @@
|
|||||||
"flow": "flow",
|
"flow": "flow",
|
||||||
"pretest": "npm run code:build",
|
"pretest": "npm run code:build",
|
||||||
"test": "npm run test:unit",
|
"test": "npm run test:unit",
|
||||||
"test:unit": "cross-env NODE_ENV=test BABEL_ENV=test TZ=UTC jest --config ./jest.config.unit.js --maxWorkers 2",
|
"test:unit": "cross-env NODE_ENV=test BABEL_ENV=test TZ=UTC jest --config ./jest.config.js --maxWorkers 2",
|
||||||
"test:functional": "cross-env NODE_ENV=testOldEnv jest --config ./test/jest.config.functional.js --testPathPattern ./test/functional/index*",
|
"test:functional": "cross-env NODE_ENV=testOldEnv jest --config ./test/jest.config.functional.js --testPathPattern ./test/functional/index*",
|
||||||
"test:e2e": "cross-env BABEL_ENV=testOldEnv jest --config ./test/jest.config.e2e.js",
|
"test:e2e": "cross-env BABEL_ENV=testOldEnv jest --config ./test/jest.config.e2e.js",
|
||||||
"test:all": "npm run test && npm run test:functional && npm run test:e2e",
|
"test:all": "npm run build:webui && npm run test && npm run test:functional && npm run test:e2e",
|
||||||
"pre:ci": "npm run lint && npm run build:webui",
|
"pre:ci": "npm run lint && npm run build:webui",
|
||||||
"commitmsg": "commitlint -e $GIT_PARAMS",
|
"commitmsg": "commitlint -e $GIT_PARAMS",
|
||||||
"coverage:publish": "codecov",
|
"coverage:publish": "codecov",
|
||||||
|
@ -15,13 +15,13 @@ import type {$ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler,
|
|||||||
import type {Config as IConfig} from '@verdaccio/types';
|
import type {Config as IConfig} from '@verdaccio/types';
|
||||||
import {ErrorCode} from '../lib/utils';
|
import {ErrorCode} from '../lib/utils';
|
||||||
import {API_ERROR, HTTP_STATUS} from '../lib/constants';
|
import {API_ERROR, HTTP_STATUS} from '../lib/constants';
|
||||||
|
import AppConfig from '../lib/config';
|
||||||
|
|
||||||
const LoggerApp = require('../lib/logger');
|
const LoggerApp = require('../lib/logger');
|
||||||
const Config = require('../lib/config');
|
|
||||||
const Middleware = require('./middleware');
|
const Middleware = require('./middleware');
|
||||||
const Cats = require('../lib/status-cats');
|
const Cats = require('../lib/status-cats');
|
||||||
|
|
||||||
const defineAPI = function(config: Config, storage: IStorageHandler) {
|
const defineAPI = function(config: IConfig, storage: IStorageHandler) {
|
||||||
const auth: IAuth = new Auth(config);
|
const auth: IAuth = new Auth(config);
|
||||||
const app: $Application = express();
|
const app: $Application = express();
|
||||||
// run in production mode by default, just in case
|
// run in production mode by default, just in case
|
||||||
@ -103,7 +103,7 @@ const defineAPI = function(config: Config, storage: IStorageHandler) {
|
|||||||
|
|
||||||
export default async function(configHash: any) {
|
export default async function(configHash: any) {
|
||||||
LoggerApp.setup(configHash.logs);
|
LoggerApp.setup(configHash.logs);
|
||||||
const config: IConfig = new Config(configHash);
|
const config: IConfig = new AppConfig(configHash);
|
||||||
const storage: IStorageHandler = new Storage(config);
|
const storage: IStorageHandler = new Storage(config);
|
||||||
// waits until init calls have been intialized
|
// waits until init calls have been intialized
|
||||||
await storage.init(config);
|
await storage.init(config);
|
||||||
|
34
src/lib/auth-utils.js
Normal file
34
src/lib/auth-utils.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import {ErrorCode} from './utils';
|
||||||
|
import {API_ERROR} from './constants';
|
||||||
|
|
||||||
|
export function allow_action(action) {
|
||||||
|
return function(user, pkg, callback) {
|
||||||
|
const {name, groups} = user;
|
||||||
|
const hasPermission = pkg[action].some((group) => name === group || groups.includes(group));
|
||||||
|
|
||||||
|
if (hasPermission) {
|
||||||
|
return callback(null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
callback(ErrorCode.getForbidden(`user ${name} is not allowed to ${action} package ${pkg.name}`));
|
||||||
|
} else {
|
||||||
|
callback(ErrorCode.getForbidden(`unregistered users are not allowed to ${action} package ${pkg.name}`));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultPlugins() {
|
||||||
|
return {
|
||||||
|
authenticate(user, password, cb) {
|
||||||
|
cb(ErrorCode.getForbidden(API_ERROR.BAD_USERNAME_PASSWORD));
|
||||||
|
},
|
||||||
|
|
||||||
|
add_user(user, password, cb) {
|
||||||
|
return cb(ErrorCode.getConflict(API_ERROR.BAD_USERNAME_PASSWORD));
|
||||||
|
},
|
||||||
|
|
||||||
|
allow_access: allow_action('access'),
|
||||||
|
allow_publish: allow_action('publish'),
|
||||||
|
};
|
||||||
|
}
|
113
src/lib/auth.js
113
src/lib/auth.js
@ -2,20 +2,20 @@
|
|||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {loadPlugin} from '../lib/plugin-loader';
|
import {loadPlugin} from '../lib/plugin-loader';
|
||||||
import {ErrorCode} from './utils';
|
import {buildBase64Buffer, ErrorCode} from './utils';
|
||||||
import {aesDecrypt, aesEncrypt, signPayload, verifyPayload} from './crypto-utils';
|
import {aesDecrypt, aesEncrypt, signPayload, verifyPayload} from './crypto-utils';
|
||||||
|
|
||||||
import type {Config, Logger, Callback} from '@verdaccio/types';
|
import type {Config, Logger, Callback} from '@verdaccio/types';
|
||||||
import type {$Response, NextFunction} from 'express';
|
import type {$Response, NextFunction} from 'express';
|
||||||
import type {$RequestExtend, JWTPayload} from '../../types';
|
import type {$RequestExtend, JWTPayload} from '../../types';
|
||||||
import {ROLES} from './constants';
|
import {API_ERROR, HTTP_STATUS, ROLES, TOKEN_BASIC, TOKEN_BEARER} from './constants';
|
||||||
|
import {getMatchedPackagesSpec} from './config-utils';
|
||||||
|
import type {IAuth} from '../../types';
|
||||||
|
import {getDefaultPlugins} from './auth-utils';
|
||||||
|
|
||||||
const LoggerApi = require('./logger');
|
const LoggerApi = require('./logger');
|
||||||
/**
|
|
||||||
* Handles the authentification, load auth plugins.
|
class Auth implements IAuth {
|
||||||
*/
|
|
||||||
class Auth {
|
|
||||||
config: Config;
|
config: Config;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
secret: string;
|
secret: string;
|
||||||
@ -31,48 +31,20 @@ class Auth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_loadPlugin(config: Config) {
|
_loadPlugin(config: Config) {
|
||||||
const plugin_params = {
|
const pluginOptions = {
|
||||||
config,
|
config,
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
};
|
};
|
||||||
|
|
||||||
return loadPlugin(config, config.auth, plugin_params, function(p) {
|
return loadPlugin(config, config.auth, pluginOptions, (plugin) => {
|
||||||
return p.authenticate || p.allow_access || p.allow_publish;
|
const {authenticate, allow_access, allow_publish} = plugin;
|
||||||
|
|
||||||
|
return authenticate || allow_access || allow_publish;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_applyDefaultPlugins() {
|
_applyDefaultPlugins() {
|
||||||
const allow_action = function(action) {
|
this.plugins.push(getDefaultPlugins());
|
||||||
return function(user, pkg, cb) {
|
|
||||||
const ok = pkg[action].reduce(function(prev, curr) {
|
|
||||||
if (user.name === curr || user.groups.indexOf(curr) !== -1) return true;
|
|
||||||
return prev;
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
return cb(null, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.name) {
|
|
||||||
cb(ErrorCode.getForbidden(`user ${user.name} is not allowed to ${action} package ${pkg.name}`));
|
|
||||||
} else {
|
|
||||||
cb(ErrorCode.getForbidden(`unregistered users are not allowed to ${action} package ${pkg.name}`));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
this.plugins.push({
|
|
||||||
authenticate: function(user, password, cb) {
|
|
||||||
cb(ErrorCode.getForbidden('bad username/password, access denied'));
|
|
||||||
},
|
|
||||||
|
|
||||||
add_user: function(user, password, cb) {
|
|
||||||
return cb(ErrorCode.getConflict('bad username/password, access denied'));
|
|
||||||
},
|
|
||||||
|
|
||||||
allow_access: allow_action('access'),
|
|
||||||
allow_publish: allow_action('publish'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticate(user: string, password: string, cb: Callback) {
|
authenticate(user: string, password: string, cb: Callback) {
|
||||||
@ -80,7 +52,7 @@ class Auth {
|
|||||||
(function next() {
|
(function next() {
|
||||||
const plugin = plugins.shift();
|
const plugin = plugins.shift();
|
||||||
|
|
||||||
if (typeof(plugin.authenticate) !== 'function') {
|
if (_.isFunction(plugin.authenticate) === false) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,12 +70,12 @@ class Auth {
|
|||||||
// Info: Cannot use `== false to check falsey values`
|
// Info: Cannot use `== false to check falsey values`
|
||||||
if (!!groups && groups.length !== 0) {
|
if (!!groups && groups.length !== 0) {
|
||||||
// TODO: create a better understanding of expectations
|
// TODO: create a better understanding of expectations
|
||||||
if (typeof groups === 'string') {
|
if (_.isString(groups)) {
|
||||||
throw new TypeError('invalid type for function');
|
throw new TypeError('invalid type for function');
|
||||||
}
|
}
|
||||||
const isGroupValid: boolean = _.isArray(groups);
|
const isGroupValid: boolean = _.isArray(groups);
|
||||||
if (!isGroupValid) {
|
if (!isGroupValid) {
|
||||||
throw new TypeError('user groups is different than an array');
|
throw new TypeError(API_ERROR.BAD_FORMAT_USER_GROUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(err, authenticatedUser(user, groups));
|
return cb(err, authenticatedUser(user, groups));
|
||||||
@ -115,19 +87,19 @@ class Auth {
|
|||||||
|
|
||||||
add_user(user: string, password: string, cb: Callback) {
|
add_user(user: string, password: string, cb: Callback) {
|
||||||
let self = this;
|
let self = this;
|
||||||
let plugins = this.plugins.slice(0)
|
let plugins = this.plugins.slice(0);
|
||||||
|
|
||||||
;(function next() {
|
(function next() {
|
||||||
let p = plugins.shift();
|
let plugin = plugins.shift();
|
||||||
let n = 'adduser';
|
let method = 'adduser';
|
||||||
if (typeof(p[n]) !== 'function') {
|
if (_.isFunction(plugin[method]) === false) {
|
||||||
n = 'add_user';
|
method = 'add_user';
|
||||||
}
|
}
|
||||||
if (typeof(p[n]) !== 'function') {
|
if (_.isFunction[method] === false) {
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
// p.add_user() execution
|
// p.add_user() execution
|
||||||
p[n](user, password, function(err, ok) {
|
plugin[method](user, password, function(err, ok) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
@ -146,7 +118,7 @@ class Auth {
|
|||||||
allow_access(packageName: string, user: string, callback: Callback) {
|
allow_access(packageName: string, user: string, callback: Callback) {
|
||||||
let plugins = this.plugins.slice(0);
|
let plugins = this.plugins.slice(0);
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
let pkg = Object.assign({name: packageName}, this.config.getMatchedPackagesSpec(packageName));
|
let pkg = Object.assign({name: packageName}, getMatchedPackagesSpec(packageName, this.config.packages));
|
||||||
|
|
||||||
(function next() {
|
(function next() {
|
||||||
const plugin = plugins.shift();
|
const plugin = plugins.shift();
|
||||||
@ -175,7 +147,7 @@ class Auth {
|
|||||||
allow_publish(packageName: string, user: string, callback: Callback) {
|
allow_publish(packageName: string, user: string, callback: Callback) {
|
||||||
let plugins = this.plugins.slice(0);
|
let plugins = this.plugins.slice(0);
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
let pkg = Object.assign({name: packageName}, this.config.getMatchedPackagesSpec(packageName));
|
let pkg = Object.assign({name: packageName}, getMatchedPackagesSpec(packageName, this.config.packages));
|
||||||
|
|
||||||
(function next() {
|
(function next() {
|
||||||
const plugin = plugins.shift();
|
const plugin = plugins.shift();
|
||||||
@ -184,7 +156,7 @@ class Auth {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.allow_publish(user, pkg, function(err, ok) {
|
plugin.allow_publish(user, pkg, (err, ok) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
@ -219,13 +191,13 @@ class Auth {
|
|||||||
req.remote_user = buildAnonymousUser();
|
req.remote_user = buildAnonymousUser();
|
||||||
|
|
||||||
const authorization = req.headers.authorization;
|
const authorization = req.headers.authorization;
|
||||||
if (authorization == null) {
|
if (_.isNil(authorization)) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = authorization.split(' ');
|
const parts = authorization.split(' ');
|
||||||
if (parts.length !== 2) {
|
if (parts.length !== 2) {
|
||||||
return next( ErrorCode.getBadRequest('bad authorization header') );
|
return next( ErrorCode.getBadRequest(API_ERROR.BAD_AUTH_HEADER) );
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = this._parseCredentials(parts);
|
const credentials = this._parseCredentials(parts);
|
||||||
@ -257,12 +229,12 @@ class Auth {
|
|||||||
_parseCredentials(parts: Array<string>) {
|
_parseCredentials(parts: Array<string>) {
|
||||||
let credentials;
|
let credentials;
|
||||||
const scheme = parts[0];
|
const scheme = parts[0];
|
||||||
if (scheme.toUpperCase() === 'BASIC') {
|
if (scheme.toUpperCase() === TOKEN_BASIC.toUpperCase()) {
|
||||||
credentials = new Buffer(parts[1], 'base64').toString();
|
credentials = buildBase64Buffer(parts[1]).toString();
|
||||||
this.logger.info('basic authentication is deprecated, please use JWT instead');
|
this.logger.info(API_ERROR.DEPRECATED_BASIC_HEADER);
|
||||||
return credentials;
|
return credentials;
|
||||||
} else if (scheme.toUpperCase() === 'BEARER') {
|
} else if (scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
|
||||||
const token = new Buffer(parts[1], 'base64');
|
const token = buildBase64Buffer(parts[1]);
|
||||||
|
|
||||||
credentials = aesDecrypt(token, this.secret).toString('utf8');
|
credentials = aesDecrypt(token, this.secret).toString('utf8');
|
||||||
return credentials;
|
return credentials;
|
||||||
@ -276,17 +248,17 @@ class Auth {
|
|||||||
*/
|
*/
|
||||||
webUIJWTmiddleware() {
|
webUIJWTmiddleware() {
|
||||||
return (req: $RequestExtend, res: $Response, _next: NextFunction) => {
|
return (req: $RequestExtend, res: $Response, _next: NextFunction) => {
|
||||||
if (req.remote_user !== null && req.remote_user.name !== undefined) {
|
if (_.isNull(req.remote_user) === false && _.isNil(req.remote_user.name) === false) {
|
||||||
return _next();
|
return _next();
|
||||||
}
|
}
|
||||||
|
|
||||||
req.pause();
|
req.pause();
|
||||||
const next = function(_err) {
|
const next = () => {
|
||||||
req.resume();
|
req.resume();
|
||||||
return _next();
|
return _next();
|
||||||
};
|
};
|
||||||
|
|
||||||
const token = (req.headers.authorization || '').replace('Bearer ', '');
|
const token = (req.headers.authorization || '').replace(`${TOKEN_BEARER} `, '');
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
@ -328,7 +300,7 @@ class Auth {
|
|||||||
try {
|
try {
|
||||||
decoded = verifyPayload(token, this.secret);
|
decoded = verifyPayload(token, this.secret);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw ErrorCode.getCode(401, err.message);
|
throw ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return decoded;
|
return decoded;
|
||||||
@ -350,7 +322,7 @@ function buildAnonymousUser() {
|
|||||||
return {
|
return {
|
||||||
name: undefined,
|
name: undefined,
|
||||||
// groups without '$' are going to be deprecated eventually
|
// groups without '$' are going to be deprecated eventually
|
||||||
groups: ['$all', '$anonymous', '@all', '@anonymous'],
|
groups: [ROLES.$ALL, ROLES.$ANONYMOUS, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_ANONUMOUS],
|
||||||
real_groups: [],
|
real_groups: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -361,7 +333,12 @@ function buildAnonymousUser() {
|
|||||||
*/
|
*/
|
||||||
function authenticatedUser(name: string, pluginGroups: Array<any>) {
|
function authenticatedUser(name: string, pluginGroups: Array<any>) {
|
||||||
const isGroupValid: boolean = _.isArray(pluginGroups);
|
const isGroupValid: boolean = _.isArray(pluginGroups);
|
||||||
const groups = (isGroupValid ? pluginGroups : []).concat([ROLES.$ALL, ROLES.$AUTH, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_AUTH, ROLES.ALL]);
|
const groups = (isGroupValid ? pluginGroups : []).concat([
|
||||||
|
ROLES.$ALL,
|
||||||
|
ROLES.$AUTH,
|
||||||
|
ROLES.DEPRECATED_ALL,
|
||||||
|
ROLES.DEPRECATED_AUTH,
|
||||||
|
ROLES.ALL]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
|
9
src/lib/bootstrap.js
vendored
9
src/lib/bootstrap.js
vendored
@ -64,9 +64,12 @@ export function getListListenAddresses(argListen: string, configListen: mixed) {
|
|||||||
* @param {String} pkgVersion
|
* @param {String} pkgVersion
|
||||||
* @param {String} pkgName
|
* @param {String} pkgName
|
||||||
*/
|
*/
|
||||||
function startVerdaccio(config: any, cliListen: string,
|
function startVerdaccio(config: any,
|
||||||
configPath: string, pkgVersion: string,
|
cliListen: string,
|
||||||
pkgName: string, callback: Callback) {
|
configPath: string,
|
||||||
|
pkgVersion: string,
|
||||||
|
pkgName: string,
|
||||||
|
callback: Callback) {
|
||||||
if (isObject(config) === false) {
|
if (isObject(config) === false) {
|
||||||
throw new Error('config file must be an object');
|
throw new Error('config file must be an object');
|
||||||
}
|
}
|
||||||
|
128
src/lib/config-utils.js
Normal file
128
src/lib/config-utils.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// @flow
|
||||||
|
import _ from 'lodash';
|
||||||
|
import assert from 'assert';
|
||||||
|
import minimatch from 'minimatch';
|
||||||
|
|
||||||
|
import {ErrorCode} from './utils';
|
||||||
|
|
||||||
|
import type {PackageList, UpLinksConfList} from '@verdaccio/types';
|
||||||
|
import type {MatchedPackage} from '../../types';
|
||||||
|
|
||||||
|
const BLACKLIST = {
|
||||||
|
all: true,
|
||||||
|
anonymous: true,
|
||||||
|
undefined: true,
|
||||||
|
owner: true,
|
||||||
|
none: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalise user list.
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
export function normalizeUserlist(oldFormat: any, newFormat: any) {
|
||||||
|
const result = [];
|
||||||
|
/* eslint prefer-rest-params: "off" */
|
||||||
|
|
||||||
|
for (let i=0; i < arguments.length; i++) {
|
||||||
|
if (arguments[i] == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it's a string, split it to array
|
||||||
|
if (_.isString(arguments[i])) {
|
||||||
|
result.push(arguments[i].split(/\s+/));
|
||||||
|
} else if (Array.isArray(arguments[i])) {
|
||||||
|
result.push(arguments[i]);
|
||||||
|
} else {
|
||||||
|
throw ErrorCode.getInternalError('CONFIG: bad package acl (array or string expected): ' + JSON.stringify(arguments[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _.flatten(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function uplinkSanityCheck(uplinks: UpLinksConfList, users: any = BLACKLIST) {
|
||||||
|
const newUplinks = _.clone(uplinks);
|
||||||
|
let newUsers = _.clone(users);
|
||||||
|
|
||||||
|
for (let 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) {
|
||||||
|
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: any) {
|
||||||
|
const uplinks = _.clone(configUpLinks);
|
||||||
|
|
||||||
|
for (let 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): MatchedPackage);
|
||||||
|
const proxyList = typeof matchedPkg !== 'undefined' ? matchedPkg.proxy : [];
|
||||||
|
if (proxyList) {
|
||||||
|
return proxyList.some((curr) => upLink === curr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMatchedPackagesSpec(pkg: string, packages: PackageList): MatchedPackage {
|
||||||
|
for (let i in packages) {
|
||||||
|
// $FlowFixMe
|
||||||
|
if (minimatch.makeRe(i).exec(pkg)) {
|
||||||
|
return packages[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalisePackageAccess(packages: PackageList): PackageList {
|
||||||
|
const normalizedPkgs: PackageList = {...packages};
|
||||||
|
// add a default rule for all packages to make writing plugins easier
|
||||||
|
if (_.isNil(normalizedPkgs['**'])) {
|
||||||
|
normalizedPkgs['**'] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let pkg in packages) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(packages, pkg)) {
|
||||||
|
assert(_.isObject(packages[pkg]) && _.isArray(packages[pkg]) === false,
|
||||||
|
`CONFIG: bad "'${pkg}'" package description (object expected)`);
|
||||||
|
normalizedPkgs[pkg].access = normalizeUserlist(packages[pkg].allow_access, packages[pkg].access);
|
||||||
|
delete normalizedPkgs[pkg].allow_access;
|
||||||
|
normalizedPkgs[pkg].publish = normalizeUserlist(packages[pkg].allow_publish, packages[pkg].publish);
|
||||||
|
delete normalizedPkgs[pkg].allow_publish;
|
||||||
|
normalizedPkgs[pkg].proxy = normalizeUserlist(packages[pkg].proxy_access, packages[pkg].proxy);
|
||||||
|
delete normalizedPkgs[pkg].proxy_access;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedPkgs;
|
||||||
|
}
|
||||||
|
|
@ -1,210 +1,107 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
import {generateRandomHexString} from './crypto-utils';
|
import {generateRandomHexString} from './crypto-utils';
|
||||||
|
import {
|
||||||
|
getMatchedPackagesSpec,
|
||||||
|
normalisePackageAccess,
|
||||||
|
sanityCheckUplinksProps,
|
||||||
|
uplinkSanityCheck} from './config-utils';
|
||||||
|
import {getUserAgent, isObject} from './utils';
|
||||||
|
import {APP_ERROR} from './constants';
|
||||||
|
|
||||||
const assert = require('assert');
|
import type {
|
||||||
const _ = require('lodash');
|
PackageList,
|
||||||
const Error = require('http-errors');
|
Config as AppConfig,
|
||||||
const minimatch = require('minimatch');
|
Logger,
|
||||||
|
} from '@verdaccio/types';
|
||||||
|
|
||||||
const Utils = require('./utils');
|
import type {MatchedPackage, StartUpConfig} from '../../types';
|
||||||
const pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars
|
|
||||||
const pkgVersion = module.exports.version;
|
const LoggerApi = require('./logger');
|
||||||
const pkgName = module.exports.name;
|
const strategicConfigProps = ['uplinks', 'packages'];
|
||||||
const strategicConfigProps = ['users', 'uplinks', 'packages'];
|
|
||||||
const allowedEnvConfig = ['http_proxy', 'https_proxy', 'no_proxy'];
|
const allowedEnvConfig = ['http_proxy', 'https_proxy', 'no_proxy'];
|
||||||
|
|
||||||
/**
|
|
||||||
* [[a, [b, c]], d] -> [a, b, c, d]
|
|
||||||
* @param {*} array
|
|
||||||
* @return {Array}
|
|
||||||
*/
|
|
||||||
function flatten(array) {
|
|
||||||
let result = [];
|
|
||||||
for (let i=0; i < array.length; i++) {
|
|
||||||
if (Array.isArray(array[i])) {
|
|
||||||
/* eslint prefer-spread: "off" */
|
|
||||||
result.push.apply(result, flatten(array[i]));
|
|
||||||
} else {
|
|
||||||
result.push(array[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkUserOrUplink(item, users) {
|
|
||||||
assert(item !== 'all' && item !== 'owner'
|
|
||||||
&& item !== 'anonymous' && item !== 'undefined' && item !== 'none', 'CONFIG: reserved user/uplink name: ' + item);
|
|
||||||
assert(!item.match(/\s/), 'CONFIG: invalid user name: ' + item);
|
|
||||||
assert(users[item] == null, 'CONFIG: duplicate user/uplink name: ' + item);
|
|
||||||
users[item] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalise user list.
|
|
||||||
* @return {Array}
|
|
||||||
*/
|
|
||||||
function normalizeUserlist() {
|
|
||||||
let result = [];
|
|
||||||
/* eslint prefer-rest-params: "off" */
|
|
||||||
|
|
||||||
for (let i=0; i < arguments.length; i++) {
|
|
||||||
if (arguments[i] == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it's a string, split it to array
|
|
||||||
if (typeof(arguments[i]) === 'string') {
|
|
||||||
result.push(arguments[i].split(/\s+/));
|
|
||||||
} else if (Array.isArray(arguments[i])) {
|
|
||||||
result.push(arguments[i]);
|
|
||||||
} else {
|
|
||||||
throw Error('CONFIG: bad package acl (array or string expected): ' + JSON.stringify(arguments[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return flatten(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coordinates the application configuration
|
* Coordinates the application configuration
|
||||||
*/
|
*/
|
||||||
class Config {
|
class Config implements AppConfig {
|
||||||
/**
|
logger: Logger;
|
||||||
* @param {*} config config the content
|
user_agent: string;
|
||||||
*/
|
secret: string;
|
||||||
constructor(config) {
|
uplinks: any;
|
||||||
|
packages: PackageList;
|
||||||
|
users: any;
|
||||||
|
server_id: string;
|
||||||
|
self_path: string;
|
||||||
|
storage: string | void;
|
||||||
|
$key: any;
|
||||||
|
$value: any;
|
||||||
|
|
||||||
|
constructor(config: StartUpConfig) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const users = {
|
this.logger = LoggerApi.logger;
|
||||||
all: true,
|
this.self_path = config.self_path;
|
||||||
anonymous: true,
|
this.storage = config.storage;
|
||||||
undefined: true,
|
|
||||||
owner: true,
|
|
||||||
none: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let configProp in config) {
|
for (let configProp in config) {
|
||||||
if (self[configProp] == null) {
|
if (self[configProp] == null) {
|
||||||
self[configProp] = config[configProp];
|
self[configProp] = config[configProp];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!self.user_agent) {
|
|
||||||
self.user_agent = `${pkgName}/${pkgVersion}`;
|
if (_.isNil(this.user_agent)) {
|
||||||
|
this.user_agent = getUserAgent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// some weird shell scripts are valid yaml files parsed as string
|
// some weird shell scripts are valid yaml files parsed as string
|
||||||
assert.equal(typeof(config), 'object', 'CONFIG: it doesn\'t look like a valid config file');
|
assert(_.isObject(config), APP_ERROR.CONFIG_NOT_VALID);
|
||||||
|
|
||||||
// sanity check for strategic config properties
|
// sanity check for strategic config properties
|
||||||
strategicConfigProps.forEach(function(x) {
|
strategicConfigProps.forEach(function(x) {
|
||||||
if (self[x] == null) self[x] = {};
|
if (self[x] == null) {
|
||||||
assert(Utils.isObject(self[x]), `CONFIG: bad "${x}" value (object expected)`);
|
self[x] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(isObject(self[x]), `CONFIG: bad "${x}" value (object expected)`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// sanity check for users
|
this.uplinks = sanityCheckUplinksProps(uplinkSanityCheck(this.uplinks));
|
||||||
for (let i in self.users) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(self.users, i)) {
|
if (_.isNil(this.users) === false) {
|
||||||
checkUserOrUplink(i, users);
|
this.logger.warn(`[users]: property on configuration file
|
||||||
}
|
is not longer supported, property being ignored`);
|
||||||
}
|
|
||||||
// sanity check for uplinks
|
|
||||||
/* eslint guard-for-in: 0 */
|
|
||||||
for (let i in self.uplinks) {
|
|
||||||
if (self.uplinks[i].cache == null) {
|
|
||||||
self.uplinks[i].cache = true;
|
|
||||||
}
|
|
||||||
if (Object.prototype.hasOwnProperty.call(self.uplinks, i)) {
|
|
||||||
checkUserOrUplink(i, users);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let user in self.users) {
|
this.packages = normalisePackageAccess(self.packages);
|
||||||
if (Object.prototype.hasOwnProperty.call(self.users, user)) {
|
|
||||||
assert(self.users[user].password, 'CONFIG: no password for user: ' + user);
|
|
||||||
assert(typeof(self.users[user].password) === 'string' &&
|
|
||||||
self.users[user].password.match(/^[a-f0-9]{40}$/)
|
|
||||||
, 'CONFIG: wrong password format for user: ' + user + ', sha1 expected');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let uplink in self.uplinks) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(self.uplinks, uplink)) {
|
|
||||||
assert(self.uplinks[uplink].url, 'CONFIG: no url for uplink: ' + uplink);
|
|
||||||
assert( typeof(self.uplinks[uplink].url) === 'string'
|
|
||||||
, 'CONFIG: wrong url format for uplink: ' + uplink);
|
|
||||||
self.uplinks[uplink].url = self.uplinks[uplink].url.replace(/\/$/, '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add a default rule for all packages to make writing plugins easier
|
|
||||||
if (self.packages['**'] == null) {
|
|
||||||
self.packages['**'] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let pkg in self.packages) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(self.packages, pkg)) {
|
|
||||||
assert(
|
|
||||||
typeof(self.packages[pkg]) === 'object' &&
|
|
||||||
!Array.isArray(self.packages[pkg])
|
|
||||||
, 'CONFIG: bad "'+pkg+'" package description (object expected)');
|
|
||||||
|
|
||||||
self.packages[pkg].access = normalizeUserlist(self.packages[pkg].allow_access, self.packages[pkg].access);
|
|
||||||
delete self.packages[pkg].allow_access;
|
|
||||||
|
|
||||||
self.packages[pkg].publish = normalizeUserlist(self.packages[pkg].allow_publish, self.packages[pkg].publish);
|
|
||||||
delete self.packages[pkg].allow_publish;
|
|
||||||
|
|
||||||
self.packages[pkg].proxy = normalizeUserlist(self.packages[pkg].proxy_access, self.packages[pkg].proxy);
|
|
||||||
delete self.packages[pkg].proxy_access;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loading these from ENV if aren't in config
|
// loading these from ENV if aren't in config
|
||||||
allowedEnvConfig.forEach((function(v) {
|
allowedEnvConfig.forEach((envConf) => {
|
||||||
if (!(v in self)) {
|
if (!(envConf in self)) {
|
||||||
self[v] = process.env[v] || process.env[v.toUpperCase()];
|
self[envConf] = process.env[envConf] || process.env[envConf.toUpperCase()];
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
|
||||||
// unique identifier of self server (or a cluster), used to avoid loops
|
// unique identifier of self server (or a cluster), used to avoid loops
|
||||||
if (!self.server_id) {
|
if (!this.server_id) {
|
||||||
self.server_id = generateRandomHexString(6);
|
this.server_id = generateRandomHexString(6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether an uplink can proxy
|
|
||||||
* @param {String} pkg package anem
|
|
||||||
* @param {*} upLink
|
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
|
||||||
hasProxyTo(pkg, upLink) {
|
|
||||||
return (this.getMatchedPackagesSpec(pkg).proxy || []).reduce(function(prev, curr) {
|
|
||||||
if (upLink === curr) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return prev;
|
|
||||||
}, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for package spec
|
* Check for package spec
|
||||||
* @param {String} pkg package name
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
*/
|
||||||
getMatchedPackagesSpec(pkg) {
|
getMatchedPackagesSpec(pkg: string): MatchedPackage {
|
||||||
for (let i in this.packages) {
|
return getMatchedPackagesSpec(pkg, this.packages);
|
||||||
if (minimatch.makeRe(i).exec(pkg)) {
|
|
||||||
return this.packages[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store or create whether recieve a secret key
|
* Store or create whether recieve a secret key
|
||||||
* @param {String} secret
|
|
||||||
* @return {String}
|
|
||||||
*/
|
*/
|
||||||
checkSecretKey(secret) {
|
checkSecretKey(secret: string): string {
|
||||||
if (_.isString(secret) && secret !== '') {
|
if (_.isString(secret) && _.isEmpty(secret) === false) {
|
||||||
this.secret = secret;
|
this.secret = secret;
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
@ -215,4 +112,4 @@ class Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Config;
|
export default Config;
|
||||||
|
@ -29,10 +29,12 @@ export const DEFAULT_UPLINK = 'npmjs';
|
|||||||
|
|
||||||
export const ROLES = {
|
export const ROLES = {
|
||||||
$ALL: '$all',
|
$ALL: '$all',
|
||||||
|
ALL: 'all',
|
||||||
$AUTH: '$authenticated',
|
$AUTH: '$authenticated',
|
||||||
|
$ANONYMOUS: '$anonymous',
|
||||||
DEPRECATED_ALL: '@all',
|
DEPRECATED_ALL: '@all',
|
||||||
DEPRECATED_AUTH: '@authenticated',
|
DEPRECATED_AUTH: '@authenticated',
|
||||||
ALL: 'all',
|
DEPRECATED_ANONUMOUS: '@anonymous',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HTTP_STATUS = {
|
export const HTTP_STATUS = {
|
||||||
@ -65,21 +67,37 @@ export const API_MESSAGE = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const API_ERROR = {
|
export const API_ERROR = {
|
||||||
|
BAD_USERNAME_PASSWORD: 'bad username/password, access denied {APP}',
|
||||||
NO_PACKAGE: 'no such package available',
|
NO_PACKAGE: 'no such package available',
|
||||||
NOT_ALLOWED: 'not allowed to access package',
|
NOT_ALLOWED: 'not allowed to access package',
|
||||||
INTERNAL_SERVER_ERROR: 'internal server error',
|
INTERNAL_SERVER_ERROR: 'internal server error',
|
||||||
UNKNOWN_ERROR: 'unknown error',
|
UNKNOWN_ERROR: 'unknown error',
|
||||||
NOT_PACKAGE_UPLINK: 'package does not exist on uplink',
|
NOT_PACKAGE_UPLINK: 'package does not exist on uplink',
|
||||||
|
UPLINK_OFFLINE_PUBLISH: 'one of the uplinks is down, refuse to publish',
|
||||||
|
UPLINK_OFFLINE: 'uplink is offline',
|
||||||
CONTENT_MISMATCH: 'content length mismatch',
|
CONTENT_MISMATCH: 'content length mismatch',
|
||||||
NOT_FILE_UPLINK: 'file doesn\'t exist on uplink',
|
NOT_FILE_UPLINK: 'file doesn\'t exist on uplink',
|
||||||
MAX_USERS_REACHED: 'maximum amount of users reached',
|
MAX_USERS_REACHED: 'maximum amount of users reached',
|
||||||
VERSION_NOT_EXIST: 'this version doesn\'t exist',
|
VERSION_NOT_EXIST: 'this version doesn\'t exist',
|
||||||
FILE_NOT_FOUND: 'File not found',
|
FILE_NOT_FOUND: 'File not found',
|
||||||
BAD_STATUS_CODE: 'bad status code',
|
BAD_STATUS_CODE: 'bad status code',
|
||||||
|
PACKAGE_EXIST: 'this package is already present',
|
||||||
|
BAD_AUTH_HEADER: 'bad authorization header',
|
||||||
WEB_DISABLED: 'Web interface is disabled in the config file',
|
WEB_DISABLED: 'Web interface is disabled in the config file',
|
||||||
|
DEPRECATED_BASIC_HEADER: 'basic authentication is deprecated, please use JWT instead',
|
||||||
|
BAD_FORMAT_USER_GROUP: 'user groups is different than an array',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const APP_ERROR = {
|
||||||
|
CONFIG_NOT_VALID: 'CONFIG: it does not look like a valid config file',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_NO_README = 'ERROR: No README data found!';
|
export const DEFAULT_NO_README = 'ERROR: No README data found!';
|
||||||
|
|
||||||
|
|
||||||
export const WEB_TITLE = 'Verdaccio';
|
export const WEB_TITLE = 'Verdaccio';
|
||||||
|
|
||||||
|
export const PACKAGE_ACCESS = {
|
||||||
|
SCOPE: '@*/*',
|
||||||
|
ALL: '**',
|
||||||
|
};
|
||||||
|
@ -7,6 +7,7 @@ import {generateRandomHexString} from '../lib/crypto-utils';
|
|||||||
|
|
||||||
import type {Package, Version} from '@verdaccio/types';
|
import type {Package, Version} from '@verdaccio/types';
|
||||||
import type {IStorage} from '../../types';
|
import type {IStorage} from '../../types';
|
||||||
|
import {API_ERROR, HTTP_STATUS} from './constants';
|
||||||
|
|
||||||
const pkgFileName = 'package.json';
|
const pkgFileName = 'package.json';
|
||||||
const fileExist: string = 'EEXISTS';
|
const fileExist: string = 'EEXISTS';
|
||||||
@ -123,11 +124,11 @@ export function cleanUpLinksRef(keepUpLinkData: boolean, result: Package): Packa
|
|||||||
export function checkPackageLocal(name: string, localStorage: IStorage): Promise<any> {
|
export function checkPackageLocal(name: string, localStorage: IStorage): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
localStorage.getPackageMetadata(name, (err, results) => {
|
localStorage.getPackageMetadata(name, (err, results) => {
|
||||||
if (!_.isNil(err) && err.status !== 404) {
|
if (!_.isNil(err) && err.status !== HTTP_STATUS.NOT_FOUND) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
if (results) {
|
if (results) {
|
||||||
return reject(ErrorCode.getConflict('this package is already present'));
|
return reject(ErrorCode.getConflict(API_ERROR.PACKAGE_EXIST));
|
||||||
}
|
}
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
@ -152,25 +153,25 @@ export function checkPackageRemote(name: string, isAllowPublishOffline: boolean,
|
|||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
syncMetadata(name, null, {}, (err, packageJsonLocal, upLinksErrors) => {
|
syncMetadata(name, null, {}, (err, packageJsonLocal, upLinksErrors) => {
|
||||||
// something weird
|
// something weird
|
||||||
if (err && err.status !== 404) {
|
if (err && err.status !== HTTP_STATUS.NOT_FOUND) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// checking package exist already
|
// checking package exist already
|
||||||
if (_.isNil(packageJsonLocal) === false) {
|
if (_.isNil(packageJsonLocal) === false) {
|
||||||
return reject(ErrorCode.getConflict('this package is already present'));
|
return reject(ErrorCode.getConflict(API_ERROR.PACKAGE_EXIST));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let errorItem = 0; errorItem < upLinksErrors.length; errorItem++) {
|
for (let errorItem = 0; errorItem < upLinksErrors.length; errorItem++) {
|
||||||
// checking error
|
// checking error
|
||||||
// if uplink fails with a status other than 404, we report failure
|
// if uplink fails with a status other than 404, we report failure
|
||||||
if (_.isNil(upLinksErrors[errorItem][0]) === false) {
|
if (_.isNil(upLinksErrors[errorItem][0]) === false) {
|
||||||
if (upLinksErrors[errorItem][0].status !== 404) {
|
if (upLinksErrors[errorItem][0].status !== HTTP_STATUS.NOT_FOUND) {
|
||||||
if (isAllowPublishOffline) {
|
if (isAllowPublishOffline) {
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return reject(ErrorCode.getServiceUnavailable('one of the uplinks is down, refuse to publish'));
|
return reject(ErrorCode.getServiceUnavailable(API_ERROR.UPLINK_OFFLINE_PUBLISH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ Callback,
|
|||||||
Logger,
|
Logger,
|
||||||
} from '@verdaccio/types';
|
} from '@verdaccio/types';
|
||||||
import type {IReadTarball, IUploadTarball} from '@verdaccio/streams';
|
import type {IReadTarball, IUploadTarball} from '@verdaccio/streams';
|
||||||
|
import {hasProxyTo} from './config-utils';
|
||||||
|
|
||||||
const LoggerApi = require('../lib/logger');
|
const LoggerApi = require('../lib/logger');
|
||||||
|
|
||||||
@ -412,9 +413,9 @@ class Storage implements IStorageHandler {
|
|||||||
packageInfo = generatePackageTemplate(name);
|
packageInfo = generatePackageTemplate(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let up in this.uplinks) {
|
for (let uplink in this.uplinks) {
|
||||||
if (this.config.hasProxyTo(name, up)) {
|
if (hasProxyTo(name, uplink, this.config.packages)) {
|
||||||
upLinks.push(this.uplinks[up]);
|
upLinks.push(this.uplinks[uplink]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,10 +105,10 @@ class ProxyStorage implements IProxy {
|
|||||||
|
|
||||||
process.nextTick(function() {
|
process.nextTick(function() {
|
||||||
if (cb) {
|
if (cb) {
|
||||||
cb(ErrorCode.getInternalError('uplink is offline'));
|
cb(ErrorCode.getInternalError(API_ERROR.UPLINK_OFFLINE));
|
||||||
}
|
}
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
streamRead.emit('error', ErrorCode.getInternalError('uplink is offline'));
|
streamRead.emit('error', ErrorCode.getInternalError(API_ERROR.UPLINK_OFFLINE));
|
||||||
});
|
});
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
streamRead._read = function() {};
|
streamRead._read = function() {};
|
||||||
|
@ -17,9 +17,22 @@ import type {$Request} from 'express';
|
|||||||
import type {StringValue} from '../../types';
|
import type {StringValue} from '../../types';
|
||||||
|
|
||||||
const Logger = require('./logger');
|
const Logger = require('./logger');
|
||||||
|
const pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars
|
||||||
|
const pkgVersion = module.exports.version;
|
||||||
|
const pkgName = module.exports.name;
|
||||||
|
|
||||||
export const DIST_TAGS = 'dist-tags';
|
export const DIST_TAGS = 'dist-tags';
|
||||||
|
|
||||||
|
export function getUserAgent(): string {
|
||||||
|
assert(_.isString(pkgName));
|
||||||
|
assert(_.isString(pkgVersion));
|
||||||
|
return `${pkgName}/${pkgVersion}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildBase64Buffer(payload: string): Buffer {
|
||||||
|
return new Buffer(payload, 'base64');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate a package.
|
* Validate a package.
|
||||||
* @return {Boolean} whether the package is valid or not
|
* @return {Boolean} whether the package is valid or not
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
storage: ./.verdaccio_test_env/test-storage
|
storage: ./.verdaccio_test_env/test-storage
|
||||||
|
|
||||||
users:
|
|
||||||
test:
|
|
||||||
password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
|
|
||||||
|
|
||||||
uplinks:
|
uplinks:
|
||||||
npmjs:
|
npmjs:
|
||||||
url: https://registry.npmjs.org/
|
url: https://registry.npmjs.org/
|
||||||
|
@ -4,17 +4,21 @@ import path from 'path';
|
|||||||
import rimraf from 'rimraf';
|
import rimraf from 'rimraf';
|
||||||
|
|
||||||
import {HEADERS} from '../../../src/lib/constants';
|
import {HEADERS} from '../../../src/lib/constants';
|
||||||
import configDefault from '../partials/config/access';
|
import configDefault from '../partials/config/config_access';
|
||||||
import Config from '../../../src/lib/config';
|
import Config from '../../../src/lib/config';
|
||||||
import endPointAPI from '../../../src/api/index';
|
import endPointAPI from '../../../src/api/index';
|
||||||
|
import {mockServer} from './mock';
|
||||||
|
import {DOMAIN_SERVERS} from '../../functional/config.functional';
|
||||||
|
|
||||||
require('../../../src/lib/logger').setup([]);
|
require('../../../src/lib/logger').setup([]);
|
||||||
|
|
||||||
describe('api with no limited access configuration', () => {
|
describe('api with no limited access configuration', () => {
|
||||||
let config;
|
let config;
|
||||||
let app;
|
let app;
|
||||||
|
let mockRegistry;
|
||||||
|
|
||||||
beforeAll(function(done) {
|
beforeAll(function(done) {
|
||||||
|
const mockServerPort = 55530;
|
||||||
const store = path.join(__dirname, './partials/store/access-storage');
|
const store = path.join(__dirname, './partials/store/access-storage');
|
||||||
rimraf(store, async () => {
|
rimraf(store, async () => {
|
||||||
const configForTest = _.clone(configDefault);
|
const configForTest = _.clone(configDefault);
|
||||||
@ -24,8 +28,14 @@ describe('api with no limited access configuration', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
configForTest.self_path = store;
|
configForTest.self_path = store;
|
||||||
|
configForTest.uplinks = {
|
||||||
|
npmjs: {
|
||||||
|
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`
|
||||||
|
}
|
||||||
|
};
|
||||||
config = new Config(configForTest);
|
config = new Config(configForTest);
|
||||||
app = await endPointAPI(config);
|
app = await endPointAPI(config);
|
||||||
|
mockRegistry = await mockServer(mockServerPort).init();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -34,9 +44,11 @@ describe('api with no limited access configuration', () => {
|
|||||||
const store = path.join(__dirname, './partials/store/access-storage');
|
const store = path.join(__dirname, './partials/store/access-storage');
|
||||||
rimraf(store, (err) => {
|
rimraf(store, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
mockRegistry[0].stop();
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mockRegistry[0].stop();
|
||||||
return done();
|
return done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -29,7 +29,7 @@ describe('endpoint unit test', () => {
|
|||||||
const configForTest = _.clone(configDefault);
|
const configForTest = _.clone(configDefault);
|
||||||
configForTest.auth = {
|
configForTest.auth = {
|
||||||
htpasswd: {
|
htpasswd: {
|
||||||
file: './test-storage/htpasswd-test'
|
file: './test-storage/.htpasswd'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
configForTest.uplinks = {
|
configForTest.uplinks = {
|
||||||
|
226
test/unit/api/config-utils.spec.js
Normal file
226
test/unit/api/config-utils.spec.js
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
|
import {spliceURL} from '../../../src/utils/string';
|
||||||
|
import {parseConfigFile} from '../../../src/lib/utils';
|
||||||
|
import {
|
||||||
|
getMatchedPackagesSpec,
|
||||||
|
hasProxyTo,
|
||||||
|
normalisePackageAccess, sanityCheckUplinksProps,
|
||||||
|
uplinkSanityCheck
|
||||||
|
} from '../../../src/lib/config-utils';
|
||||||
|
import {PACKAGE_ACCESS, ROLES} from '../../../src/lib/constants';
|
||||||
|
|
||||||
|
describe('Config Utilities', () => {
|
||||||
|
|
||||||
|
const parsePartial = (name) => {
|
||||||
|
return path.join(__dirname, `../partials/config/yaml/${name}.yaml`);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('uplinkSanityCheck', () => {
|
||||||
|
test('should test basic conversion', ()=> {
|
||||||
|
const uplinks = uplinkSanityCheck(parseConfigFile(parsePartial('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(parsePartial('uplink-wrong'));
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
uplinkSanityCheck(uplinks)
|
||||||
|
}).toThrow('CONFIG: reserved uplink name: anonymous');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sanityCheckUplinksProps', () => {
|
||||||
|
test('should fails if url prop is missing', ()=> {
|
||||||
|
const {uplinks} = parseConfigFile(parsePartial('uplink-wrong'));
|
||||||
|
expect(() => {
|
||||||
|
sanityCheckUplinksProps(uplinks)
|
||||||
|
}).toThrow('CONFIG: no url for uplink: none-url');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should bypass an empty uplink list', ()=> {
|
||||||
|
expect(sanityCheckUplinksProps([])).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('normalisePackageAccess', () => {
|
||||||
|
test('should test basic conversion', ()=> {
|
||||||
|
const {packages} = parseConfigFile(parsePartial('pkgs-basic'));
|
||||||
|
const access = normalisePackageAccess(packages);
|
||||||
|
|
||||||
|
expect(access).toBeDefined();
|
||||||
|
const scoped = access[`${PACKAGE_ACCESS.SCOPE}`];
|
||||||
|
const all = access[`${PACKAGE_ACCESS.ALL}`];
|
||||||
|
|
||||||
|
expect(scoped).toBeDefined();
|
||||||
|
expect(all).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should test multi group', ()=> {
|
||||||
|
const {packages} = parseConfigFile(parsePartial('pkgs-multi-group'));
|
||||||
|
const access = normalisePackageAccess(packages);
|
||||||
|
|
||||||
|
expect(access).toBeDefined();
|
||||||
|
const scoped = access[`${PACKAGE_ACCESS.SCOPE}`];
|
||||||
|
|
||||||
|
const all = access[`${PACKAGE_ACCESS.ALL}`];
|
||||||
|
|
||||||
|
expect(scoped).toBeDefined();
|
||||||
|
expect(scoped.access).toContain('$all');
|
||||||
|
expect(scoped.publish).toHaveLength(2);
|
||||||
|
expect(scoped.publish).toContain('admin');
|
||||||
|
expect(scoped.publish).toContain('superadmin');
|
||||||
|
|
||||||
|
expect(all).toBeDefined();
|
||||||
|
expect(all.access).toHaveLength(3);
|
||||||
|
expect(all.access).toContain('$all');
|
||||||
|
expect(all.publish).toHaveLength(1);
|
||||||
|
expect(all.publish).toContain('admin');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('should deprecated packages props', ()=> {
|
||||||
|
const {packages} = parseConfigFile(parsePartial('deprecated-pkgs-basic'));
|
||||||
|
const access = normalisePackageAccess(packages);
|
||||||
|
|
||||||
|
expect(access).toBeDefined();
|
||||||
|
const scoped = access[`${PACKAGE_ACCESS.SCOPE}`];
|
||||||
|
const all = access[`${PACKAGE_ACCESS.ALL}`];
|
||||||
|
const react = access['react-*'];
|
||||||
|
|
||||||
|
expect(react).toBeDefined();
|
||||||
|
expect(react.access).toBeDefined();
|
||||||
|
// $FlowFixMe
|
||||||
|
expect(react.access[0]).toBe(ROLES.$ALL);
|
||||||
|
expect(react.publish).toBeDefined();
|
||||||
|
// $FlowFixMe);
|
||||||
|
expect(react.publish[0]).toBe('admin');
|
||||||
|
expect(react.proxy).toBeDefined();
|
||||||
|
// $FlowFixMe
|
||||||
|
expect(react.proxy[0]).toBe('uplink2');
|
||||||
|
expect(react.storage).toBeDefined();
|
||||||
|
|
||||||
|
expect(react.storage).toBe('react-storage');
|
||||||
|
expect(scoped).toBeDefined();
|
||||||
|
expect(scoped.storage).not.toBeDefined();
|
||||||
|
expect(all).toBeDefined();
|
||||||
|
expect(all.access).toBeDefined();
|
||||||
|
expect(all.storage).not.toBeDefined();
|
||||||
|
expect(all.publish).toBeDefined();
|
||||||
|
expect(all.proxy).toBeDefined();
|
||||||
|
expect(all.allow_access).toBeUndefined();
|
||||||
|
expect(all.allow_publish).toBeUndefined();
|
||||||
|
expect(all.proxy_access).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should check not default packages access', ()=> {
|
||||||
|
const {packages} = parseConfigFile(parsePartial('pkgs-empty'));
|
||||||
|
const access = normalisePackageAccess(packages);
|
||||||
|
expect(access).toBeDefined();
|
||||||
|
|
||||||
|
const scoped = access[`${PACKAGE_ACCESS.SCOPE}`];
|
||||||
|
expect(scoped).toBeUndefined();
|
||||||
|
|
||||||
|
// ** should be added by default
|
||||||
|
const all = access[`${PACKAGE_ACCESS.ALL}`];
|
||||||
|
expect(all).toBeDefined();
|
||||||
|
|
||||||
|
expect(all.access).toBeUndefined();
|
||||||
|
expect(all.publish).toBeUndefined();
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getMatchedPackagesSpec', () => {
|
||||||
|
test('should test basic config', () => {
|
||||||
|
const {packages} = parseConfigFile(parsePartial('pkgs-custom'));
|
||||||
|
// $FlowFixMe
|
||||||
|
expect(getMatchedPackagesSpec('react', packages).proxy).toMatch('facebook');
|
||||||
|
// $FlowFixMe
|
||||||
|
expect(getMatchedPackagesSpec('angular', packages).proxy).toMatch('google');
|
||||||
|
// $FlowFixMe
|
||||||
|
expect(getMatchedPackagesSpec('vue', packages).proxy).toMatch('npmjs');
|
||||||
|
// $FlowFixMe
|
||||||
|
expect(getMatchedPackagesSpec('@scope/vue', packages).proxy).toMatch('npmjs');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should test no ** wildcard on config', () => {
|
||||||
|
const {packages} = parseConfigFile(parsePartial('pkgs-nosuper-wildcard-custom'));
|
||||||
|
// $FlowFixMe
|
||||||
|
expect(getMatchedPackagesSpec('react', packages).proxy).toMatch('facebook');
|
||||||
|
// $FlowFixMe
|
||||||
|
expect(getMatchedPackagesSpec('angular', packages).proxy).toMatch('google');
|
||||||
|
// $FlowFixMe
|
||||||
|
expect(getMatchedPackagesSpec('@fake/angular', packages).proxy).toMatch('npmjs');
|
||||||
|
expect(getMatchedPackagesSpec('vue', packages)).toBeUndefined();
|
||||||
|
expect(getMatchedPackagesSpec('@scope/vue', packages)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasProxyTo', () => {
|
||||||
|
test('should test basic config', () => {
|
||||||
|
const packages = normalisePackageAccess(parseConfigFile(parsePartial('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(parsePartial('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(parsePartial('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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -6,6 +6,7 @@ import {parseConfigFile} from '../../../src/lib/utils';
|
|||||||
import {DEFAULT_REGISTRY, DEFAULT_UPLINK, ROLES, WEB_TITLE} from '../../../src/lib/constants';
|
import {DEFAULT_REGISTRY, DEFAULT_UPLINK, ROLES, WEB_TITLE} from '../../../src/lib/constants';
|
||||||
|
|
||||||
const resolveConf = (conf) => path.join(__dirname, `../../../conf/${conf}.yaml`);
|
const resolveConf = (conf) => path.join(__dirname, `../../../conf/${conf}.yaml`);
|
||||||
|
require('../../../src/lib/logger').setup([]);
|
||||||
|
|
||||||
const checkDefaultUplink = (config) => {
|
const checkDefaultUplink = (config) => {
|
||||||
expect(_.isObject(config.uplinks[DEFAULT_UPLINK])).toBeTruthy();
|
expect(_.isObject(config.uplinks[DEFAULT_UPLINK])).toBeTruthy();
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
|
||||||
|
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import Search from '../../../src/lib/search';
|
import Search from '../../../src/lib/search';
|
||||||
|
import Config from '../../../src/lib/config';
|
||||||
import Storage from '../../../src/lib/storage';
|
import Storage from '../../../src/lib/storage';
|
||||||
|
|
||||||
let config_hash = require('../partials/config/index');
|
let config_hash = require('../partials/config/index');
|
||||||
let Config = require('../../../src/lib/config');
|
|
||||||
|
|
||||||
require('../../../src/lib/logger').setup([]);
|
require('../../../src/lib/logger').setup([]);
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {setup} from '../../../src/lib/logger';
|
|||||||
|
|
||||||
import type {Config, UpLinkConf} from '@verdaccio/types';
|
import type {Config, UpLinkConf} from '@verdaccio/types';
|
||||||
import type {IProxy} from '../../../types/index';
|
import type {IProxy} from '../../../types/index';
|
||||||
import {API_ERROR} from "../../../src/lib/constants";
|
import {API_ERROR, HTTP_STATUS} from "../../../src/lib/constants";
|
||||||
import {mockServer} from './mock';
|
import {mockServer} from './mock';
|
||||||
import {DOMAIN_SERVERS} from '../../functional/config.functional';
|
import {DOMAIN_SERVERS} from '../../functional/config.functional';
|
||||||
|
|
||||||
@ -102,8 +102,8 @@ describe('UpStorge', () => {
|
|||||||
|
|
||||||
stream.on('error', function(err) {
|
stream.on('error', function(err) {
|
||||||
expect(err).not.toBeNull();
|
expect(err).not.toBeNull();
|
||||||
expect(err.statusCode).toBe(404);
|
expect(err.statusCode).toBe(HTTP_STATUS.NOT_FOUND);
|
||||||
expect(err.message).toMatch(/file doesn't exist on uplink/);
|
expect(err.message).toMatch(API_ERROR.NOT_FILE_UPLINK);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -141,9 +141,9 @@ describe('UpStorge', () => {
|
|||||||
const streamThirdTry = proxy.fetchTarball(tarball);
|
const streamThirdTry = proxy.fetchTarball(tarball);
|
||||||
streamThirdTry.on('error', function(err) {
|
streamThirdTry.on('error', function(err) {
|
||||||
expect(err).not.toBeNull();
|
expect(err).not.toBeNull();
|
||||||
expect(err.statusCode).toBe(500);
|
expect(err.statusCode).toBe(HTTP_STATUS.INTERNAL_ERROR);
|
||||||
expect(proxy.failed_requests).toBe(2);
|
expect(proxy.failed_requests).toBe(2);
|
||||||
expect(err.message).toMatch(/uplink is offline/);
|
expect(err.message).toMatch(API_ERROR.UPLINK_OFFLINE);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {DEFAULT_REGISTRY} from '../../../../src/lib/constants';
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
storage: path.join(__dirname, '../store/access-storage'),
|
storage: path.join(__dirname, '../store/access-storage'),
|
||||||
uplinks: {
|
uplinks: {
|
||||||
'npmjs': {
|
'npmjs': {
|
||||||
'url': DEFAULT_REGISTRY
|
'url': 'http://never_use:0000/'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
packages: {
|
packages: {
|
14
test/unit/partials/config/yaml/deprecated-pkgs-basic.yaml
Normal file
14
test/unit/partials/config/yaml/deprecated-pkgs-basic.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
packages:
|
||||||
|
'@*/*':
|
||||||
|
access: $all
|
||||||
|
publish: $authenticated
|
||||||
|
proxy: npmjs
|
||||||
|
'react-*':
|
||||||
|
allow_access: $all
|
||||||
|
publish: admin
|
||||||
|
proxy_access: uplink2
|
||||||
|
storage: 'react-storage'
|
||||||
|
'**':
|
||||||
|
allow_access: $all
|
||||||
|
allow_publish: $authenticated
|
||||||
|
proxy_access: npmjs
|
9
test/unit/partials/config/yaml/pkgs-basic.yaml
Normal file
9
test/unit/partials/config/yaml/pkgs-basic.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
packages:
|
||||||
|
'@*/*':
|
||||||
|
access: $all
|
||||||
|
publish: $authenticated
|
||||||
|
proxy: npmjs
|
||||||
|
'**':
|
||||||
|
access: $all
|
||||||
|
publish: $authenticated
|
||||||
|
proxy: npmjs
|
17
test/unit/partials/config/yaml/pkgs-custom.yaml
Normal file
17
test/unit/partials/config/yaml/pkgs-custom.yaml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
packages:
|
||||||
|
'react':
|
||||||
|
access: admin
|
||||||
|
publish: admin
|
||||||
|
proxy: facebook
|
||||||
|
'angular':
|
||||||
|
access: admin
|
||||||
|
publish: admin
|
||||||
|
proxy: google
|
||||||
|
'@*/*':
|
||||||
|
access: $all
|
||||||
|
publish: $authenticated
|
||||||
|
proxy: npmjs
|
||||||
|
'**':
|
||||||
|
access: $all
|
||||||
|
publish: $authenticated
|
||||||
|
proxy: npmjs
|
4
test/unit/partials/config/yaml/pkgs-empty.yaml
Normal file
4
test/unit/partials/config/yaml/pkgs-empty.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
packages:
|
||||||
|
'private':
|
||||||
|
access: admin
|
||||||
|
publish: admin
|
9
test/unit/partials/config/yaml/pkgs-multi-group.yaml
Normal file
9
test/unit/partials/config/yaml/pkgs-multi-group.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
packages:
|
||||||
|
'@*/*':
|
||||||
|
access: $all
|
||||||
|
publish: admin superadmin
|
||||||
|
proxy: npmjs
|
||||||
|
'**':
|
||||||
|
access: $all user1 user2
|
||||||
|
publish: admin
|
||||||
|
proxy: npmjs
|
@ -0,0 +1,13 @@
|
|||||||
|
packages:
|
||||||
|
'react':
|
||||||
|
access: admin
|
||||||
|
publish: admin
|
||||||
|
proxy: facebook
|
||||||
|
'angular':
|
||||||
|
access: admin
|
||||||
|
publish: admin
|
||||||
|
proxy: google
|
||||||
|
'@fake/*':
|
||||||
|
access: $all
|
||||||
|
publish: $authenticated
|
||||||
|
proxy: npmjs
|
7
test/unit/partials/config/yaml/uplink-basic.yaml
Normal file
7
test/unit/partials/config/yaml/uplink-basic.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
uplinks:
|
||||||
|
server1:
|
||||||
|
url: http://localhost:55551/
|
||||||
|
maxage: 0
|
||||||
|
server2:
|
||||||
|
url: http://localhost:55551/
|
||||||
|
maxage: 0
|
9
test/unit/partials/config/yaml/uplink-wrong.yaml
Normal file
9
test/unit/partials/config/yaml/uplink-wrong.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
uplinks:
|
||||||
|
facebook:
|
||||||
|
url: http://localhost:55551/
|
||||||
|
maxage: 0
|
||||||
|
anonymous:
|
||||||
|
url: http://localhost:55551/
|
||||||
|
maxage: 0
|
||||||
|
none-url:
|
||||||
|
maxage: 0
|
@ -8,6 +8,7 @@ import type {
|
|||||||
MergeTags,
|
MergeTags,
|
||||||
Config,
|
Config,
|
||||||
Logger,
|
Logger,
|
||||||
|
PackageAccess,
|
||||||
Package} from '@verdaccio/types';
|
Package} from '@verdaccio/types';
|
||||||
import type {
|
import type {
|
||||||
IUploadTarball,
|
IUploadTarball,
|
||||||
@ -95,6 +96,13 @@ export interface IStorageHandler {
|
|||||||
_updateVersionsHiddenUpLink(versions: Versions, upLink: IProxy): void;
|
_updateVersionsHiddenUpLink(versions: Versions, upLink: IProxy): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type StartUpConfig = {
|
||||||
|
storage: string;
|
||||||
|
self_path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MatchedPackage = PackageAccess | void;
|
||||||
|
|
||||||
export interface IStorage {
|
export interface IStorage {
|
||||||
config: Config;
|
config: Config;
|
||||||
localData: ILocalData;
|
localData: ILocalData;
|
||||||
|
BIN
yarn.lock
BIN
yarn.lock
Binary file not shown.
Loading…
Reference in New Issue
Block a user