diff --git a/.changeset/config.json b/.changeset/config.json index 8dac55d6b..90a7d53bf 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -14,6 +14,5 @@ ], "access": "public", "baseBranch": "master", - "updateInternalDependencies": "patch", - "ignore": [] + "updateInternalDependencies": "patch" } diff --git a/.changeset/early-jokes-nail.md b/.changeset/early-jokes-nail.md new file mode 100644 index 000000000..9fba14f66 --- /dev/null +++ b/.changeset/early-jokes-nail.md @@ -0,0 +1,76 @@ +--- +'@verdaccio/api': major +'@verdaccio/auth': major +'@verdaccio/config': major +'@verdaccio/types': major +'@verdaccio/loaders': major +'@verdaccio/node-api': major +'verdaccio-audit': major +'verdaccio-auth-memory': major +'verdaccio-htpasswd': major +'@verdaccio/local-storage': major +'verdaccio-memory': major +'@verdaccio/server': major +'@verdaccio/server-fastify': major +'@verdaccio/store': major +'@verdaccio/test-helper': major +'customprefix-auth': major +'verdaccio': major +'@verdaccio/web': major +--- + +feat(plugins): improve plugin loader + +### Changes + +- Add scope plugin support to 6.x https://github.com/verdaccio/verdaccio/pull/3227 +- Avoid config collisions https://github.com/verdaccio/verdaccio/issues/928 +- https://github.com/verdaccio/verdaccio/issues/1394 +- `config.plugins` plugin path validations +- Updated algorithm for plugin loader. +- improved documentation (included dev) + +## Features + +- Add scope plugin support to 6.x https://github.com/verdaccio/verdaccio/pull/3227 +- Custom prefix: + +``` +// config.yaml +server: + pluginPrefix: mycompany +middleware: + audit: + foo: 1 +``` + +This configuration will look up for `mycompany-audit` instead `Verdaccio-audit`. + +## Breaking Changes + +### sinopia plugins + +- `sinopia` fallback support is removed, but can be restored using `pluginPrefix` + +### plugin filter + +- method rename `filter_metadata`->`filterMetadata` + +### Plugin constructor does not merge configs anymore https://github.com/verdaccio/verdaccio/issues/928 + +The plugin receives as first argument `config`, which represents the config of the plugin. Example: + +``` +// config.yaml +auth: + plugin: + foo: 1 + bar: 2 + +export class Plugin { + public constructor(config: T, options: PluginOptions) { + console.log(config); + // {foo:1, bar: 2} + } +} +``` diff --git a/docs/development.md b/docs/development.md deleted file mode 100644 index 55d077ced..000000000 --- a/docs/development.md +++ /dev/null @@ -1,116 +0,0 @@ -## Development notes - -The `5.x` still under development, key points: - -Ensure you have `nvm` installed or the latest Node.js (check `.nvmrc` -for mode details). - -```bash -nvm install -``` - -Verdaccio uses **pnpm** as monorepo management. To install - -```bash -npm i -g pnpm@latest-6 -``` - -Install all needed packages - -```bash -pnpm install -``` - -For building the application: - -```bash -pnpm build -``` - -Running the test - -``` -pnpm test -``` - -### Running the application (with UI hot reloading) - -```bash -pnpm start -``` - -with hot reloading (server and UI), `nodemon` will restart the server and `babel` runs -in watch mode. - -```bash -pnpm start:watch -``` - -Running with `ts-node` - -``` -pnpm start:ts -``` - -### Running the Website - -We use _Gatsbyjs_ as development stack for website, -please [for more information check their official guidelines.](https://www.gatsbyjs.com/docs/quick-start/) - -``` -pnpm website:develop -``` - -### Running E2E - -For running the CLI test - -``` -pnpm test:e2e:cli -``` - -For running the UI test - -``` -pnpm test:e2e:ui -``` - -### Linting - -Linting the code. - -```bash -pnpm lint -``` - -For website runs - -```bash -pnpm website:lint -``` - -Formatting the code with prettier - -```bash -pnpm prettier -``` - -### Debugging - -Run the server in debug mode (it does not include UI hot reload) -with `--inspect` support. - -``` -pnpm debug -pnpm debug:break -``` - -> requires `pnpm build` previously - -#### debug internal output - -Each verdaccio module uses `debug`, use the namespaces in combination with filters to get a verbose output about each action, for example: - -``` -DEBUG=verdaccio:* pnpm start -``` diff --git a/jest/config.js b/jest/config.js index 6fcf39388..07c32bdaf 100644 --- a/jest/config.js +++ b/jest/config.js @@ -5,6 +5,7 @@ module.exports = { }, verbose: false, collectCoverage: true, + coverageReporters: ['text', 'html'], collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!**/partials/**', '!**/fixture/**'], coveragePathIgnorePatterns: ['node_modules', 'fixtures'], coverageThreshold: { diff --git a/packages/api/test/integration/_helper.ts b/packages/api/test/integration/_helper.ts index b8b0288b9..53894e8f6 100644 --- a/packages/api/test/integration/_helper.ts +++ b/packages/api/test/integration/_helper.ts @@ -27,7 +27,8 @@ export const getConf = (conf) => { }; export async function initializeServer(configName): Promise { - return initializeServerHelper(getConf(configName), [apiMiddleware], Storage); + const config = getConf(configName); + return initializeServerHelper(config, [apiMiddleware], Storage); } export function createUser(app, name: string, password: string): supertest.Test { diff --git a/packages/api/test/integration/publish.spec.ts b/packages/api/test/integration/publish.spec.ts index 71c7bfd6b..daa91d2ed 100644 --- a/packages/api/test/integration/publish.spec.ts +++ b/packages/api/test/integration/publish.spec.ts @@ -22,6 +22,9 @@ jest.mock('@verdaccio/auth', () => ({ apiJWTmiddleware() { return mockApiJWTmiddleware(); } + init() { + return Promise.resolve(); + } allow_access(_d, f_, cb) { cb(null, true); } diff --git a/packages/api/test/integration/user.jwt.spec.ts b/packages/api/test/integration/user.jwt.spec.ts index 899e884ef..bb79c5e4e 100644 --- a/packages/api/test/integration/user.jwt.spec.ts +++ b/packages/api/test/integration/user.jwt.spec.ts @@ -7,6 +7,8 @@ import { createUser, getPackage, initializeServer } from './_helper'; const FORBIDDEN_VUE = 'authorization required to access package vue'; +jest.setTimeout(20000); + describe('token', () => { describe('basics', () => { const FAKE_TOKEN: string = buildToken(TOKEN_BEARER, 'fake'); diff --git a/packages/api/test/integration/user.spec.ts b/packages/api/test/integration/user.spec.ts index 190732983..edd3fc21d 100644 --- a/packages/api/test/integration/user.spec.ts +++ b/packages/api/test/integration/user.spec.ts @@ -35,6 +35,9 @@ jest.mock('@verdaccio/auth', () => ({ apiJWTmiddleware() { return mockApiJWTmiddleware(); } + init() { + return Promise.resolve(); + } allow_access(_d, f_, cb) { cb(null, true); } diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts index 8e5315c22..b543f9317 100644 --- a/packages/auth/src/auth.ts +++ b/packages/auth/src/auth.ts @@ -1,7 +1,7 @@ import buildDebug from 'debug'; import { NextFunction, Request, Response } from 'express'; import _ from 'lodash'; -import { HTPasswd, HTPasswdConfig } from 'verdaccio-htpasswd'; +import { HTPasswd } from 'verdaccio-htpasswd'; import { createAnonymousRemoteUser, createRemoteUser } from '@verdaccio/config'; import { @@ -12,7 +12,7 @@ import { VerdaccioError, errorUtils, } from '@verdaccio/core'; -import { loadPlugin } from '@verdaccio/loaders'; +import { asyncLoadPlugin } from '@verdaccio/loaders'; import { AllowAccess, AuthPluginPackage, @@ -82,6 +82,7 @@ export interface IAuth extends IBasicAuth, IAuthMiddleware, TokenEncrypt plugins: any[]; allow_unpublish(pkg: AuthPluginPackage, user: RemoteUser, callback: Callback): void; invalidateToken(token: string): Promise; + init(): Promise; } class Auth implements IAuth { @@ -94,24 +95,32 @@ class Auth implements IAuth { this.config = config; this.logger = LoggerApi.logger.child({ sub: 'auth' }); this.secret = config.secret; + this.plugins = []; if (!this.secret) { throw new TypeError('secret it is required value on initialize the auth class'); } + } + + public async init() { + let plugins = await this.loadPlugin(); + debug('auth plugins found %s', plugins.length); + if (!plugins || plugins.length === 0) { + plugins = this.loadDefaultPlugin(); + } + this.plugins = plugins; - this.plugins = - _.isNil(config?.auth) === false ? this._loadPlugin(config) : this.loadDefaultPlugin(config); this._applyDefaultPlugins(); } - private loadDefaultPlugin(config: Config) { - const plugingConf: HTPasswdConfig = { ...config, file: './htpasswd' }; - const pluginOptions: PluginOptions<{}> = { - config, + private loadDefaultPlugin() { + debug('load default auth plugin'); + const pluginOptions: PluginOptions = { + config: this.config, logger: this.logger, }; let authPlugin; try { - authPlugin = new HTPasswd(plugingConf, pluginOptions as any as PluginOptions); + authPlugin = new HTPasswd({ file: './htpasswd' }, pluginOptions as any as PluginOptions); } catch (error: any) { debug('error on loading auth htpasswd plugin stack: %o', error); return []; @@ -120,22 +129,20 @@ class Auth implements IAuth { return [authPlugin]; } - private _loadPlugin(config: Config): IPluginAuth[] { - const pluginOptions = { - config, - logger: this.logger, - }; - - return loadPlugin>( - config, - config.auth, - pluginOptions, + private async loadPlugin(): Promise[]> { + return asyncLoadPlugin>( + this.config.auth, + { + config: this.config, + logger: this.logger, + }, (plugin: IPluginAuth): boolean => { const { authenticate, allow_access, allow_publish } = plugin; // @ts-ignore return authenticate || allow_access || allow_publish; - } + }, + this.config?.server?.pluginPrefix ); } diff --git a/packages/auth/test/auth-utils.spec.ts b/packages/auth/test/auth-utils.spec.ts index b28efd96b..725c29752 100644 --- a/packages/auth/test/auth-utils.spec.ts +++ b/packages/auth/test/auth-utils.spec.ts @@ -71,6 +71,7 @@ describe('Auth utilities', () => { ): Promise { const config: Config = getConfig(configFileName, secret); const auth: IAuth = new Auth(config); + await auth.init(); // @ts-ignore const spy = jest.spyOn(auth, methodToSpy); // @ts-ignore @@ -409,6 +410,7 @@ describe('Auth utilities', () => { const secret = 'b2df428b9929d3ace7c598bbf4e496b2'; const config: Config = getConfig('security-legacy', secret); const auth: IAuth = new Auth(config); + await auth.init(); const token = auth.aesEncrypt(null); const security: Security = config.security; const credentials = getMiddlewareCredentials( diff --git a/packages/auth/test/auth.spec.ts b/packages/auth/test/auth.spec.ts index 4f3b17a01..e57cd682d 100644 --- a/packages/auth/test/auth.spec.ts +++ b/packages/auth/test/auth.spec.ts @@ -1,7 +1,7 @@ -import _ from 'lodash'; +import path from 'path'; import { IAuth } from '@verdaccio/auth'; -import { Config as AppConfig, ROLES } from '@verdaccio/config'; +import { Config as AppConfig, ROLES, getDefaultConfig } from '@verdaccio/config'; import { errorUtils } from '@verdaccio/core'; import { setup } from '@verdaccio/logger'; import { Config } from '@verdaccio/types'; @@ -12,22 +12,31 @@ import { authPluginFailureConf, authPluginPassThrougConf, authProfileConf } from setup([]); describe('AuthTest', () => { - test('should be defined', () => { - const config: Config = new AppConfig(_.cloneDeep(authProfileConf)); + test('should init correctly', async () => { + const config: Config = new AppConfig({ ...authProfileConf }); config.checkSecretKey('12345'); const auth: IAuth = new Auth(config); + await auth.init(); + expect(auth).toBeDefined(); + }); + test('should load default auth plugin', async () => { + const config: Config = new AppConfig({ ...authProfileConf, auth: undefined }); + config.checkSecretKey('12345'); + + const auth: IAuth = new Auth(config); + await auth.init(); expect(auth).toBeDefined(); }); describe('test authenticate method', () => { describe('test authenticate states', () => { - test('should be a success login', () => { - const config: Config = new AppConfig(_.cloneDeep(authProfileConf)); + test('should be a success login', async () => { + const config: Config = new AppConfig({ ...authProfileConf }); config.checkSecretKey('12345'); const auth: IAuth = new Auth(config); - + await auth.init(); expect(auth).toBeDefined(); const callback = jest.fn(); @@ -50,11 +59,11 @@ describe('AuthTest', () => { }); }); - test('should be a fail on login', () => { + test('should be a fail on login', async () => { const config: Config = new AppConfig(authPluginFailureConf); config.checkSecretKey('12345'); const auth: IAuth = new Auth(config); - + await auth.init(); expect(auth).toBeDefined(); const callback = jest.fn(); @@ -69,11 +78,11 @@ describe('AuthTest', () => { // that might make break the request // the @ts-ignore below are intended describe('test authenticate out of control inputs from plugins', () => { - test('should skip falsy values', () => { - const config: Config = new AppConfig(_.cloneDeep(authPluginPassThrougConf)); + test('should skip falsy values', async () => { + const config: Config = new AppConfig({ ...authPluginPassThrougConf }); config.checkSecretKey('12345'); const auth: IAuth = new Auth(config); - + await auth.init(); expect(auth).toBeDefined(); const callback = jest.fn(); @@ -89,11 +98,11 @@ describe('AuthTest', () => { } }); - test('should error truthy non-array', () => { - const config: Config = new AppConfig(_.cloneDeep(authPluginPassThrougConf)); + test('should error truthy non-array', async () => { + const config: Config = new AppConfig({ ...authPluginPassThrougConf }); config.checkSecretKey('12345'); const auth: IAuth = new Auth(config); - + await auth.init(); expect(auth).toBeDefined(); const callback = jest.fn(); @@ -107,11 +116,11 @@ describe('AuthTest', () => { } }); - test('should skip empty array', () => { - const config: Config = new AppConfig(_.cloneDeep(authPluginPassThrougConf)); + test('should skip empty array', async () => { + const config: Config = new AppConfig({ ...authPluginPassThrougConf }); config.checkSecretKey('12345'); const auth: IAuth = new Auth(config); - + await auth.init(); expect(auth).toBeDefined(); const callback = jest.fn(); @@ -124,11 +133,11 @@ describe('AuthTest', () => { expect(callback.mock.calls[0][1]).toBeUndefined(); }); - test('should accept valid array', () => { - const config: Config = new AppConfig(_.cloneDeep(authPluginPassThrougConf)); + test('should accept valid array', async () => { + const config: Config = new AppConfig({ ...authPluginPassThrougConf }); config.checkSecretKey('12345'); const auth: IAuth = new Auth(config); - + await auth.init(); expect(auth).toBeDefined(); const callback = jest.fn(); @@ -144,4 +153,31 @@ describe('AuthTest', () => { }); }); }); + + describe('test multiple authenticate methods', () => { + test('should skip falsy values', async () => { + const config: Config = new AppConfig({ + ...getDefaultConfig(), + plugins: path.join(__dirname, './partials/plugin'), + auth: { + success: {}, + 'fail-invalid-method': {}, + }, + }); + config.checkSecretKey('12345'); + const auth: IAuth = new Auth(config); + await auth.init(); + + return new Promise((resolve) => { + auth.authenticate('foo', 'bar', (err, value) => { + expect(value).toEqual({ + name: 'foo', + groups: ['test', '$all', '$authenticated', '@all', '@authenticated', 'all'], + real_groups: ['test'], + }); + resolve(value); + }); + }); + }); + }); }); diff --git a/packages/auth/test/helper/plugin.ts b/packages/auth/test/helper/plugin.ts index a090608f2..8ce1ef4ee 100644 --- a/packages/auth/test/helper/plugin.ts +++ b/packages/auth/test/helper/plugin.ts @@ -4,21 +4,32 @@ import { getDefaultConfig } from '@verdaccio/config'; export const authProfileConf = { ...getDefaultConfig(), + plugins: path.join(__dirname, '../partials/plugin'), auth: { - [`${path.join(__dirname, '../partials/plugin/authenticate.success')}`]: {}, + success: {}, }, }; export const authPluginFailureConf = { ...getDefaultConfig(), + plugins: path.join(__dirname, '../partials/plugin'), auth: { - [`${path.join(__dirname, '../partials/plugin/authenticate.fail.js')}`]: {}, + fail: {}, }, }; export const authPluginPassThrougConf = { ...getDefaultConfig(), + plugins: path.join(__dirname, '../partials/plugin'), auth: { - [`${path.join(__dirname, '../partials/plugin/authenticate.passthroug')}`]: {}, + passthroug: {}, + }, +}; + +export const authFailInvalidMethod = { + ...getDefaultConfig(), + plugins: path.join(__dirname, '../partials/plugin'), + auth: { + 'fail-invalid-method': {}, }, }; diff --git a/packages/auth/test/partials/plugin/authenticate.fail.js b/packages/auth/test/partials/plugin/authenticate.fail.js deleted file mode 100644 index a358d5430..000000000 --- a/packages/auth/test/partials/plugin/authenticate.fail.js +++ /dev/null @@ -1,11 +0,0 @@ -import { errorUtils } from '@verdaccio/core'; - -module.exports = function () { - return { - authenticate(user, pass, callback) { - // we return an 500 error, the second argument must be false. - // https://verdaccio.org/docs/en/dev-plugins#onerror - callback(errorUtils.getInternalError(), false); - }, - }; -}; diff --git a/packages/auth/test/partials/plugin/verdaccio-fail-invalid-method/fail.js b/packages/auth/test/partials/plugin/verdaccio-fail-invalid-method/fail.js new file mode 100644 index 000000000..d3e39bc7b --- /dev/null +++ b/packages/auth/test/partials/plugin/verdaccio-fail-invalid-method/fail.js @@ -0,0 +1,11 @@ +const { errorUtils } = require('@verdaccio/core'); + +module.exports = function () { + return { + authenticateFake(user, pass, callback) { + /* user and pass are used here to forward errors + and success types respectively for testing purposes */ + callback(errorUtils.getInternalError(), false); + }, + }; +}; diff --git a/packages/auth/test/partials/plugin/verdaccio-fail-invalid-method/package.json b/packages/auth/test/partials/plugin/verdaccio-fail-invalid-method/package.json new file mode 100644 index 000000000..a5750f254 --- /dev/null +++ b/packages/auth/test/partials/plugin/verdaccio-fail-invalid-method/package.json @@ -0,0 +1,5 @@ +{ + "name": "verdaccio-fail", + "main": "fail.js", + "version": "1.0.0" +} diff --git a/packages/auth/test/partials/plugin/verdaccio-fail/fail.js b/packages/auth/test/partials/plugin/verdaccio-fail/fail.js new file mode 100644 index 000000000..435a05333 --- /dev/null +++ b/packages/auth/test/partials/plugin/verdaccio-fail/fail.js @@ -0,0 +1,11 @@ +const { errorUtils } = require('@verdaccio/core'); + +module.exports = function () { + return { + authenticate(user, pass, callback) { + /* user and pass are used here to forward errors + and success types respectively for testing purposes */ + callback(errorUtils.getInternalError(), false); + }, + }; +}; diff --git a/packages/auth/test/partials/plugin/verdaccio-fail/package.json b/packages/auth/test/partials/plugin/verdaccio-fail/package.json new file mode 100644 index 000000000..a5750f254 --- /dev/null +++ b/packages/auth/test/partials/plugin/verdaccio-fail/package.json @@ -0,0 +1,5 @@ +{ + "name": "verdaccio-fail", + "main": "fail.js", + "version": "1.0.0" +} diff --git a/packages/auth/test/partials/plugin/verdaccio-passthroug/package.json b/packages/auth/test/partials/plugin/verdaccio-passthroug/package.json new file mode 100644 index 000000000..cd4d55f21 --- /dev/null +++ b/packages/auth/test/partials/plugin/verdaccio-passthroug/package.json @@ -0,0 +1,5 @@ +{ + "name": "verdaccio-passthroug", + "main": "passthroug.js", + "version": "1.0.0" +} diff --git a/packages/auth/test/partials/plugin/authenticate.passthroug.js b/packages/auth/test/partials/plugin/verdaccio-passthroug/passthroug.js similarity index 100% rename from packages/auth/test/partials/plugin/authenticate.passthroug.js rename to packages/auth/test/partials/plugin/verdaccio-passthroug/passthroug.js diff --git a/packages/auth/test/partials/plugin/verdaccio-success/package.json b/packages/auth/test/partials/plugin/verdaccio-success/package.json new file mode 100644 index 000000000..214cb24d4 --- /dev/null +++ b/packages/auth/test/partials/plugin/verdaccio-success/package.json @@ -0,0 +1,5 @@ +{ + "name": "verdaccio-success", + "main": "success.js", + "version": "1.0.0" +} diff --git a/packages/auth/test/partials/plugin/authenticate.success.js b/packages/auth/test/partials/plugin/verdaccio-success/success.js similarity index 100% rename from packages/auth/test/partials/plugin/authenticate.success.js rename to packages/auth/test/partials/plugin/verdaccio-success/success.js diff --git a/packages/config/src/conf/default.yaml b/packages/config/src/conf/default.yaml index c5f5b2755..12f653b36 100644 --- a/packages/config/src/conf/default.yaml +++ b/packages/config/src/conf/default.yaml @@ -11,8 +11,9 @@ # path to a directory with all packages storage: ./storage -# path to a directory with plugins to include -plugins: ./plugins +# path to a directory with plugins to include, the plugins folder has the higher priority for loading plugins +# disable this folder to avoid warnings if is not used +# plugins: ./plugins # https://verdaccio.org/docs/webui web: @@ -99,6 +100,9 @@ packages: # WORKAROUND: Through given configuration you can workaround following issue https://github.com/verdaccio/verdaccio/issues/301. Set to 0 in case 60 is not enough. server: keepAliveTimeout: 60 + # The pluginPrefix replaces the default plugins prefix which is `verdaccio`, please don't include `-`. If `something` is provided + # the resolve package will be `something-xxxx`. + # pluginPrefix: something # https://verdaccio.org/docs/configuration#offline-publish # publish: diff --git a/packages/config/src/conf/docker.yaml b/packages/config/src/conf/docker.yaml index 62bb5493b..9aab78f3f 100644 --- a/packages/config/src/conf/docker.yaml +++ b/packages/config/src/conf/docker.yaml @@ -14,7 +14,8 @@ # path to a directory with all packages storage: /verdaccio/storage/data -# path to a directory with plugins to include +# path to a directory with plugins to include, the plugins folder has the higher priority for loading plugins +# disable this folder to avoid warnings if is not used plugins: /verdaccio/plugins # https://verdaccio.org/docs/webui @@ -105,6 +106,9 @@ packages: # https://github.com/verdaccio/verdaccio/issues/301. Set to 0 in case 60 is not enough. server: keepAliveTimeout: 60 + # The pluginPrefix replaces the default plugins prefix which is `verdaccio`, please don't include `-`. If `something` is provided + # the resolve package will be `something-xxxx`. + # pluginPrefix: something # https://verdaccio.org/docs/configuration#offline-publish # publish: diff --git a/packages/config/src/config.ts b/packages/config/src/config.ts index a24d59494..62dedbf18 100644 --- a/packages/config/src/config.ts +++ b/packages/config/src/config.ts @@ -43,7 +43,7 @@ class Config implements AppConfig { public configPath: string; public storage: string | void; - public plugins: string | void; + public plugins: string | void | null; public security: Security; public serverSettings: ServerSettingsConf; // @ts-ignore diff --git a/packages/core/types/src/configuration.ts b/packages/core/types/src/configuration.ts index 3ccfb34a3..05b268dbe 100644 --- a/packages/core/types/src/configuration.ts +++ b/packages/core/types/src/configuration.ts @@ -226,6 +226,13 @@ export type ServerSettingsConf = { // express-rate-limit settings rateLimit: RateLimit; keepAliveTimeout?: number; + /** + * Plugins should be prefixed verdaccio-XXXXXX by default. + * To override the default prefix, use this property without `-` + * If you set pluginPrefix: acme, the packages to resolve will be + * acme-XXXXXX + */ + pluginPrefix?: string; }; /** @@ -245,7 +252,7 @@ export interface ConfigYaml { listen?: ListenAddress; https?: HttpsConf; http_proxy?: string; - plugins?: string | void; + plugins?: string | void | null; https_proxy?: string; no_proxy?: string; max_body_size?: string; @@ -264,7 +271,7 @@ export interface ConfigYaml { } /** - * Configuration object with additonal methods for configuration, includes yaml and internal medatada. + * Configuration object with additional methods for configuration, includes yaml and internal medatada. * @interface Config * @extends {ConfigYaml} */ diff --git a/packages/core/types/src/plugins/commons.ts b/packages/core/types/src/plugins/commons.ts index 6d415ce2a..296358aa1 100644 --- a/packages/core/types/src/plugins/commons.ts +++ b/packages/core/types/src/plugins/commons.ts @@ -1,7 +1,7 @@ import { Config, Logger } from '../configuration'; export class Plugin { - public constructor(config: T, options: PluginOptions) {} + public constructor(config: T, options: PluginOptions) {} } export interface IPlugin { @@ -9,7 +9,7 @@ export interface IPlugin { version?: string; } -export interface PluginOptions { - config: T & Config; +export interface PluginOptions { + config: Config; logger: Logger; } diff --git a/packages/core/types/src/plugins/filter.ts b/packages/core/types/src/plugins/filter.ts index 2c8bc1315..d10fcefd2 100644 --- a/packages/core/types/src/plugins/filter.ts +++ b/packages/core/types/src/plugins/filter.ts @@ -2,5 +2,5 @@ import { Manifest } from '../manifest'; import { IPlugin } from './commons'; export interface IPluginStorageFilter extends IPlugin { - filter_metadata(packageInfo: Manifest): Promise; + filterMetadata(packageInfo: Manifest): Promise; } diff --git a/packages/core/types/src/plugins/storage.ts b/packages/core/types/src/plugins/storage.ts index 595e587c6..587bfef18 100644 --- a/packages/core/types/src/plugins/storage.ts +++ b/packages/core/types/src/plugins/storage.ts @@ -84,7 +84,7 @@ export type IPackageStorageManager = ILocalPackageManager; */ interface ILocalData extends IPlugin, ITokenActions { logger: Logger; - config: T & Config; + config: T; add(name: string): Promise; remove(name: string): Promise; get(): Promise; diff --git a/packages/loaders/package.json b/packages/loaders/package.json index a716cb298..96286f0d2 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -20,7 +20,10 @@ "devDependencies": { "@verdaccio/core": "workspace:6.0.0-6-next.47", "@verdaccio/config": "workspace:6.0.0-6-next.47", - "@verdaccio/types": "workspace:11.0.0-6-next.16" + "@verdaccio/types": "workspace:11.0.0-6-next.16", + "@verdaccio-scope/verdaccio-auth-foo": "0.0.2", + "verdaccio-auth-memory": "workspace:*", + "customprefix-auth": "0.0.1" }, "homepage": "https://verdaccio.org", "keywords": [ diff --git a/packages/loaders/src/index.ts b/packages/loaders/src/index.ts index e3a9df4e8..498517ddc 100644 --- a/packages/loaders/src/index.ts +++ b/packages/loaders/src/index.ts @@ -1 +1 @@ -export * from './plugin-loader'; +export { asyncLoadPlugin } from './plugin-async-loader'; diff --git a/packages/loaders/src/plugin-async-loader.ts b/packages/loaders/src/plugin-async-loader.ts new file mode 100644 index 000000000..5907248cd --- /dev/null +++ b/packages/loaders/src/plugin-async-loader.ts @@ -0,0 +1,132 @@ +import buildDebug from 'debug'; +import { lstat } from 'fs/promises'; +import { dirname, isAbsolute, join, resolve } from 'path'; + +import { logger } from '@verdaccio/logger'; +import { Config, IPlugin, Logger } from '@verdaccio/types'; + +import { isES6, isValid, tryLoad } from './utils'; + +const debug = buildDebug('verdaccio:plugin:loader:async'); + +async function isDirectory(pathFolder) { + const stat = await lstat(pathFolder); + return stat.isDirectory(); +} + +export type Params = { config: Config; logger: Logger }; + +/** + * The plugin loader find recursively plugins, if one plugin fails is ignored and report the error to the logger. + * + * The loader follows the order: + * - If the at the `config.yaml` file the `plugins: ./plugins` is defined + * - If is absolute will use the provided path + * - If is relative, will use the base path of the config file. eg: /root/config.yaml the plugins folder should be + * hosted at /root/plugins + * - The next step is find at the node_modules or global based on the `require` native algorithm. + * - If the package is scoped eg: @scope/foo, try to load the package `@scope/foo` + * - If the package is not scoped, will use the default prefix: verdaccio-foo. + * - If a custom prefix is provided, the verdaccio- is replaced by the config.server.pluginPrefix. + * + * The `sanityCheck` is the validation for the required methods to load the plugin, if the validation fails the plugin won't be loaded. + * The `params` is an object that contains the global configuration and the logger. + * + * @param {*} pluginConfigs the custom plugin section + * @param {*} params a set of params to initialize the plugin + * @param {*} sanityCheck callback that check the shape that should fulfill the plugin + * @param {*} prefix by default is verdaccio but can be override with config.server.pluginPrefix + * @return {Array} list of plugins + */ +export async function asyncLoadPlugin>( + pluginConfigs: any = {}, + params: Params, + sanityCheck: any, + prefix: string = 'verdaccio' +): Promise { + const pluginsIds = Object.keys(pluginConfigs); + const { config } = params; + let plugins: any[] = []; + for (let pluginId of pluginsIds) { + debug('plugin %s', pluginId); + if (typeof config.plugins === 'string') { + let pluginsPath = config.plugins; + debug('plugin path %s', pluginsPath); + if (!isAbsolute(pluginsPath)) { + if (typeof config.config_path === 'string' && !config.configPath) { + logger.error( + 'configPath is missing and the legacy config.config_path is not available for loading plugins' + ); + } + + if (!config.configPath) { + logger.error('config path property is required for loading plugins'); + continue; + } + pluginsPath = resolve(join(dirname(config.configPath), pluginsPath)); + } + + logger.debug({ path: pluginsPath }, 'plugins folder defined, loading plugins from @{path} '); + // throws if is nto a directory + try { + await isDirectory(pluginsPath); + const pluginDir = pluginsPath; + const externalFilePlugin = resolve(pluginDir, `${prefix}-${pluginId}`); + let plugin = tryLoad(externalFilePlugin); + if (plugin && isValid(plugin)) { + plugin = executePlugin(plugin, pluginConfigs[pluginId], params); + if (!sanityCheck(plugin)) { + logger.error( + { content: externalFilePlugin }, + "@{content} doesn't look like a valid plugin" + ); + continue; + } + plugins.push(plugin); + continue; + } + } catch (err: any) { + logger.warn( + { err: err.message, pluginsPath, pluginId }, + '@{err} on loading plugins at @{pluginsPath} for @{pluginId}' + ); + } + } + + if (typeof pluginId === 'string') { + const isScoped: boolean = pluginId.startsWith('@') && pluginId.includes('/'); + debug('is scoped plugin %s', isScoped); + const pluginName = isScoped ? pluginId : `${prefix}-${pluginId}`; + debug('plugin pkg name %s', pluginName); + let plugin = tryLoad(pluginName); + if (plugin && isValid(plugin)) { + plugin = executePlugin(plugin, pluginConfigs[pluginId], params); + if (!sanityCheck(plugin)) { + logger.error({ content: pluginName }, "@{content} doesn't look like a valid plugin"); + continue; + } + plugins.push(plugin); + continue; + } else { + logger.error( + { pluginName }, + 'package not found, try to install @{pluginName} with a package manager' + ); + continue; + } + } + } + debug('plugin found %s', plugins.length); + return plugins; +} + +export function executePlugin(plugin, pluginConfig, params: Params) { + if (isES6(plugin)) { + debug('plugin is ES6'); + // eslint-disable-next-line new-cap + return new plugin.default(pluginConfig, params); + } else { + debug('plugin is commonJS'); + return plugin(pluginConfig, params); + } +} diff --git a/packages/loaders/src/plugin-loader.ts b/packages/loaders/src/plugin-loader.ts deleted file mode 100644 index 85a706ed2..000000000 --- a/packages/loaders/src/plugin-loader.ts +++ /dev/null @@ -1,143 +0,0 @@ -import buildDebug from 'debug'; -import _ from 'lodash'; -import Path from 'path'; - -import { logger } from '@verdaccio/logger'; -import { Config, IPlugin } from '@verdaccio/types'; - -const debug = buildDebug('verdaccio:plugin:loader'); - -export const MODULE_NOT_FOUND = 'MODULE_NOT_FOUND'; - -/** - * Requires a module. - * @param {*} path the module's path - * @return {Object} - */ -function tryLoad(path: string): any { - try { - return require(path); - } catch (err: any) { - if (err.code === MODULE_NOT_FOUND) { - return null; - } - throw err; - } -} - -function mergeConfig(appConfig, pluginConfig): Config { - return _.merge(appConfig, pluginConfig); -} - -function isValid(plugin): boolean { - return _.isFunction(plugin) || _.isFunction(plugin.default); -} - -function isES6(plugin): boolean { - return Object.keys(plugin).includes('default'); -} - -// export type PluginGeneric = ; - -/** - * Load a plugin following the rules - * - First try to load from the internal directory plugins (which will disappear soon or later). - * - A second attempt from the external plugin directory - * - A third attempt from node_modules, in case to have multiple match as for instance - * verdaccio-ldap - * and sinopia-ldap. All verdaccio prefix will have preferences. - * @param {*} config a reference of the configuration settings - * @param {*} pluginConfigs - * @param {*} params a set of params to initialize the plugin - * @param {*} sanityCheck callback that check the shape that should fulfill the plugin - * @return {Array} list of plugins - */ -export function loadPlugin>( - config: Config, - pluginConfigs: any = {}, - params: any, - sanityCheck: any, - prefix: string = 'verdaccio' -): any[] { - return Object.keys(pluginConfigs).map((pluginId: string): IPlugin => { - let plugin; - - const localPlugin = Path.resolve(__dirname + '/../plugins', pluginId); - // try local plugins first - plugin = tryLoad(localPlugin); - - // try the external plugin directory - if (plugin === null && config.plugins) { - const pluginDir = config.plugins; - const externalFilePlugin = Path.resolve(pluginDir, pluginId); - plugin = tryLoad(externalFilePlugin); - - // npm package - if (plugin === null && pluginId.match(/^[^\.\/]/)) { - plugin = tryLoad(Path.resolve(pluginDir, `${prefix}-${pluginId}`)); - // compatibility for old sinopia plugins - if (!plugin) { - plugin = tryLoad(Path.resolve(pluginDir, `sinopia-${pluginId}`)); - } - } - } - - // npm package - if (plugin === null && pluginId.match(/^[^\.\/]/)) { - plugin = tryLoad(`${prefix}-${pluginId}`); - // compatibility for old sinopia plugins - if (!plugin) { - plugin = tryLoad(`sinopia-${pluginId}`); - } - } - - if (plugin === null) { - plugin = tryLoad(pluginId); - } - - // relative to config path - debug('config path: %s', config.configPath); - if (plugin === null && pluginId.match(/^\.\.?($|\/)/) && config.configPath) { - plugin = tryLoad(Path.resolve(Path.dirname(config.configPath), pluginId)); - } - - if (plugin === null) { - logger.error( - { content: pluginId, prefix }, - 'plugin not found. try npm install @{prefix}-@{content}' - ); - throw Error(` - ${prefix}-${pluginId} plugin not found. try "npm install ${prefix}-${pluginId}"`); - } - - if (!isValid(plugin)) { - logger.error( - { content: pluginId }, - '@{prefix}-@{content} plugin does not have the right code structure' - ); - throw Error(`"${pluginId}" plugin does not have the right code structure`); - } - - /* eslint new-cap:off */ - try { - plugin = isES6(plugin) - ? new plugin.default(mergeConfig(config, pluginConfigs[pluginId]), params) - : new plugin(pluginConfigs[pluginId], params); - } catch (error: any) { - plugin = null; - logger.error({ error, pluginId }, 'error loading a plugin @{pluginId}: @{error}'); - } - /* eslint new-cap:off */ - - if (plugin === null || !sanityCheck(plugin)) { - logger.error( - { content: pluginId, prefix }, - "@{prefix}-@{content} doesn't look like a valid plugin" - ); - throw Error(`sanity check has failed, "${pluginId}" is not a valid plugin`); - } - - debug('Plugin successfully loaded: %o-%o', pluginId, prefix); - return plugin; - }); -} diff --git a/packages/loaders/src/utils.ts b/packages/loaders/src/utils.ts new file mode 100644 index 000000000..8cc7fe5c8 --- /dev/null +++ b/packages/loaders/src/utils.ts @@ -0,0 +1,40 @@ +import buildDebug from 'debug'; +import _ from 'lodash'; + +import { logger } from '@verdaccio/logger'; +import { Config } from '@verdaccio/types'; + +const debug = buildDebug('verdaccio:plugin:loader:utils'); + +const MODULE_NOT_FOUND = 'MODULE_NOT_FOUND'; + +export function mergeConfig(appConfig, pluginConfig): Config { + return _.merge(appConfig, pluginConfig); +} + +export function isValid(plugin): boolean { + return _.isFunction(plugin) || _.isFunction(plugin.default); +} + +export function isES6(plugin): boolean { + return Object.keys(plugin).includes('default'); +} + +/** + * Requires a module. + * @param {*} path the module's path + * @return {Object} + */ +export function tryLoad(path: string): any { + try { + debug('loading plugin %s', path); + return require(path); + } catch (err: any) { + if (err.code === MODULE_NOT_FOUND) { + debug('plugin %s not found', path); + return null; + } + logger.error({ err: err.msg }, 'error loading plugin @{err}'); + throw err; + } +} diff --git a/packages/loaders/test/partials/config/custom-prefix-auth.yaml b/packages/loaders/test/partials/config/custom-prefix-auth.yaml new file mode 100644 index 000000000..e04f5aca8 --- /dev/null +++ b/packages/loaders/test/partials/config/custom-prefix-auth.yaml @@ -0,0 +1,3 @@ +auth: + '@verdaccio-scope/verdaccio-auth-foo': + enabled: true diff --git a/packages/loaders/test/partials/config/npm-plugin-auth.yaml b/packages/loaders/test/partials/config/npm-plugin-auth.yaml new file mode 100644 index 000000000..262fcd153 --- /dev/null +++ b/packages/loaders/test/partials/config/npm-plugin-auth.yaml @@ -0,0 +1,3 @@ +auth: + auth-memory: + enabled: true diff --git a/packages/loaders/test/partials/config/npm-plugin-not-found.yaml b/packages/loaders/test/partials/config/npm-plugin-not-found.yaml new file mode 100644 index 000000000..b006b3ff4 --- /dev/null +++ b/packages/loaders/test/partials/config/npm-plugin-not-found.yaml @@ -0,0 +1,3 @@ +auth: + not-found: + enabled: true diff --git a/packages/loaders/test/partials/config/plugins-folder-fake.yaml b/packages/loaders/test/partials/config/plugins-folder-fake.yaml new file mode 100644 index 000000000..9179cdfb7 --- /dev/null +++ b/packages/loaders/test/partials/config/plugins-folder-fake.yaml @@ -0,0 +1,4 @@ +auth: + something: + enabled: true +plugins: /roo/does-not-exist diff --git a/packages/loaders/test/partials/config/relative-plugins.yaml b/packages/loaders/test/partials/config/relative-plugins.yaml new file mode 100644 index 000000000..fc4308a1c --- /dev/null +++ b/packages/loaders/test/partials/config/relative-plugins.yaml @@ -0,0 +1,4 @@ +plugins: '../test-plugin-storage' +auth: + plugin: + enabled: true diff --git a/packages/loaders/test/partials/config/scope-auth.yaml b/packages/loaders/test/partials/config/scope-auth.yaml new file mode 100644 index 000000000..e04f5aca8 --- /dev/null +++ b/packages/loaders/test/partials/config/scope-auth.yaml @@ -0,0 +1,3 @@ +auth: + '@verdaccio-scope/verdaccio-auth-foo': + enabled: true diff --git a/packages/loaders/test/partials/config/valid-plugin.yaml b/packages/loaders/test/partials/config/valid-plugin.yaml new file mode 100644 index 000000000..fc911a186 --- /dev/null +++ b/packages/loaders/test/partials/config/valid-plugin.yaml @@ -0,0 +1,3 @@ +auth: + plugin: + enabled: true diff --git a/packages/loaders/test/partials/test-plugin-storage/verdaccio-scope/index.js b/packages/loaders/test/partials/test-plugin-storage/verdaccio-scope/index.js new file mode 100644 index 000000000..75df4c216 --- /dev/null +++ b/packages/loaders/test/partials/test-plugin-storage/verdaccio-scope/index.js @@ -0,0 +1,7 @@ +function ValidScopedVerdaccioPlugin() { + return { + authenticate: function () {}, + }; +} + +module.exports = ValidVerdaccioPlugin; diff --git a/packages/loaders/test/partials/test-plugin-storage/verdaccio-scope/package.json b/packages/loaders/test/partials/test-plugin-storage/verdaccio-scope/package.json new file mode 100644 index 000000000..639abefd1 --- /dev/null +++ b/packages/loaders/test/partials/test-plugin-storage/verdaccio-scope/package.json @@ -0,0 +1,11 @@ +{ + "name": "@verdaccio-scoped/verdaccio-plugin", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/packages/loaders/test/plugin_loader.spec.ts b/packages/loaders/test/plugin_loader.spec.ts deleted file mode 100644 index abb5ec9cd..000000000 --- a/packages/loaders/test/plugin_loader.spec.ts +++ /dev/null @@ -1,88 +0,0 @@ -import path from 'path'; - -import { setup } from '@verdaccio/logger'; - -import { loadPlugin } from '../src/plugin-loader'; - -setup([]); - -describe('plugin loader', () => { - const relativePath = path.join(__dirname, './partials/test-plugin-storage'); - const buildConf = (name) => { - return { - config_path: path.join(__dirname, './'), - max_users: 0, - auth: { - [`${relativePath}/${name}`]: {}, - }, - }; - }; - - describe('auth plugins', () => { - test('testing auth valid plugin loader', () => { - const _config = buildConf('verdaccio-plugin'); - // @ts-ignore - const plugins = loadPlugin(_config, _config.auth, {}, function (plugin) { - return plugin.authenticate || plugin.allow_access || plugin.allow_publish; - }); - - expect(plugins).toHaveLength(1); - }); - - test('testing storage valid plugin loader', () => { - const _config = buildConf('verdaccio-es6-plugin'); - // @ts-ignore - const plugins = loadPlugin(_config, _config.auth, {}, function (p) { - return p.getPackageStorage; - }); - - expect(plugins).toHaveLength(1); - }); - - test('testing auth plugin invalid plugin', () => { - const _config = buildConf('invalid-plugin'); - try { - // @ts-ignore - loadPlugin(_config, _config.auth, {}, function (p) { - return p.authenticate || p.allow_access || p.allow_publish; - }); - } catch (e: any) { - expect(e.message).toEqual( - `"${relativePath}/invalid-plugin" plugin does not have the right code structure` - ); - } - }); - - test('testing auth plugin invalid plugin sanityCheck', () => { - const _config = buildConf('invalid-plugin-sanity'); - try { - // @ts-ignore - loadPlugin(_config, _config.auth, {}, function (plugin) { - return plugin.authenticate || plugin.allow_access || plugin.allow_publish; - }); - } catch (err: any) { - expect(err.message).toEqual( - `sanity check has failed, "${relativePath}/invalid-plugin-sanity" is not a valid plugin` - ); - } - }); - - test('testing auth plugin no plugins', () => { - const _config = buildConf('invalid-package'); - try { - // @ts-ignore - loadPlugin(_config, _config.auth, {}, function (plugin) { - return plugin.authenticate || plugin.allow_access || plugin.allow_publish; - }); - } catch (e: any) { - expect(e.message).toMatch('plugin not found'); - expect(e.message.replace(/\\/g, '/')).toMatch( - '/partials/test-plugin-storage/invalid-package' - ); - } - }); - - test.todo('test middleware plugins'); - test.todo('test storage plugins'); - }); -}); diff --git a/packages/loaders/test/plugin_loader_async.spec.ts b/packages/loaders/test/plugin_loader_async.spec.ts new file mode 100644 index 000000000..392fdcd61 --- /dev/null +++ b/packages/loaders/test/plugin_loader_async.spec.ts @@ -0,0 +1,168 @@ +import path from 'path'; + +import { Config, parseConfigFile } from '@verdaccio/config'; +import { logger, setup } from '@verdaccio/logger'; + +import { asyncLoadPlugin } from '../src/plugin-async-loader'; + +function getConfig(file: string) { + const conPath = path.join(__dirname, './partials/config', file); + return new Config(parseConfigFile(conPath)); +} + +const authSanitize = function (plugin) { + return plugin.authenticate || plugin.allow_access || plugin.allow_publish; +}; + +const pluginsPartialsFolder = path.join(__dirname, './partials/test-plugin-storage'); + +setup(); + +describe('plugin loader', () => { + describe('file plugins', () => { + describe('absolute path', () => { + test('testing auth valid plugin loader', async () => { + const config = getConfig('valid-plugin.yaml'); + config.plugins = pluginsPartialsFolder; + const plugins = await asyncLoadPlugin(config.auth, { config, logger }, authSanitize); + + expect(plugins).toHaveLength(1); + }); + + test('should handle does not exist plugin folder', async () => { + const config = getConfig('plugins-folder-fake.yaml'); + const plugins = await asyncLoadPlugin( + config.auth, + { logger: logger, config: config }, + authSanitize + ); + + expect(plugins).toHaveLength(0); + }); + + test('testing load auth npm package invalid method check', async () => { + const config = getConfig('valid-plugin.yaml'); + config.plugins = pluginsPartialsFolder; + const plugins = await asyncLoadPlugin(config.auth, { config, logger }, (p) => p.anyMethod); + + expect(plugins).toHaveLength(0); + }); + + test('should fails if plugins folder is not a directory', async () => { + const config = getConfig('plugins-folder-fake.yaml'); + // force file instead a folder. + config.plugins = path.join(__dirname, 'just-a-file.js'); + const plugins = await asyncLoadPlugin( + config.auth, + { logger: logger, config: config }, + authSanitize + ); + + expect(plugins).toHaveLength(0); + }); + }); + describe('relative path', () => { + test('should resolve plugin based on relative path', async () => { + const config = getConfig('relative-plugins.yaml'); + // force file instead a folder. + const plugins = await asyncLoadPlugin( + config.auth, + { logger: logger, config: config }, + authSanitize + ); + + expect(plugins).toHaveLength(1); + }); + + test('should fails if config path is missing', async () => { + const config = getConfig('relative-plugins.yaml'); + // @ts-expect-error + config.configPath = undefined; + // @ts-expect-error + config.config_path = undefined; + // force file instead a folder. + const plugins = await asyncLoadPlugin( + config.auth, + { logger: logger, config: config }, + authSanitize + ); + + expect(plugins).toHaveLength(0); + }); + + // config.config_path is not considered for loading plugins due legacy support + test('should fails if config path is missing (only config_path)', async () => { + const config = getConfig('relative-plugins.yaml'); + // @ts-expect-error + config.configPath = undefined; + // force file instead a folder. + const plugins = await asyncLoadPlugin( + config.auth, + { logger: logger, config: config }, + authSanitize + ); + + expect(plugins).toHaveLength(0); + }); + }); + }); + + describe('npm plugins', () => { + test('testing load auth npm package', async () => { + const config = getConfig('npm-plugin-auth.yaml'); + const plugins = await asyncLoadPlugin(config.auth, { config, logger }, authSanitize); + + expect(plugins).toHaveLength(1); + }); + + test('should handle not found installed package', async () => { + const config = getConfig('npm-plugin-not-found.yaml'); + const plugins = await asyncLoadPlugin(config.auth, { config, logger }, (p) => p.anyMethod); + + expect(plugins).toHaveLength(0); + }); + + test('testing load auth npm package invalid method check', async () => { + const config = getConfig('npm-plugin-auth.yaml'); + const plugins = await asyncLoadPlugin(config.auth, { config, logger }, (p) => p.anyMethod); + + expect(plugins).toHaveLength(0); + }); + + test('testing load auth npm package custom prefix', async () => { + const config = getConfig('custom-prefix-auth.yaml'); + const plugins = await asyncLoadPlugin( + config.auth, + { config, logger }, + authSanitize, + 'customprefix' + ); + + expect(plugins).toHaveLength(1); + }); + + test('testing load auth scope npm package', async () => { + const config = getConfig('scope-auth.yaml'); + const plugins = await asyncLoadPlugin(config.auth, { config, logger }, authSanitize); + expect(plugins).toHaveLength(1); + }); + }); + + describe('fallback plugins', () => { + test('should fallback to npm package if does not find on plugins folder', async () => { + const config = getConfig('npm-plugin-auth.yaml'); + config.plugins = pluginsPartialsFolder; + const plugins = await asyncLoadPlugin(config.auth, { config, logger }, authSanitize); + + expect(plugins).toHaveLength(1); + }); + + test('should fallback to npm package if plugins folder does not exist', async () => { + const config = getConfig('npm-plugin-auth.yaml'); + config.plugins = '/does-not-exist'; + const plugins = await asyncLoadPlugin(config.auth, { config, logger }, authSanitize); + + expect(plugins).toHaveLength(1); + }); + }); +}); diff --git a/packages/node-api/src/server.ts b/packages/node-api/src/server.ts index 677c07a5e..f9987b97c 100644 --- a/packages/node-api/src/server.ts +++ b/packages/node-api/src/server.ts @@ -10,7 +10,7 @@ import url from 'url'; import { findConfigFile, parseConfigFile } from '@verdaccio/config'; import { API_ERROR } from '@verdaccio/core'; import { setup } from '@verdaccio/logger'; -import server from '@verdaccio/server'; +import expressServer from '@verdaccio/server'; import fastifyServer from '@verdaccio/server-fastify'; import { ConfigYaml, HttpsConfKeyCert, HttpsConfPfx } from '@verdaccio/types'; @@ -123,7 +123,7 @@ export async function initServer( } }); } else { - app = await server(config); + app = await expressServer(config); const serverFactory = createServerFactory(config, addr, app); serverFactory .listen(addr.port || addr.path, addr.host, (): void => { @@ -196,6 +196,6 @@ export async function runServer(config?: string | ConfigYaml): Promise { displayExperimentsInfoBox(configurationParsed.flags); // FIXME: get only the first match, the multiple address will be removed const [addr] = getListListenAddresses(undefined, configurationParsed.listen); - const app = await server(configurationParsed); + const app = await expressServer(configurationParsed); return createServerFactory(configurationParsed, addr, app); } diff --git a/packages/plugins/audit/src/audit.ts b/packages/plugins/audit/src/audit.ts index a27072e3a..dae81fda0 100644 --- a/packages/plugins/audit/src/audit.ts +++ b/packages/plugins/audit/src/audit.ts @@ -16,7 +16,7 @@ export default class ProxyAudit implements IPluginMiddleware<{}, {}> { public logger: Logger; public strict_ssl: boolean; - public constructor(config: ConfigAudit, options: PluginOptions<{}>) { + public constructor(config: ConfigAudit, options: PluginOptions) { this.enabled = config.enabled || false; this.strict_ssl = config.strict_ssl !== undefined ? config.strict_ssl : true; this.logger = options.logger; diff --git a/packages/plugins/audit/src/types.ts b/packages/plugins/audit/src/types.ts index 75dc0ea07..fc4043349 100644 --- a/packages/plugins/audit/src/types.ts +++ b/packages/plugins/audit/src/types.ts @@ -1,7 +1,4 @@ -// Temporary solution for requiring types will not cause the error. -import { Config } from '@verdaccio/types'; - -export interface ConfigAudit extends Config { +export interface ConfigAudit { enabled: boolean; strict_ssl?: boolean | void; } diff --git a/packages/plugins/auth-memory/src/Memory.ts b/packages/plugins/auth-memory/src/Memory.ts index 318c5221c..ee042ca8e 100644 --- a/packages/plugins/auth-memory/src/Memory.ts +++ b/packages/plugins/auth-memory/src/Memory.ts @@ -3,6 +3,7 @@ import buildDebug from 'debug'; import { API_ERROR, errorUtils } from '@verdaccio/core'; import { Callback, + Config, IPluginAuth, Logger, PackageAccess, @@ -18,12 +19,9 @@ export default class Memory implements IPluginAuth { public _logger: Logger; public _users: Users; public _config: {}; - public _app_config: VerdaccioMemoryConfig; + public _app_config: Config; - public constructor( - config: VerdaccioMemoryConfig, - appOptions: PluginOptions - ) { + public constructor(config: VerdaccioMemoryConfig, appOptions: PluginOptions) { this._users = config.users || {}; this._config = config; this._logger = appOptions.logger; diff --git a/packages/plugins/auth-memory/src/types/index.ts b/packages/plugins/auth-memory/src/types/index.ts index a7de3400f..77e72fb1c 100644 --- a/packages/plugins/auth-memory/src/types/index.ts +++ b/packages/plugins/auth-memory/src/types/index.ts @@ -1,5 +1,3 @@ -import { Config } from '@verdaccio/types'; - export interface UserMemory { name: string; password: string; @@ -9,7 +7,7 @@ export interface Users { [key: string]: UserMemory; } -export interface VerdaccioMemoryConfig extends Config { +export interface VerdaccioMemoryConfig { max_users?: number; users: Users; } diff --git a/packages/plugins/htpasswd/src/htpasswd.ts b/packages/plugins/htpasswd/src/htpasswd.ts index 5b4d254ec..3f5810f9d 100644 --- a/packages/plugins/htpasswd/src/htpasswd.ts +++ b/packages/plugins/htpasswd/src/htpasswd.ts @@ -3,7 +3,7 @@ import fs from 'fs'; import { dirname, join, resolve } from 'path'; import { unlockFile } from '@verdaccio/file-locking'; -import { Callback, Config, IPluginAuth, Logger, PluginOptions } from '@verdaccio/types'; +import { Callback, IPluginAuth, Logger, PluginOptions } from '@verdaccio/types'; import { HtpasswdHashAlgorithm, @@ -24,7 +24,7 @@ export type HTPasswdConfig = { rounds?: number; max_users?: number; slow_verify_ms?: number; -} & Config; +}; export const DEFAULT_BCRYPT_ROUNDS = 10; export const DEFAULT_SLOW_VERIFY_MS = 200; @@ -46,7 +46,7 @@ export default class HTPasswd implements IPluginAuth { private logger: Logger; private lastTime: any; // constructor - public constructor(config: HTPasswdConfig, options: PluginOptions) { + public constructor(config: HTPasswdConfig, options: PluginOptions) { this.users = {}; // verdaccio logger diff --git a/packages/plugins/htpasswd/src/index.ts b/packages/plugins/htpasswd/src/index.ts index 8a222cf94..5afcfc485 100644 --- a/packages/plugins/htpasswd/src/index.ts +++ b/packages/plugins/htpasswd/src/index.ts @@ -1,7 +1,9 @@ +import { PluginOptions } from '@verdaccio/types'; + import HTPasswd, { HTPasswdConfig } from './htpasswd'; -export default function (config: HTPasswdConfig, stuff): HTPasswd { - return new HTPasswd(config, stuff); +export default function (config: HTPasswdConfig, params: PluginOptions): HTPasswd { + return new HTPasswd(config, params); } export { HTPasswd, HTPasswdConfig }; diff --git a/packages/plugins/local-storage/src/local-fs.ts b/packages/plugins/local-storage/src/local-fs.ts index c675e6299..bed3a60fd 100644 --- a/packages/plugins/local-storage/src/local-fs.ts +++ b/packages/plugins/local-storage/src/local-fs.ts @@ -97,10 +97,10 @@ export default class LocalFS implements ILocalFSPackageManager { if (locked) { debug('unlock %s', packageJSONFileName); await this._unlockJSON(packageJSONFileName); - this.logger.debug({ packageName }, 'the package @{packageName} has been updated'); + this.logger.debug({ packageName }, 'the package @{packageName} has been updated'); return manifestUpdated; } else { - this.logger.debug({ packageName }, 'the package @{packageName} has been updated'); + this.logger.debug({ packageName }, 'the package @{packageName} has been updated'); return manifestUpdated; } } catch (err: any) { diff --git a/packages/plugins/local-storage/tests/local-database.test.ts b/packages/plugins/local-storage/tests/local-database.test.ts index c337e3e26..77745050d 100644 --- a/packages/plugins/local-storage/tests/local-database.test.ts +++ b/packages/plugins/local-storage/tests/local-database.test.ts @@ -20,7 +20,7 @@ jest.mock('../src/fs', () => ({ setup(); // @ts-expect-error -const optionsPlugin: PluginOptions<{}> = { +const optionsPlugin: PluginOptions = { logger, }; diff --git a/packages/plugins/memory/src/local-memory.ts b/packages/plugins/memory/src/local-memory.ts index de1fb6714..91900f51b 100644 --- a/packages/plugins/memory/src/local-memory.ts +++ b/packages/plugins/memory/src/local-memory.ts @@ -1,11 +1,11 @@ import buildDebug from 'debug'; import { errorUtils } from '@verdaccio/core'; -import { Callback, Config, IPluginStorage, Logger, PluginOptions, Token } from '@verdaccio/types'; +import { Callback, IPluginStorage, Logger, PluginOptions, Token } from '@verdaccio/types'; import MemoryHandler, { DataHandler } from './memory-handler'; -export type ConfigMemory = Config & { limit?: number }; +export type ConfigMemory = { limit?: number }; export interface MemoryLocalStorage { secret: string; list: string[]; @@ -22,7 +22,7 @@ class LocalMemory implements IPluginStorage { private data: MemoryLocalStorage; public config: ConfigMemory; - public constructor(config: ConfigMemory, options: PluginOptions) { + public constructor(config: ConfigMemory, options: PluginOptions) { this.config = config; this.limit = config.limit || DEFAULT_LIMIT; this.logger = options.logger; diff --git a/packages/server/express/src/server.ts b/packages/server/express/src/server.ts index 17281a1b1..641d2f116 100644 --- a/packages/server/express/src/server.ts +++ b/packages/server/express/src/server.ts @@ -11,12 +11,12 @@ import apiEndpoint from '@verdaccio/api'; import { Auth, IBasicAuth } from '@verdaccio/auth'; import { Config as AppConfig } from '@verdaccio/config'; import { API_ERROR, HTTP_STATUS, errorUtils } from '@verdaccio/core'; -import { loadPlugin } from '@verdaccio/loaders'; +import { asyncLoadPlugin } from '@verdaccio/loaders'; import { logger } from '@verdaccio/logger'; import { errorReportingMiddleware, final, log } from '@verdaccio/middleware'; import { Storage } from '@verdaccio/store'; import { ConfigYaml } from '@verdaccio/types'; -import { Config as IConfig, IPlugin, IPluginStorageFilter } from '@verdaccio/types'; +import { Config as IConfig, IPlugin } from '@verdaccio/types'; import webMiddleware from '@verdaccio/web'; import { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types/custom'; @@ -29,8 +29,9 @@ export interface IPluginMiddleware extends IPlugin { const debug = buildDebug('verdaccio:server'); -const defineAPI = function (config: IConfig, storage: Storage): any { +const defineAPI = async function (config: IConfig, storage: Storage): Promise { const auth: Auth = new Auth(config); + await auth.init(); const app: Application = express(); const limiter = new RateLimit(config.serverSettings.rateLimit); // run in production mode by default, just in case @@ -62,27 +63,21 @@ const defineAPI = function (config: IConfig, storage: Storage): any { hookDebug(app, config.configPath); } - // register middleware plugins - const plugin_params = { - config: config, - logger: logger, - }; - - const plugins: IPluginMiddleware[] = loadPlugin( - config, + const plugins: IPluginMiddleware[] = await asyncLoadPlugin( config.middlewares, - plugin_params, + { + config, + logger, + }, function (plugin: IPluginMiddleware) { return plugin.register_middlewares; } ); - if (_.isEmpty(plugins)) { + if (plugins.length === 0) { + logger.info('none middleware plugins has been defined, adding audit middleware by default'); plugins.push( - new AuditMiddleware( - { ...config, enabled: true, strict_ssl: true }, - { config, logger: logger } - ) + new AuditMiddleware({ ...config, enabled: true, strict_ssl: true }, { config, logger }) ); } @@ -96,7 +91,7 @@ const defineAPI = function (config: IConfig, storage: Storage): any { // For WebUI & WebUI API if (_.get(config, 'web.enable', true)) { - app.use(webMiddleware(config, auth, storage)); + app.use(await webMiddleware(config, auth, storage)); } else { app.get('/', function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) { next(errorUtils.getNotFound(API_ERROR.WEB_DISABLED)); @@ -135,31 +130,21 @@ const defineAPI = function (config: IConfig, storage: Storage): any { return app; }; -export default (async function (configHash: ConfigYaml): Promise { +export default (async function startServer(configHash: ConfigYaml): Promise { debug('start server'); - const config: IConfig = new AppConfig(_.cloneDeep(configHash) as any); + const config: IConfig = new AppConfig({ ...configHash } as any); // register middleware plugins - const plugin_params = { - config: config, - logger, - }; - const filters = loadPlugin( - config, - config.filters || {}, - plugin_params, - (plugin: IPluginStorageFilter) => plugin.filter_metadata - ); debug('loaded filter plugin'); // @ts-ignore const storage: Storage = new Storage(config); try { // waits until init calls have been initialized debug('storage init start'); - await storage.init(config, filters); + await storage.init(config); debug('storage init end'); } catch (err: any) { logger.error({ error: err.msg }, 'storage has failed: @{error}'); throw new Error(err); } - return defineAPI(config, storage); + return await defineAPI(config, storage); }); diff --git a/packages/server/fastify/src/plugins/auth.ts b/packages/server/fastify/src/plugins/auth.ts index 3341818b6..b9a6b2cb1 100644 --- a/packages/server/fastify/src/plugins/auth.ts +++ b/packages/server/fastify/src/plugins/auth.ts @@ -8,6 +8,7 @@ export default fp( async function (fastify: FastifyInstance, opts: { config: IConfig; filters?: unknown }) { const { config } = opts; const auth: IAuth = new Auth(config); + await auth.init(); fastify.decorate('auth', auth); }, { diff --git a/packages/store/src/local-storage.ts b/packages/store/src/local-storage.ts index 096eeb708..68c9e0ec6 100644 --- a/packages/store/src/local-storage.ts +++ b/packages/store/src/local-storage.ts @@ -3,7 +3,7 @@ import buildDebug from 'debug'; import _ from 'lodash'; import { errorUtils, pluginUtils } from '@verdaccio/core'; -import { loadPlugin } from '@verdaccio/loaders'; +import { asyncLoadPlugin } from '@verdaccio/loaders'; import LocalDatabase from '@verdaccio/local-storage'; import { Config, Logger } from '@verdaccio/types'; @@ -31,7 +31,7 @@ class LocalStorage { public async init() { if (this.storagePlugin === null) { - this.storagePlugin = this._loadStorage(this.config, this.logger); + this.storagePlugin = await this.loadStorage(this.config, this.logger); debug('storage plugin init'); await this.storagePlugin.init(); debug('storage plugin initialized'); @@ -55,31 +55,35 @@ class LocalStorage { return this.storagePlugin.setSecret(config.checkSecretKey(secretKey)); } - private _loadStorage(config: Config, logger: Logger): IPluginStorage { - const Storage = this._loadStorePlugin(); - + private async loadStorage(config: Config, logger: Logger): Promise { + const Storage = await this.loadStorePlugin(); if (_.isNil(Storage)) { assert(this.config.storage, 'CONFIG: storage path not defined'); + debug('no custom storage found, loading default storage @verdaccio/local-storage'); return new LocalDatabase(config, logger); } return Storage as IPluginStorage; } - private _loadStorePlugin(): IPluginStorage | void { - const plugin_params = { - config: this.config, - logger: this.logger, - }; - - const plugins: IPluginStorage[] = loadPlugin( - this.config, + private async loadStorePlugin(): Promise { + const plugins: IPluginStorage[] = await asyncLoadPlugin( this.config.store, - plugin_params, + { + config: this.config, + logger: this.logger, + }, (plugin): IPluginStorage => { return plugin.getPackageStorage; - } + }, + this.config?.server?.pluginPrefix ); + if (plugins.length > 1) { + this.logger.warn( + 'more than one storage plugins has been detected, multiple storage are not supported, one will be selected automatically' + ); + } + return _.head(plugins); } } diff --git a/packages/store/src/storage.ts b/packages/store/src/storage.ts index 98bfea187..789b295e5 100644 --- a/packages/store/src/storage.ts +++ b/packages/store/src/storage.ts @@ -19,6 +19,7 @@ import { searchUtils, validatioUtils, } from '@verdaccio/core'; +import { asyncLoadPlugin } from '@verdaccio/loaders'; import { logger } from '@verdaccio/logger'; import { IProxy, ISyncUplinksOptions, ProxySearchParams, ProxyStorage } from '@verdaccio/proxy'; import { @@ -33,6 +34,7 @@ import { DistFile, GenericBody, IPackageStorage, + IPluginStorageFilter, Logger, Manifest, MergeTags, @@ -80,7 +82,7 @@ export const PROTO_NAME = '__proto__'; class Storage { public localStorage: LocalStorage; - public filters: IPluginFilters; + public filters: IPluginFilters | null; public readonly config: Config; public readonly logger: Logger; public readonly uplinks: ProxyInstanceList; @@ -88,7 +90,7 @@ class Storage { this.config = config; this.uplinks = setupUpLinks(config); this.logger = logger.child({ module: 'storage' }); - this.filters = []; + this.filters = null; // @ts-ignore this.localStorage = null; debug('uplinks available %o', Object.keys(this.uplinks)); @@ -655,10 +657,8 @@ class Storage { * @param filters IPluginFilters * @returns Storage instance */ - public async init(config: Config, filters: IPluginFilters = []): Promise { + public async init(config: Config): Promise { if (this.localStorage === null) { - this.filters = filters || []; - debug('filters available %o', filters); this.localStorage = new LocalStorage(this.config, logger); await this.localStorage.init(); debug('local init storage initialized'); @@ -667,6 +667,20 @@ class Storage { } else { debug('storage has been already initialized'); } + if (!this.filters) { + this.filters = await asyncLoadPlugin>( + this.config.filters, + { + config: this.config, + logger: this.logger, + }, + (plugin) => { + return plugin.filterMetadata; + }, + this.config?.server?.pluginPrefix + ); + debug('filters available %o', this.filters); + } return; } @@ -1728,7 +1742,7 @@ class Storage { * @returns */ public async applyFilters(manifest: Manifest): Promise<[Manifest, any]> { - if (this.filters.length === 0) { + if (this.filters === null || this.filters.length === 0) { return [manifest, []]; } @@ -1739,7 +1753,7 @@ class Storage { // and return it directly for // performance (i.e. need not be pure) try { - filteredManifest = await filter.filter_metadata(manifest); + filteredManifest = await filter.filterMetadata(manifest); } catch (err: any) { this.logger.error({ err: err.message }, 'filter has failed @{err}'); filterPluginErrors.push(err); diff --git a/packages/tools/helpers/jest.config.js b/packages/tools/helpers/jest.config.js index 9d99de964..a8bfe6885 100644 --- a/packages/tools/helpers/jest.config.js +++ b/packages/tools/helpers/jest.config.js @@ -4,7 +4,7 @@ module.exports = Object.assign({}, config, { coverageThreshold: { global: { // FIXME: increase to 90 - lines: 50, + lines: 48, }, }, }); diff --git a/packages/tools/helpers/src/initializeServer.ts b/packages/tools/helpers/src/initializeServer.ts index bb516413d..e7d5968ec 100644 --- a/packages/tools/helpers/src/initializeServer.ts +++ b/packages/tools/helpers/src/initializeServer.ts @@ -27,14 +27,19 @@ export async function initializeServer( const storage = new Storage(config); await storage.init(config, []); const auth: IAuth = new Auth(config); + await auth.init(); // TODO: this might not be need it, used in apiEndpoints app.use(bodyParser.json({ strict: false, limit: '10mb' })); // @ts-ignore app.use(errorReportingMiddleware); - // @ts-ignore - routesMiddleware.map((route: any) => { - app.use(route(config, auth, storage)); - }); + for (let route of routesMiddleware) { + if (route.async) { + const middleware = await route.routes(config, auth, storage); + app.use(middleware); + } else { + app.use(route(config, auth, storage)); + } + } // catch 404 app.get('/*', function (req, res, next) { diff --git a/packages/tools/verdaccio-prefix-fake-plugin/index.js b/packages/tools/verdaccio-prefix-fake-plugin/index.js new file mode 100644 index 000000000..c1291e321 --- /dev/null +++ b/packages/tools/verdaccio-prefix-fake-plugin/index.js @@ -0,0 +1,7 @@ +function ValidVerdaccioPlugin() { + return { + authenticate: function () {}, + }; +} + +module.exports = ValidVerdaccioPlugin; diff --git a/packages/tools/verdaccio-prefix-fake-plugin/package.json b/packages/tools/verdaccio-prefix-fake-plugin/package.json new file mode 100644 index 000000000..7870626ba --- /dev/null +++ b/packages/tools/verdaccio-prefix-fake-plugin/package.json @@ -0,0 +1,13 @@ +{ + "name": "customprefix-auth", + "version": "0.0.1", + "private": true, + "description": "fake plugin for test", + "author": "Juan Picado ", + "license": "MIT", + "homepage": "https://verdaccio.org", + "main": "index.js", + "scripts": { + "build": "echo 0" + } +} diff --git a/packages/verdaccio/test/types-test/plugins/auth/example.auth.plugin.ts b/packages/verdaccio/test/types-test/plugins/auth/example.auth.plugin.ts index 00688fc90..7c345bd33 100644 --- a/packages/verdaccio/test/types-test/plugins/auth/example.auth.plugin.ts +++ b/packages/verdaccio/test/types-test/plugins/auth/example.auth.plugin.ts @@ -16,7 +16,7 @@ class ExampleAuthPlugin implements IPluginAuth<{}> { config: AppConfig; logger: Logger; - constructor(config: AppConfig, options: PluginOptions<{}>) { + constructor(config: AppConfig, options: PluginOptions) { this.config = config; this.logger = options.logger; } @@ -50,7 +50,7 @@ class ExampleAuthCustomPlugin implements IPluginAuth<{}> { config: AppConfig; logger: Logger; - constructor(config: AppConfig, options: PluginOptions<{}>) { + constructor(config: AppConfig, options: PluginOptions) { this.config = config; this.logger = options.logger; } @@ -81,7 +81,7 @@ const config1: AppConfig = new Config({ config_path: '/home/sotrage', }); -const options: PluginOptions<{}> = { +const options: PluginOptions = { config: config1, logger: logger.child(), }; diff --git a/packages/web/src/middleware/render-web.ts b/packages/web/src/middleware/render-web.ts index 15f05b89a..dc0c25859 100644 --- a/packages/web/src/middleware/render-web.ts +++ b/packages/web/src/middleware/render-web.ts @@ -4,7 +4,8 @@ import _ from 'lodash'; import path from 'path'; import { HTTP_STATUS } from '@verdaccio/core'; -import { loadPlugin } from '@verdaccio/loaders'; +import { asyncLoadPlugin } from '@verdaccio/loaders'; +import { logger } from '@verdaccio/logger'; import { isURLhasValidProtocol } from '@verdaccio/url'; import renderHTML from '../renderHTML'; @@ -12,19 +13,24 @@ import { setSecurityWebHeaders } from './security'; const debug = buildDebug('verdaccio:web:render'); -export function loadTheme(config) { +export async function loadTheme(config: any) { if (_.isNil(config.theme) === false) { - return _.head( - loadPlugin( - config, - config.theme, - {}, - function (plugin) { - return _.isString(plugin); - }, - 'verdaccio-theme' - ) + const plugin = await asyncLoadPlugin( + config.theme, + // @ts-ignore + { config, logger }, + function (plugin: string) { + return typeof plugin === 'string'; + }, + config?.server?.pluginPrefix ?? 'verdaccio-theme' ); + if (plugin.length > 1) { + logger.warn( + 'multiple ui themes has been detected and is not supported, only the first one will be used' + ); + } + + return _.head(plugin); } } @@ -39,8 +45,9 @@ const sendFileCallback = (next) => (err) => { } }; -export function renderWebMiddleware(config, auth): any { - const { staticPath, manifest, manifestFiles } = require('@verdaccio/ui-theme')(); +export async function renderWebMiddleware(config, auth): Promise { + const { staticPath, manifest, manifestFiles } = + (await loadTheme(config)) || require('@verdaccio/ui-theme')(); debug('static path %o', staticPath); /* eslint new-cap:off */ diff --git a/packages/web/src/web-middleware.ts b/packages/web/src/web-middleware.ts index eaea57292..d6570cb5c 100644 --- a/packages/web/src/web-middleware.ts +++ b/packages/web/src/web-middleware.ts @@ -3,11 +3,11 @@ import express from 'express'; import { renderWebMiddleware } from './middleware/render-web'; import { webAPI } from './middleware/web-api'; -export default (config, auth, storage) => { +export default async (config, auth, storage) => { // eslint-disable-next-line new-cap const app = express.Router(); // load application - app.use('/', renderWebMiddleware(config, auth)); + app.use('/', await renderWebMiddleware(config, auth)); // web endpoints, search, packages, etc app.use('/-/verdaccio/', webAPI(config, auth, storage)); return app; diff --git a/packages/web/test/helper.ts b/packages/web/test/helper.ts index 7f1f9743b..7b0c20760 100644 --- a/packages/web/test/helper.ts +++ b/packages/web/test/helper.ts @@ -18,5 +18,9 @@ export const getConf = (configName: string) => { // @deprecated export async function initializeServer(configName): Promise { - return initializeServerHelper(getConf(configName), [apiMiddleware, routes], Storage); + return initializeServerHelper( + getConf(configName), + [apiMiddleware, { async: true, routes }], + Storage + ); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e36f72c2..0eb78ad4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -426,20 +426,26 @@ importers: packages/loaders: specifiers: + '@verdaccio-scope/verdaccio-auth-foo': 0.0.2 '@verdaccio/config': workspace:6.0.0-6-next.47 '@verdaccio/core': workspace:6.0.0-6-next.47 '@verdaccio/logger': workspace:6.0.0-6-next.15 '@verdaccio/types': workspace:11.0.0-6-next.16 + customprefix-auth: 0.0.1 debug: 4.3.4 lodash: 4.17.21 + verdaccio-auth-memory: workspace:* dependencies: '@verdaccio/logger': link:../logger debug: 4.3.4 lodash: 4.17.21 devDependencies: + '@verdaccio-scope/verdaccio-auth-foo': 0.0.2 '@verdaccio/config': link:../config '@verdaccio/core': link:../core/core '@verdaccio/types': link:../core/types + customprefix-auth: link:../tools/verdaccio-prefix-fake-plugin + verdaccio-auth-memory: link:../plugins/auth-memory packages/logger: specifiers: @@ -1092,6 +1098,9 @@ importers: ts-node: 10.9.1_03bba433ca420ee50bf31a7f62506788 verdaccio: link:../../verdaccio + packages/tools/verdaccio-prefix-fake-plugin: + specifiers: {} + packages/types: specifiers: lunr-mutable-indexes: 2.3.2 @@ -9340,6 +9349,21 @@ packages: '@typescript-eslint/types': 5.37.0 eslint-visitor-keys: 3.3.0 + /@verdaccio-scope/verdaccio-auth-foo/0.0.2: + resolution: {integrity: sha512-BqeDqLcYcm3CRYlrQnAueKg8vabBtqwgZ4jRLZJtig+JWzSX50sKdWrzbhf6waQT/HjRO+ADUs/2gjl1tdOTMQ==} + engines: {node: '>=12'} + dependencies: + '@verdaccio/commons-api': 10.2.0 + dev: true + + /@verdaccio/commons-api/10.2.0: + resolution: {integrity: sha512-F/YZANu4DmpcEV0jronzI7v2fGVWkQ5Mwi+bVmV+ACJ+EzR0c9Jbhtbe5QyLUuzR97t8R5E/Xe53O0cc2LukdQ==} + engines: {node: '>=8'} + dependencies: + http-errors: 2.0.0 + http-status-codes: 2.2.0 + dev: true + /@vue/compiler-core/3.0.11: resolution: {integrity: sha512-6sFj6TBac1y2cWCvYCA8YzHJEbsVkX7zdRs/3yK/n1ilvRqcn983XvpBbnN3v4mZ1UiQycTvOiajJmOgN9EVgw==} dependencies: @@ -15119,7 +15143,6 @@ packages: /http-status-codes/2.2.0: resolution: {integrity: sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng==} - dev: false /http2-wrapper/1.0.3: resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==}