mirror of
https://github.com/verdaccio/verdaccio.git
synced 2025-02-21 07:29:37 +01:00
feat: allow override user rate limit and user agent (#2803)
* feat: allow override user rate limit - improve user_agent config allow string * chore: fix tests * chore: refactor userRateLimit * chore: remove comment * chore: optional prop * chore: refactor limiter * chore: refactor endpoints * chore: fix undefined * chore: fix params * chore: fix params * chore: update ui * chore: refactor limiter * chore: fix tests * chore: fix test
This commit is contained in:
parent
f64e403f0a
commit
5b1264c733
4
.github/workflows/e2e-jest-workflow.yml
vendored
4
.github/workflows/e2e-jest-workflow.yml
vendored
@ -117,7 +117,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 12.x
|
node-version: 12.x
|
||||||
- name: 'install latest npm'
|
- name: 'install latest npm'
|
||||||
run: npm i -g npm
|
run: npm i -g npm@next-7
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: yarn install
|
run: yarn install
|
||||||
- name: 'Run verdaccio in the background'
|
- name: 'Run verdaccio in the background'
|
||||||
@ -140,7 +140,7 @@ jobs:
|
|||||||
yarn jest module.test.js
|
yarn jest module.test.js
|
||||||
|
|
||||||
pnpm:
|
pnpm:
|
||||||
name: 'pnpm:jest example'
|
name: 'pnpm:latest:jest example'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
20
.pnp.js
generated
20
.pnp.js
generated
@ -83,8 +83,8 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["@verdaccio/local-storage", "npm:10.1.0"],
|
["@verdaccio/local-storage", "npm:10.1.0"],
|
||||||
["@verdaccio/readme", "npm:10.2.0"],
|
["@verdaccio/readme", "npm:10.2.0"],
|
||||||
["@verdaccio/streams", "npm:10.1.0"],
|
["@verdaccio/streams", "npm:10.1.0"],
|
||||||
["@verdaccio/types", "npm:9.7.2"],
|
["@verdaccio/types", "npm:10.2.2"],
|
||||||
["@verdaccio/ui-theme", "npm:3.2.1"],
|
["@verdaccio/ui-theme", "npm:3.4.1"],
|
||||||
["JSONStream", "npm:1.3.5"],
|
["JSONStream", "npm:1.3.5"],
|
||||||
["all-contributors-cli", "npm:6.20.0"],
|
["all-contributors-cli", "npm:6.20.0"],
|
||||||
["async", "npm:3.2.2"],
|
["async", "npm:3.2.2"],
|
||||||
@ -5397,19 +5397,19 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
}]
|
}]
|
||||||
]],
|
]],
|
||||||
["@verdaccio/types", [
|
["@verdaccio/types", [
|
||||||
["npm:9.7.2", {
|
["npm:10.2.2", {
|
||||||
"packageLocation": "./.yarn/cache/@verdaccio-types-npm-9.7.2-9a89bfb123-ad04a1cd1f.zip/node_modules/@verdaccio/types/",
|
"packageLocation": "./.yarn/cache/@verdaccio-types-npm-10.2.2-3a8a5ff733-cd16cad480.zip/node_modules/@verdaccio/types/",
|
||||||
"packageDependencies": [
|
"packageDependencies": [
|
||||||
["@verdaccio/types", "npm:9.7.2"]
|
["@verdaccio/types", "npm:10.2.2"]
|
||||||
],
|
],
|
||||||
"linkType": "HARD",
|
"linkType": "HARD",
|
||||||
}]
|
}]
|
||||||
]],
|
]],
|
||||||
["@verdaccio/ui-theme", [
|
["@verdaccio/ui-theme", [
|
||||||
["npm:3.2.1", {
|
["npm:3.4.1", {
|
||||||
"packageLocation": "./.yarn/cache/@verdaccio-ui-theme-npm-3.2.1-86c131bd8f-bd0c23b2ff.zip/node_modules/@verdaccio/ui-theme/",
|
"packageLocation": "./.yarn/cache/@verdaccio-ui-theme-npm-3.4.1-ff9f347e40-9873985e77.zip/node_modules/@verdaccio/ui-theme/",
|
||||||
"packageDependencies": [
|
"packageDependencies": [
|
||||||
["@verdaccio/ui-theme", "npm:3.2.1"]
|
["@verdaccio/ui-theme", "npm:3.4.1"]
|
||||||
],
|
],
|
||||||
"linkType": "HARD",
|
"linkType": "HARD",
|
||||||
}]
|
}]
|
||||||
@ -17240,8 +17240,8 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["@verdaccio/local-storage", "npm:10.1.0"],
|
["@verdaccio/local-storage", "npm:10.1.0"],
|
||||||
["@verdaccio/readme", "npm:10.2.0"],
|
["@verdaccio/readme", "npm:10.2.0"],
|
||||||
["@verdaccio/streams", "npm:10.1.0"],
|
["@verdaccio/streams", "npm:10.1.0"],
|
||||||
["@verdaccio/types", "npm:9.7.2"],
|
["@verdaccio/types", "npm:10.2.2"],
|
||||||
["@verdaccio/ui-theme", "npm:3.2.1"],
|
["@verdaccio/ui-theme", "npm:3.4.1"],
|
||||||
["JSONStream", "npm:1.3.5"],
|
["JSONStream", "npm:1.3.5"],
|
||||||
["all-contributors-cli", "npm:6.20.0"],
|
["all-contributors-cli", "npm:6.20.0"],
|
||||||
["async", "npm:3.2.2"],
|
["async", "npm:3.2.2"],
|
||||||
|
BIN
.yarn/cache/@verdaccio-types-npm-10.2.2-3a8a5ff733-cd16cad480.zip
vendored
Normal file
BIN
.yarn/cache/@verdaccio-types-npm-10.2.2-3a8a5ff733-cd16cad480.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/@verdaccio-ui-theme-npm-3.4.1-ff9f347e40-9873985e77.zip
vendored
Normal file
BIN
.yarn/cache/@verdaccio-ui-theme-npm-3.4.1-ff9f347e40-9873985e77.zip
vendored
Normal file
Binary file not shown.
@ -22,7 +22,7 @@
|
|||||||
"@verdaccio/local-storage": "10.1.0",
|
"@verdaccio/local-storage": "10.1.0",
|
||||||
"@verdaccio/readme": "10.2.0",
|
"@verdaccio/readme": "10.2.0",
|
||||||
"@verdaccio/streams": "10.1.0",
|
"@verdaccio/streams": "10.1.0",
|
||||||
"@verdaccio/ui-theme": "3.2.1",
|
"@verdaccio/ui-theme": "3.4.1",
|
||||||
"JSONStream": "1.3.5",
|
"JSONStream": "1.3.5",
|
||||||
"async": "3.2.2",
|
"async": "3.2.2",
|
||||||
"body-parser": "1.19.1",
|
"body-parser": "1.19.1",
|
||||||
@ -104,7 +104,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "4.13.0",
|
"@typescript-eslint/eslint-plugin": "4.13.0",
|
||||||
"@typescript-eslint/parser": "4.13.0",
|
"@typescript-eslint/parser": "4.13.0",
|
||||||
"@verdaccio/eslint-config": "^8.5.0",
|
"@verdaccio/eslint-config": "^8.5.0",
|
||||||
"@verdaccio/types": "^9.7.2",
|
"@verdaccio/types": "10.2.2",
|
||||||
"all-contributors-cli": "6.20.0",
|
"all-contributors-cli": "6.20.0",
|
||||||
"babel-eslint": "10.1.0",
|
"babel-eslint": "10.1.0",
|
||||||
"babel-jest": "26.6.3",
|
"babel-jest": "26.6.3",
|
||||||
|
@ -9,21 +9,20 @@ import { createRemoteUser, createSessionToken, getApiToken, getAuthenticatedMess
|
|||||||
import { logger } from '../../../lib/logger';
|
import { logger } from '../../../lib/logger';
|
||||||
|
|
||||||
import { $RequestExtend, $ResponseExtend, $NextFunctionVer, IAuth } from '../../../../types';
|
import { $RequestExtend, $ResponseExtend, $NextFunctionVer, IAuth } from '../../../../types';
|
||||||
import { limiter } from '../../user-rate-limit';
|
import { limiter } from '../../rate-limiter';
|
||||||
|
|
||||||
export default function (route: Router, auth: IAuth, config: Config): void {
|
export default function (route: Router, auth: IAuth, config: Config): void {
|
||||||
/* eslint new-cap:off */
|
/* eslint new-cap:off */
|
||||||
const userRouter = express.Router();
|
const userRouter = express.Router();
|
||||||
userRouter.use(limiter);
|
|
||||||
|
|
||||||
userRouter.get('/-/user/:org_couchdb_user', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
userRouter.get('/-/user/:org_couchdb_user', limiter(config?.userRateLimit), function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||||
res.status(HTTP_STATUS.OK);
|
res.status(HTTP_STATUS.OK);
|
||||||
next({
|
next({
|
||||||
ok: getAuthenticatedMessage(req.remote_user.name),
|
ok: getAuthenticatedMessage(req.remote_user.name),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
userRouter.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
userRouter.put('/-/user/:org_couchdb_user/:_rev?/:revision?', limiter(config?.userRateLimit), function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||||
const { name, password } = req.body;
|
const { name, password } = req.body;
|
||||||
const remoteName = req.remote_user.name;
|
const remoteName = req.remote_user.name;
|
||||||
|
|
||||||
@ -74,7 +73,7 @@ export default function (route: Router, auth: IAuth, config: Config): void {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
userRouter.delete('/-/user/token/*', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
userRouter.delete('/-/user/token/*', limiter(config?.userRateLimit), function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||||
res.status(HTTP_STATUS.OK);
|
res.status(HTTP_STATUS.OK);
|
||||||
next({
|
next({
|
||||||
ok: API_MESSAGE.LOGGED_OUT,
|
ok: API_MESSAGE.LOGGED_OUT,
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
import { Response, Router } from 'express';
|
import { Response, Router } from 'express';
|
||||||
import { limiter } from '../../../user-rate-limit';
|
|
||||||
import profile from './profile';
|
import profile from './profile';
|
||||||
import token from './token';
|
import token from './token';
|
||||||
import v1Search from './search';
|
|
||||||
|
|
||||||
export default (auth, storage, config) => {
|
export default (auth, storage, config) => {
|
||||||
const route = Router(); /* eslint new-cap: 0 */
|
const route = Router(); /* eslint new-cap: 0 */
|
||||||
route.use(limiter);
|
route.use('/-/npm/v1/', profile(auth, config));
|
||||||
route.use('/-/npm/v1/', profile(auth));
|
|
||||||
route.use('/-/npm/v1/', token(auth, storage, config));
|
route.use('/-/npm/v1/', token(auth, storage, config));
|
||||||
return route;
|
return route;
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,7 @@ import { ErrorCode } from '../../../../lib/utils';
|
|||||||
import { validatePassword } from '../../../../lib/auth-utils';
|
import { validatePassword } from '../../../../lib/auth-utils';
|
||||||
|
|
||||||
import { $NextFunctionVer, $RequestExtend, IAuth } from '../../../../../types';
|
import { $NextFunctionVer, $RequestExtend, IAuth } from '../../../../../types';
|
||||||
|
import { limiter } from '../../../rate-limiter';
|
||||||
|
|
||||||
export interface Profile {
|
export interface Profile {
|
||||||
tfa: boolean;
|
tfa: boolean;
|
||||||
@ -17,7 +18,7 @@ export interface Profile {
|
|||||||
fullname: string;
|
fullname: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (auth: IAuth): Router {
|
export default function (auth: IAuth, config): Router {
|
||||||
const profileRoute = Router(); /* eslint new-cap: 0 */
|
const profileRoute = Router(); /* eslint new-cap: 0 */
|
||||||
function buildProfile(name: string): Profile {
|
function buildProfile(name: string): Profile {
|
||||||
return {
|
return {
|
||||||
@ -32,7 +33,7 @@ export default function (auth: IAuth): Router {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
profileRoute.get('/user', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
profileRoute.get('/user', limiter(config?.userRateLimit), function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||||
if (_.isNil(req.remote_user.name) === false) {
|
if (_.isNil(req.remote_user.name) === false) {
|
||||||
return next(buildProfile(req.remote_user.name));
|
return next(buildProfile(req.remote_user.name));
|
||||||
}
|
}
|
||||||
@ -43,7 +44,7 @@ export default function (auth: IAuth): Router {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
profileRoute.post('/user', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
profileRoute.post('/user', limiter(config?.userRateLimit), function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||||
if (_.isNil(req.remote_user.name)) {
|
if (_.isNil(req.remote_user.name)) {
|
||||||
res.status(HTTP_STATUS.UNAUTHORIZED);
|
res.status(HTTP_STATUS.UNAUTHORIZED);
|
||||||
return next({
|
return next({
|
||||||
|
@ -9,7 +9,7 @@ import { stringToMD5 } from '../../../../lib/crypto-utils';
|
|||||||
import { logger } from '../../../../lib/logger';
|
import { logger } from '../../../../lib/logger';
|
||||||
|
|
||||||
import { $NextFunctionVer, $RequestExtend, IAuth, IStorageHandler } from '../../../../../types';
|
import { $NextFunctionVer, $RequestExtend, IAuth, IStorageHandler } from '../../../../../types';
|
||||||
import { limiter } from '../../../user-rate-limit';
|
import { limiter } from '../../../rate-limiter';
|
||||||
|
|
||||||
const debug = buildDebug('verdaccio:token');
|
const debug = buildDebug('verdaccio:token');
|
||||||
export type NormalizeToken = Token & {
|
export type NormalizeToken = Token & {
|
||||||
@ -26,8 +26,7 @@ function normalizeToken(token: Token): NormalizeToken {
|
|||||||
// https://github.com/npm/npm-profile/blob/latest/lib/index.js
|
// https://github.com/npm/npm-profile/blob/latest/lib/index.js
|
||||||
export default function (auth: IAuth, storage: IStorageHandler, config: Config): Router {
|
export default function (auth: IAuth, storage: IStorageHandler, config: Config): Router {
|
||||||
const tokenRoute = Router(); /* eslint new-cap: 0 */
|
const tokenRoute = Router(); /* eslint new-cap: 0 */
|
||||||
// tokenRoute.use(limiter);
|
tokenRoute.get('/tokens', limiter(config?.userRateLimit), async function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {
|
||||||
tokenRoute.get('/tokens', async function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {
|
|
||||||
const { name } = req.remote_user;
|
const { name } = req.remote_user;
|
||||||
|
|
||||||
if (_.isNil(name) === false) {
|
if (_.isNil(name) === false) {
|
||||||
@ -50,7 +49,7 @@ export default function (auth: IAuth, storage: IStorageHandler, config: Config):
|
|||||||
return next(ErrorCode.getUnauthorized());
|
return next(ErrorCode.getUnauthorized());
|
||||||
});
|
});
|
||||||
|
|
||||||
tokenRoute.post('/tokens', function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {
|
tokenRoute.post('/tokens', limiter(config?.userRateLimit), function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {
|
||||||
const { password, readonly, cidr_whitelist } = req.body;
|
const { password, readonly, cidr_whitelist } = req.body;
|
||||||
const { name } = req.remote_user;
|
const { name } = req.remote_user;
|
||||||
|
|
||||||
@ -110,7 +109,7 @@ export default function (auth: IAuth, storage: IStorageHandler, config: Config):
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
tokenRoute.delete('/tokens/token/:tokenKey', async (req: $RequestExtend, res: Response, next: $NextFunctionVer) => {
|
tokenRoute.delete('/tokens/token/:tokenKey', limiter(config?.userRateLimit), async (req: $RequestExtend, res: Response, next: $NextFunctionVer) => {
|
||||||
const {
|
const {
|
||||||
params: { tokenKey },
|
params: { tokenKey },
|
||||||
} = req;
|
} = req;
|
||||||
|
@ -49,7 +49,7 @@ export default function (config: Config, auth: IAuth, storage: IStorageHandler)
|
|||||||
ping(app);
|
ping(app);
|
||||||
stars(app, storage);
|
stars(app, storage);
|
||||||
v1Search(app, auth, storage);
|
v1Search(app, auth, storage);
|
||||||
app.use(npmV1(auth, storage, config));
|
|
||||||
user(app, auth, config);
|
user(app, auth, config);
|
||||||
|
app.use(npmV1(auth, storage, config));
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import { Config as IConfig, IPluginMiddleware, IPluginStorageFilter } from '@ver
|
|||||||
import Storage from '../lib/storage';
|
import Storage from '../lib/storage';
|
||||||
import loadPlugin from '../lib/plugin-loader';
|
import loadPlugin from '../lib/plugin-loader';
|
||||||
import Auth from '../lib/auth';
|
import Auth from '../lib/auth';
|
||||||
import { ErrorCode } from '../lib/utils';
|
import { ErrorCode, getUserAgent } from '../lib/utils';
|
||||||
import { API_ERROR, HTTP_STATUS } from '../lib/constants';
|
import { API_ERROR, HTTP_STATUS } from '../lib/constants';
|
||||||
import AppConfig from '../lib/config';
|
import AppConfig from '../lib/config';
|
||||||
import { $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler, IAuth } from '../../types';
|
import { $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler, IAuth } from '../../types';
|
||||||
@ -32,7 +32,7 @@ const defineAPI = function (config: IConfig, storage: IStorageHandler): any {
|
|||||||
app.use(errorReportingMiddleware);
|
app.use(errorReportingMiddleware);
|
||||||
if (config.user_agent) {
|
if (config.user_agent) {
|
||||||
app.use(function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
app.use(function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||||
res.setHeader('X-Powered-By', config.user_agent);
|
res.setHeader('X-Powered-By', getUserAgent(config.user_agent));
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
11
src/api/rate-limiter.ts
Normal file
11
src/api/rate-limiter.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import RateLimit from 'express-rate-limit';
|
||||||
|
import { RateLimit as RateLimitType } from '@verdaccio/types';
|
||||||
|
|
||||||
|
const limiter = (rateLimitOptions: RateLimitType) => {
|
||||||
|
// @ts-ignore
|
||||||
|
return new RateLimit({
|
||||||
|
...rateLimitOptions,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { limiter };
|
@ -1,11 +0,0 @@
|
|||||||
import RateLimit from 'express-rate-limit';
|
|
||||||
|
|
||||||
// we limit max 1000 request per 15 minutes on user endpoints
|
|
||||||
const defaultUserRateLimiting = {
|
|
||||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
||||||
max: 1000,
|
|
||||||
};
|
|
||||||
// @ts-ignore
|
|
||||||
const limiter = new RateLimit(defaultUserRateLimiting);
|
|
||||||
|
|
||||||
export { limiter };
|
|
@ -4,9 +4,7 @@ import { Config } from '@verdaccio/types';
|
|||||||
import Search from '../../lib/search';
|
import Search from '../../lib/search';
|
||||||
import { match, validateName, validatePackage, setSecurityWebHeaders } from '../middleware';
|
import { match, validateName, validatePackage, setSecurityWebHeaders } from '../middleware';
|
||||||
import { IAuth, IStorageHandler } from '../../../types';
|
import { IAuth, IStorageHandler } from '../../../types';
|
||||||
import addUserAuthApi from './endpoint/user';
|
import webApi from './endpoint';
|
||||||
import addPackageWebApi from './endpoint/package';
|
|
||||||
import addSearchWebApi from './endpoint/search';
|
|
||||||
|
|
||||||
const route = Router(); /* eslint new-cap: 0 */
|
const route = Router(); /* eslint new-cap: 0 */
|
||||||
|
|
||||||
@ -25,9 +23,6 @@ export default function (config: Config, auth: IAuth, storage: IStorageHandler):
|
|||||||
route.use(bodyParser.urlencoded({ extended: false }));
|
route.use(bodyParser.urlencoded({ extended: false }));
|
||||||
route.use(auth.webUIJWTmiddleware());
|
route.use(auth.webUIJWTmiddleware());
|
||||||
route.use(setSecurityWebHeaders);
|
route.use(setSecurityWebHeaders);
|
||||||
|
route.use(webApi(auth, storage, config));
|
||||||
addPackageWebApi(route, storage, auth, config);
|
|
||||||
addSearchWebApi(route, storage, auth);
|
|
||||||
addUserAuthApi(route, auth, config);
|
|
||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
|
22
src/api/web/endpoint/index.ts
Normal file
22
src/api/web/endpoint/index.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Response, Router } from 'express';
|
||||||
|
import { limiter } from '../../rate-limiter';
|
||||||
|
import packageApi from './package';
|
||||||
|
import search from './search';
|
||||||
|
import user from './user';
|
||||||
|
|
||||||
|
export default (auth, storage, config) => {
|
||||||
|
const route = Router(); /* eslint new-cap: 0 */
|
||||||
|
route.use(
|
||||||
|
'/data/',
|
||||||
|
limiter({
|
||||||
|
windowMs: 2 * 60 * 1000, // 2 minutes
|
||||||
|
max: 5000, // limit each IP to 1000 requests per windowMs
|
||||||
|
...config?.web?.rateLimit,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
route.use('/data/', packageApi(storage, auth, config));
|
||||||
|
route.use('/data/', search(storage, auth));
|
||||||
|
route.use('/sec/', limiter(config?.userRateLimit));
|
||||||
|
route.use('/sec/', user(auth, storage));
|
||||||
|
return route;
|
||||||
|
};
|
@ -25,8 +25,9 @@ const getOrder = (order = 'asc') => {
|
|||||||
|
|
||||||
export type PackcageExt = Package & { author: any; dist?: { tarball: string } };
|
export type PackcageExt = Package & { author: any; dist?: { tarball: string } };
|
||||||
|
|
||||||
function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth, config: Config): void {
|
function addPackageWebApi(storage: IStorageHandler, auth: IAuth, config: Config): Router {
|
||||||
const can = allow(auth);
|
const can = allow(auth);
|
||||||
|
const pkgRouter = Router(); /* eslint new-cap: 0 */
|
||||||
|
|
||||||
const checkAllow = (name, remoteUser): Promise<boolean> =>
|
const checkAllow = (name, remoteUser): Promise<boolean> =>
|
||||||
new Promise((resolve, reject): void => {
|
new Promise((resolve, reject): void => {
|
||||||
@ -43,7 +44,7 @@ function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth,
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Get list of all visible package
|
// Get list of all visible package
|
||||||
route.get('/packages', function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
pkgRouter.get('/packages', function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||||
storage.getLocalDatabase(async function (err, packages): Promise<void> {
|
storage.getLocalDatabase(async function (err, packages): Promise<void> {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -87,7 +88,7 @@ function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth,
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Get package readme
|
// Get package readme
|
||||||
route.get('/package/readme/(@:scope/)?:package/:version?', can('access'), function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
pkgRouter.get('/package/readme/(@:scope/)?:package/:version?', can('access'), function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||||
const packageName = req.params.scope ? addScope(req.params.scope, req.params.package) : req.params.package;
|
const packageName = req.params.scope ? addScope(req.params.scope, req.params.package) : req.params.package;
|
||||||
|
|
||||||
storage.getPackage({
|
storage.getPackage({
|
||||||
@ -101,13 +102,13 @@ function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth,
|
|||||||
|
|
||||||
res.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_PLAIN);
|
res.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_PLAIN);
|
||||||
const referer = req.get('Referer');
|
const referer = req.get('Referer');
|
||||||
const pathname = referer ? (new URL(referer)).pathname : undefined;
|
const pathname = referer ? new URL(referer).pathname : undefined;
|
||||||
next(parseReadme(info.name, info.readme, {pathname}));
|
next(parseReadme(info.name, info.readme, { pathname }));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
route.get('/sidebar/(@:scope/)?:package', can('access'), function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
pkgRouter.get('/sidebar/(@:scope/)?:package', can('access'), function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||||
const packageName: string = req.params.scope ? addScope(req.params.scope, req.params.package) : req.params.package;
|
const packageName: string = req.params.scope ? addScope(req.params.scope, req.params.package) : req.params.package;
|
||||||
|
|
||||||
storage.getPackage({
|
storage.getPackage({
|
||||||
@ -148,6 +149,8 @@ function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth,
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return pkgRouter;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default addPackageWebApi;
|
export default addPackageWebApi;
|
||||||
|
@ -7,58 +7,48 @@ import { Router } from 'express';
|
|||||||
import { Package } from '@verdaccio/types';
|
import { Package } from '@verdaccio/types';
|
||||||
import Search from '../../../lib/search';
|
import Search from '../../../lib/search';
|
||||||
import { DIST_TAGS } from '../../../lib/constants';
|
import { DIST_TAGS } from '../../../lib/constants';
|
||||||
import {
|
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '../../../../types';
|
||||||
IAuth,
|
|
||||||
$ResponseExtend,
|
|
||||||
$RequestExtend,
|
|
||||||
$NextFunctionVer,
|
|
||||||
IStorageHandler
|
|
||||||
} from '../../../../types';
|
|
||||||
|
|
||||||
function addSearchWebApi(route: Router, storage: IStorageHandler, auth: IAuth): void {
|
function addSearchWebApi(storage: IStorageHandler, auth: IAuth): Router {
|
||||||
|
const route = Router(); /* eslint new-cap: 0 */
|
||||||
// Search package
|
// Search package
|
||||||
route.get(
|
route.get('/search/:anything', function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||||
'/search/:anything',
|
const results: any = Search.query(req.params.anything);
|
||||||
function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
// FUTURE: figure out here the correct type
|
||||||
const results: any = Search.query(req.params.anything);
|
const packages: any[] = [];
|
||||||
// FUTURE: figure out here the correct type
|
|
||||||
const packages: any[] = [];
|
|
||||||
|
|
||||||
const getPackageInfo = function (i): void {
|
const getPackageInfo = function (i): void {
|
||||||
storage.getPackage({
|
storage.getPackage({
|
||||||
name: results[i].ref,
|
name: results[i].ref,
|
||||||
uplinksLook: false,
|
uplinksLook: false,
|
||||||
callback: (err, entry: Package): void => {
|
callback: (err, entry: Package): void => {
|
||||||
if (!err && entry) {
|
if (!err && entry) {
|
||||||
auth.allow_access(
|
auth.allow_access({ packageName: entry.name }, req.remote_user, function (err, allowed): void {
|
||||||
{ packageName: entry.name },
|
if (err || !allowed) {
|
||||||
req.remote_user,
|
return;
|
||||||
function (err, allowed): void {
|
}
|
||||||
if (err || !allowed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
packages.push(entry.versions[entry[DIST_TAGS].latest]);
|
packages.push(entry.versions[entry[DIST_TAGS].latest]);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i >= results.length - 1) {
|
|
||||||
next(packages);
|
|
||||||
} else {
|
|
||||||
getPackageInfo(i + 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (results.length) {
|
if (i >= results.length - 1) {
|
||||||
getPackageInfo(0);
|
next(packages);
|
||||||
} else {
|
} else {
|
||||||
next([]);
|
getPackageInfo(i + 1);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (results.length) {
|
||||||
|
getPackageInfo(0);
|
||||||
|
} else {
|
||||||
|
next([]);
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default addSearchWebApi;
|
export default addSearchWebApi;
|
||||||
|
@ -9,10 +9,9 @@ import { API_ERROR, APP_ERROR, HEADERS, HTTP_STATUS } from '../../../lib/constan
|
|||||||
import { IAuth, $NextFunctionVer } from '../../../../types';
|
import { IAuth, $NextFunctionVer } from '../../../../types';
|
||||||
import { ErrorCode } from '../../../lib/utils';
|
import { ErrorCode } from '../../../lib/utils';
|
||||||
import { getSecurity, validatePassword } from '../../../lib/auth-utils';
|
import { getSecurity, validatePassword } from '../../../lib/auth-utils';
|
||||||
import { limiter } from '../../user-rate-limit';
|
|
||||||
|
|
||||||
function addUserAuthApi(route: Router, auth: IAuth, config: Config): void {
|
function addUserAuthApi(auth: IAuth, config: Config): Router {
|
||||||
route.use(limiter);
|
const route = Router(); /* eslint new-cap: 0 */
|
||||||
route.post('/login', function (req: Request, res: Response, next: $NextFunctionVer): void {
|
route.post('/login', function (req: Request, res: Response, next: $NextFunctionVer): void {
|
||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
|
|
||||||
@ -58,6 +57,8 @@ function addUserAuthApi(route: Router, auth: IAuth, config: Config): void {
|
|||||||
return next(ErrorCode.getCode(HTTP_STATUS.BAD_REQUEST, APP_ERROR.PASSWORD_VALIDATION));
|
return next(ErrorCode.getCode(HTTP_STATUS.BAD_REQUEST, APP_ERROR.PASSWORD_VALIDATION));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default addUserAuthApi;
|
export default addUserAuthApi;
|
||||||
|
@ -4,7 +4,8 @@ import _ from 'lodash';
|
|||||||
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import buildDebug from 'debug';
|
import buildDebug from 'debug';
|
||||||
import RateLimit from 'express-rate-limit';
|
|
||||||
|
import { Config } from '@verdaccio/types';
|
||||||
|
|
||||||
import Search from '../../lib/search';
|
import Search from '../../lib/search';
|
||||||
import { HTTP_STATUS } from '../../lib/constants';
|
import { HTTP_STATUS } from '../../lib/constants';
|
||||||
@ -54,23 +55,15 @@ const sendFileCallback = (next) => (err) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function (config, auth, storage) {
|
export default function (config: Config, auth, storage) {
|
||||||
let { staticPath, manifest, manifestFiles } = loadTheme(config) || require('@verdaccio/ui-theme')();
|
let { staticPath, manifest, manifestFiles } = loadTheme(config) || require('@verdaccio/ui-theme')();
|
||||||
debug('static path %o', staticPath);
|
debug('static path %o', staticPath);
|
||||||
Search.configureStorage(storage);
|
Search.configureStorage(storage);
|
||||||
|
|
||||||
/* eslint new-cap:off */
|
/* eslint new-cap:off */
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
// limit 5k request on web peer 2 minutes is enough for a medium size company
|
|
||||||
// @ts-ignore
|
|
||||||
const limiter = new RateLimit({
|
|
||||||
windowMs: 2 * 60 * 1000, // 2 minutes
|
|
||||||
max: 5000, // limit each IP to 1000 requests per windowMs
|
|
||||||
...config?.web?.rateLimit,
|
|
||||||
});
|
|
||||||
// run in production mode by default, just in case
|
// run in production mode by default, just in case
|
||||||
// it shouldn't make any difference anyway
|
// it shouldn't make any difference anyway
|
||||||
router.use(limiter);
|
|
||||||
router.use(auth.webUIJWTmiddleware());
|
router.use(auth.webUIJWTmiddleware());
|
||||||
router.use(setSecurityWebHeaders);
|
router.use(setSecurityWebHeaders);
|
||||||
|
|
||||||
@ -93,6 +86,7 @@ export default function (config, auth, storage) {
|
|||||||
// Use POSIX version `path.posix.join` instead.
|
// Use POSIX version `path.posix.join` instead.
|
||||||
config.web.logo = path.posix.join('/-/static/', path.basename(config.web.logo));
|
config.web.logo = path.posix.join('/-/static/', path.basename(config.web.logo));
|
||||||
router.get(config.web.logo, function (_req, res, next) {
|
router.get(config.web.logo, function (_req, res, next) {
|
||||||
|
// @ts-ignore
|
||||||
debug('serve custom logo web:%s - local:%s', config.web.logo, absoluteLocalFile);
|
debug('serve custom logo web:%s - local:%s', config.web.logo, absoluteLocalFile);
|
||||||
res.sendFile(absoluteLocalFile, sendFileCallback(next));
|
res.sendFile(absoluteLocalFile, sendFileCallback(next));
|
||||||
});
|
});
|
||||||
|
@ -125,6 +125,12 @@ const defaultApiTokenConf: APITokenOptions = {
|
|||||||
legacy: true,
|
legacy: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// we limit max 1000 request per 15 minutes on user endpoints
|
||||||
|
export const defaultUserRateLimiting = {
|
||||||
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||||
|
max: 1000,
|
||||||
|
};
|
||||||
|
|
||||||
export const defaultSecurity: Security = {
|
export const defaultSecurity: Security = {
|
||||||
web: defaultWebTokenOptions,
|
web: defaultWebTokenOptions,
|
||||||
api: defaultApiTokenConf,
|
api: defaultApiTokenConf,
|
||||||
@ -154,7 +160,6 @@ export function isAESLegacy(security: Security): boolean {
|
|||||||
|
|
||||||
export async function getApiToken(auth: IAuthWebUI, config: Config, remoteUser: RemoteUser, aesPassword: string): Promise<string> {
|
export async function getApiToken(auth: IAuthWebUI, config: Config, remoteUser: RemoteUser, aesPassword: string): Promise<string> {
|
||||||
const security: Security = getSecurity(config);
|
const security: Security = getSecurity(config);
|
||||||
|
|
||||||
if (isAESLegacy(security)) {
|
if (isAESLegacy(security)) {
|
||||||
// fallback all goes to AES encryption
|
// fallback all goes to AES encryption
|
||||||
return await new Promise((resolve): void => {
|
return await new Promise((resolve): void => {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { PackageList, Config as AppConfig, Security, Logger } from '@verdaccio/types';
|
import { PackageList, Config as AppConfig, Security, Logger, RateLimit } from '@verdaccio/types';
|
||||||
import { MatchedPackage, StartUpConfig } from '../../types';
|
import { MatchedPackage, StartUpConfig } from '../../types';
|
||||||
import { generateRandomHexString } from './crypto-utils';
|
import { generateRandomHexString } from './crypto-utils';
|
||||||
import { getMatchedPackagesSpec, normalisePackageAccess, sanityCheckUplinksProps, uplinkSanityCheck } from './config-utils';
|
import { getMatchedPackagesSpec, normalisePackageAccess, sanityCheckUplinksProps, uplinkSanityCheck } from './config-utils';
|
||||||
import { getUserAgent, isObject } from './utils';
|
import { getUserAgent, isObject } from './utils';
|
||||||
import { APP_ERROR } from './constants';
|
import { APP_ERROR } from './constants';
|
||||||
|
import { defaultUserRateLimiting } from './auth-utils';
|
||||||
|
|
||||||
const LoggerApi = require('./logger');
|
const LoggerApi = require('./logger');
|
||||||
const strategicConfigProps = ['uplinks', 'packages'];
|
const strategicConfigProps = ['uplinks', 'packages'];
|
||||||
@ -18,12 +19,13 @@ const allowedEnvConfig = ['http_proxy', 'https_proxy', 'no_proxy'];
|
|||||||
class Config implements AppConfig {
|
class Config implements AppConfig {
|
||||||
public logger: Logger;
|
public logger: Logger;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
public user_agent: string;
|
public user_agent: boolean | string;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
public secret: string;
|
public secret: string;
|
||||||
public uplinks: any;
|
public uplinks: any;
|
||||||
public packages: PackageList;
|
public packages: PackageList;
|
||||||
public users: any;
|
public users: any;
|
||||||
|
public userRateLimit: RateLimit;
|
||||||
public server_id: string;
|
public server_id: string;
|
||||||
public self_path: string;
|
public self_path: string;
|
||||||
public storage: string | void;
|
public storage: string | void;
|
||||||
@ -44,11 +46,12 @@ class Config implements AppConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
if (config?.user_agent) {
|
if (config?.user_agent) {
|
||||||
this.user_agent = getUserAgent();
|
this.user_agent = getUserAgent(config?.user_agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.userRateLimit = { ...defaultUserRateLimiting, ...config?.userRateLimit };
|
||||||
|
|
||||||
// some weird shell scripts are valid yaml files parsed as string
|
// some weird shell scripts are valid yaml files parsed as string
|
||||||
assert(_.isObject(config), APP_ERROR.CONFIG_NOT_VALID);
|
assert(_.isObject(config), APP_ERROR.CONFIG_NOT_VALID);
|
||||||
|
|
||||||
|
@ -63,6 +63,7 @@ class ProxyStorage implements IProxy {
|
|||||||
public constructor(config: UpLinkConfLocal, mainConfig: Config) {
|
public constructor(config: UpLinkConfLocal, mainConfig: Config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.failed_requests = 0;
|
this.failed_requests = 0;
|
||||||
|
// @ts-ignore
|
||||||
this.userAgent = mainConfig.user_agent;
|
this.userAgent = mainConfig.user_agent;
|
||||||
this.ca = config.ca;
|
this.ca = config.ca;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
@ -30,9 +30,17 @@ const pkgVersion = module.exports.version;
|
|||||||
const pkgName = module.exports.name;
|
const pkgName = module.exports.name;
|
||||||
const validProtocols = ['https', 'http'];
|
const validProtocols = ['https', 'http'];
|
||||||
|
|
||||||
export function getUserAgent(): string {
|
export function getUserAgent(customUserAgent?: boolean | string): string {
|
||||||
assert(_.isString(pkgName));
|
assert(_.isString(pkgName));
|
||||||
assert(_.isString(pkgVersion));
|
assert(_.isString(pkgVersion));
|
||||||
|
if (customUserAgent === true) {
|
||||||
|
return `${pkgName}/${pkgVersion}`;
|
||||||
|
} else if (_.isString(customUserAgent) && _.isEmpty(customUserAgent) === false) {
|
||||||
|
return customUserAgent;
|
||||||
|
} else if (customUserAgent === false) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
return `${pkgName}/${pkgVersion}`;
|
return `${pkgName}/${pkgVersion}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,21 +465,19 @@ export function addGravatarSupport(pkgInfo: Package, online = true): AuthorAvata
|
|||||||
|
|
||||||
// for contributors
|
// for contributors
|
||||||
if (_.isEmpty(contributors) === false) {
|
if (_.isEmpty(contributors) === false) {
|
||||||
pkgInfoCopy.latest.contributors = contributors.map(
|
pkgInfoCopy.latest.contributors = contributors.map((contributor): AuthorAvatar => {
|
||||||
(contributor): AuthorAvatar => {
|
if (isObject(contributor)) {
|
||||||
if (isObject(contributor)) {
|
contributor.avatar = generateGravatarUrl(contributor.email, online);
|
||||||
contributor.avatar = generateGravatarUrl(contributor.email, online);
|
} else if (_.isString(contributor)) {
|
||||||
} else if (_.isString(contributor)) {
|
contributor = {
|
||||||
contributor = {
|
avatar: GENERIC_AVATAR,
|
||||||
avatar: GENERIC_AVATAR,
|
email: contributor,
|
||||||
email: contributor,
|
name: contributor,
|
||||||
name: contributor,
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return contributor;
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
return contributor;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// for maintainers
|
// for maintainers
|
||||||
@ -492,9 +498,7 @@ export function addGravatarSupport(pkgInfo: Package, online = true): AuthorAvata
|
|||||||
* @param {Object} options sanitizyReadme options
|
* @param {Object} options sanitizyReadme options
|
||||||
* @return {String} converted html template
|
* @return {String} converted html template
|
||||||
*/
|
*/
|
||||||
export function parseReadme(packageName: string,
|
export function parseReadme(packageName: string, readme: string, options: { pathname?: string | void } = {}): string | void {
|
||||||
readme: string,
|
|
||||||
options: { pathname?: string | void } = {}): string | void {
|
|
||||||
if (_.isEmpty(readme) === false) {
|
if (_.isEmpty(readme) === false) {
|
||||||
return sanitizyReadme(readme, options);
|
return sanitizyReadme(readme, options);
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,13 @@ describe('/ (Verdaccio Page)', () => {
|
|||||||
// this might be increased based on the delays included in all test
|
// this might be increased based on the delays included in all test
|
||||||
jest.setTimeout(20000);
|
jest.setTimeout(20000);
|
||||||
|
|
||||||
const clickElement = async function(selector, options = { delay: 100 }) {
|
const clickElement = async function (selector, options = { delay: 100 }) {
|
||||||
const button = await page.$(selector);
|
const button = await page.$(selector);
|
||||||
await button.focus();
|
await button.focus();
|
||||||
await button.click(options);
|
await button.click(options);
|
||||||
};
|
};
|
||||||
|
|
||||||
const evaluateSignIn = async function(matchText = 'Login') {
|
const evaluateSignIn = async function (matchText = 'Login') {
|
||||||
const text = await page.evaluate(() => {
|
const text = await page.evaluate(() => {
|
||||||
return document.querySelector('button[data-testid="header--button-login"]').textContent;
|
return document.querySelector('button[data-testid="header--button-login"]').textContent;
|
||||||
});
|
});
|
||||||
@ -20,11 +20,11 @@ describe('/ (Verdaccio Page)', () => {
|
|||||||
expect(text).toMatch(matchText);
|
expect(text).toMatch(matchText);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPackages = async function() {
|
const getPackages = async function () {
|
||||||
return await page.$$('.package-title');
|
return await page.$$('.package-title');
|
||||||
};
|
};
|
||||||
|
|
||||||
const logIn = async function() {
|
const logIn = async function () {
|
||||||
await clickElement('button[data-testid="header--button-login"]');
|
await clickElement('button[data-testid="header--button-login"]');
|
||||||
// we fill the sign in form
|
// we fill the sign in form
|
||||||
const signInDialog = await page.$('#login--dialog');
|
const signInDialog = await page.$('#login--dialog');
|
||||||
@ -47,7 +47,7 @@ describe('/ (Verdaccio Page)', () => {
|
|||||||
page = await global.__BROWSER__.newPage();
|
page = await global.__BROWSER__.newPage();
|
||||||
await page.goto('http://0.0.0.0:55558');
|
await page.goto('http://0.0.0.0:55558');
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
page.on('console', msg => console.log('PAGE LOG:', msg.text()));
|
page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@ -162,7 +162,7 @@ describe('/ (Verdaccio Page)', () => {
|
|||||||
await page.waitFor(1000);
|
await page.waitFor(1000);
|
||||||
const tags = await page.$$('.dep-tag');
|
const tags = await page.$$('.dep-tag');
|
||||||
const tag = tags[0];
|
const tag = tags[0];
|
||||||
const label = await page.evaluate(el => el.innerText, tag);
|
const label = await page.evaluate((el) => el.innerText, tag);
|
||||||
expect(label).toMatch('verdaccio@');
|
expect(label).toMatch('verdaccio@');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ const mkdirp = require('mkdirp');
|
|||||||
|
|
||||||
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
|
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
|
||||||
|
|
||||||
module.exports = async function() {
|
module.exports = async function () {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(green('Setup Puppeteer'));
|
console.log(green('Setup Puppeteer'));
|
||||||
const browser = await puppeteer.launch({ headless: true, /* slowMo: 300 */ args: ['--no-sandbox'] });
|
const browser = await puppeteer.launch({ headless: true, /* slowMo: 300 */ args: ['--no-sandbox'] });
|
||||||
|
@ -51,7 +51,6 @@ export class PromiseAssert extends Promise<any> implements IRequestPromise {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public body_error(expected: any) {
|
public body_error(expected: any) {
|
||||||
// $FlowFixMe
|
|
||||||
const selfData = this[requestData];
|
const selfData = this[requestData];
|
||||||
|
|
||||||
return injectResponse(
|
return injectResponse(
|
||||||
@ -97,7 +96,6 @@ export class PromiseAssert extends Promise<any> implements IRequestPromise {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function injectResponse(smartObject: any, promise: Promise<any>): Promise<any> {
|
function injectResponse(smartObject: any, promise: Promise<any>): Promise<any> {
|
||||||
// $FlowFixMe
|
|
||||||
promise[requestData] = smartObject[requestData];
|
promise[requestData] = smartObject[requestData];
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import { mockServer } from '../../__helper/mock';
|
|||||||
import { DOMAIN_SERVERS } from '../../../functional/config.functional';
|
import { DOMAIN_SERVERS } from '../../../functional/config.functional';
|
||||||
import { getNewToken } from '../../__helper/api';
|
import { getNewToken } from '../../__helper/api';
|
||||||
import { buildToken } from '../../../../src/lib/utils';
|
import { buildToken } from '../../../../src/lib/utils';
|
||||||
import { expectJson } from '../../../../src/api/middleware';
|
|
||||||
|
|
||||||
require('../../../../src/lib/logger').setup([{ type: 'stdout', format: 'pretty', level: 'trace' }]);
|
require('../../../../src/lib/logger').setup([{ type: 'stdout', format: 'pretty', level: 'trace' }]);
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ describe('endpoint web unit test', () => {
|
|||||||
describe('Packages', () => {
|
describe('Packages', () => {
|
||||||
test('should display all packages', (done) => {
|
test('should display all packages', (done) => {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/-/verdaccio/packages')
|
.get('/-/verdaccio/data/packages')
|
||||||
.expect(HTTP_STATUS.OK)
|
.expect(HTTP_STATUS.OK)
|
||||||
.end(function (err, res) {
|
.end(function (err, res) {
|
||||||
expect(res.body).toHaveLength(1);
|
expect(res.body).toHaveLength(1);
|
||||||
@ -72,7 +72,7 @@ describe('endpoint web unit test', () => {
|
|||||||
|
|
||||||
test.skip('should display scoped readme', (done) => {
|
test.skip('should display scoped readme', (done) => {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/-/verdaccio/package/readme/@scope/pk1-test')
|
.get('/-/verdaccio/data/package/readme/@scope/pk1-test')
|
||||||
.expect(HTTP_STATUS.OK)
|
.expect(HTTP_STATUS.OK)
|
||||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_CHARSET)
|
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_CHARSET)
|
||||||
.end(function (err, res) {
|
.end(function (err, res) {
|
||||||
@ -84,7 +84,7 @@ describe('endpoint web unit test', () => {
|
|||||||
// FIXME: disabled, we need to inspect why fails randomly
|
// FIXME: disabled, we need to inspect why fails randomly
|
||||||
test.skip('should display scoped readme 404', (done) => {
|
test.skip('should display scoped readme 404', (done) => {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/-/verdaccio/package/readme/@scope/404')
|
.get('/-/verdaccio/data/package/readme/@scope/404')
|
||||||
.expect(HTTP_STATUS.OK)
|
.expect(HTTP_STATUS.OK)
|
||||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_CHARSET)
|
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_CHARSET)
|
||||||
.end(function (err, res) {
|
.end(function (err, res) {
|
||||||
@ -95,7 +95,7 @@ describe('endpoint web unit test', () => {
|
|||||||
|
|
||||||
test('should display sidebar info', (done) => {
|
test('should display sidebar info', (done) => {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/-/verdaccio/sidebar/@scope/pk1-test')
|
.get('/-/verdaccio/data/sidebar/@scope/pk1-test')
|
||||||
.expect(HTTP_STATUS.OK)
|
.expect(HTTP_STATUS.OK)
|
||||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||||
.end(function (err, res) {
|
.end(function (err, res) {
|
||||||
@ -112,7 +112,7 @@ describe('endpoint web unit test', () => {
|
|||||||
|
|
||||||
test('should display sidebar info by version', (done) => {
|
test('should display sidebar info by version', (done) => {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/-/verdaccio/sidebar/@scope/pk1-test?v=1.0.6')
|
.get('/-/verdaccio/data/sidebar/@scope/pk1-test?v=1.0.6')
|
||||||
.expect(HTTP_STATUS.OK)
|
.expect(HTTP_STATUS.OK)
|
||||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||||
.end(function (err, res) {
|
.end(function (err, res) {
|
||||||
@ -129,7 +129,7 @@ describe('endpoint web unit test', () => {
|
|||||||
|
|
||||||
test('should display sidebar info 404', (done) => {
|
test('should display sidebar info 404', (done) => {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/-/verdaccio/sidebar/@scope/404')
|
.get('/-/verdaccio/data/sidebar/@scope/404')
|
||||||
.expect(HTTP_STATUS.NOT_FOUND)
|
.expect(HTTP_STATUS.NOT_FOUND)
|
||||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||||
.end(function () {
|
.end(function () {
|
||||||
@ -139,7 +139,7 @@ describe('endpoint web unit test', () => {
|
|||||||
|
|
||||||
test('should display sidebar info 404 with version', (done) => {
|
test('should display sidebar info 404 with version', (done) => {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/-/verdaccio/sidebar/@scope/pk1-test?v=0.0.0-not-found')
|
.get('/-/verdaccio/data/sidebar/@scope/pk1-test?v=0.0.0-not-found')
|
||||||
.expect(HTTP_STATUS.NOT_FOUND)
|
.expect(HTTP_STATUS.NOT_FOUND)
|
||||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||||
.end(function () {
|
.end(function () {
|
||||||
@ -151,7 +151,7 @@ describe('endpoint web unit test', () => {
|
|||||||
describe('Search', () => {
|
describe('Search', () => {
|
||||||
test('should search pk1-test', (done) => {
|
test('should search pk1-test', (done) => {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/-/verdaccio/search/scope')
|
.get('/-/verdaccio/data/search/scope')
|
||||||
.expect(HTTP_STATUS.OK)
|
.expect(HTTP_STATUS.OK)
|
||||||
.end(function (err, res) {
|
.end(function (err, res) {
|
||||||
expect(res.body).toHaveLength(1);
|
expect(res.body).toHaveLength(1);
|
||||||
@ -161,7 +161,7 @@ describe('endpoint web unit test', () => {
|
|||||||
|
|
||||||
test('should search with 404', (done) => {
|
test('should search with 404', (done) => {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/-/verdaccio/search/@')
|
.get('/-/verdaccio/data/search/@')
|
||||||
.expect(HTTP_STATUS.OK)
|
.expect(HTTP_STATUS.OK)
|
||||||
.end(function (err, res) {
|
.end(function (err, res) {
|
||||||
// in a normal world, the output would be 1
|
// in a normal world, the output would be 1
|
||||||
@ -173,7 +173,7 @@ describe('endpoint web unit test', () => {
|
|||||||
|
|
||||||
test('should not find forbidden-place', (done) => {
|
test('should not find forbidden-place', (done) => {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/-/verdaccio/search/forbidden-place')
|
.get('/-/verdaccio/data/search/forbidden-place')
|
||||||
.expect(HTTP_STATUS.OK)
|
.expect(HTTP_STATUS.OK)
|
||||||
.end(function (err, res) {
|
.end(function (err, res) {
|
||||||
// this is expected since we are not logged
|
// this is expected since we are not logged
|
||||||
@ -192,7 +192,7 @@ describe('endpoint web unit test', () => {
|
|||||||
describe('login webui', () => {
|
describe('login webui', () => {
|
||||||
test('should log successfully', (done) => {
|
test('should log successfully', (done) => {
|
||||||
request(app)
|
request(app)
|
||||||
.post('/-/verdaccio/login')
|
.post('/-/verdaccio/sec/login')
|
||||||
.send({
|
.send({
|
||||||
username: credentials.name,
|
username: credentials.name,
|
||||||
password: credentials.password,
|
password: credentials.password,
|
||||||
@ -210,7 +210,7 @@ describe('endpoint web unit test', () => {
|
|||||||
|
|
||||||
test('should fails on log unvalid user', (done) => {
|
test('should fails on log unvalid user', (done) => {
|
||||||
request(app)
|
request(app)
|
||||||
.post('/-/verdaccio/login')
|
.post('/-/verdaccio/sec/login')
|
||||||
.send({
|
.send({
|
||||||
username: 'fake',
|
username: 'fake',
|
||||||
password: 'fake',
|
password: 'fake',
|
||||||
|
@ -17,13 +17,14 @@ import {
|
|||||||
Package,
|
Package,
|
||||||
IPluginStorageFilter,
|
IPluginStorageFilter,
|
||||||
Author,
|
Author,
|
||||||
AuthPluginPackage,
|
AuthPluginPackage,
|
||||||
Token,
|
Token,
|
||||||
ITokenActions,
|
ITokenActions,
|
||||||
TokenFilter
|
TokenFilter,
|
||||||
|
RateLimit,
|
||||||
} from '@verdaccio/types';
|
} from '@verdaccio/types';
|
||||||
import lunrMutable from 'lunr-mutable-indexes';
|
import lunrMutable from 'lunr-mutable-indexes';
|
||||||
import {NextFunction, Request, Response} from 'express';
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
|
||||||
export type StringValue = verdaccio$StringValue;
|
export type StringValue = verdaccio$StringValue;
|
||||||
|
|
||||||
@ -31,6 +32,8 @@ export interface StartUpConfig {
|
|||||||
storage: string;
|
storage: string;
|
||||||
plugins?: string;
|
plugins?: string;
|
||||||
self_path: string;
|
self_path: string;
|
||||||
|
user_agent?: boolean;
|
||||||
|
userRateLimit?: RateLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// legacy should be removed in long term
|
// legacy should be removed in long term
|
||||||
@ -46,13 +49,13 @@ export type LegacyPackageAccess = PackageAccess & {
|
|||||||
proxy_access?: string[];
|
proxy_access?: string[];
|
||||||
// FIXME: should be published on @verdaccio/types
|
// FIXME: should be published on @verdaccio/types
|
||||||
unpublish?: string[];
|
unpublish?: string[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type MatchedPackage = PackageAccess | void;
|
export type MatchedPackage = PackageAccess | void;
|
||||||
|
|
||||||
export type JWTPayload = RemoteUser & {
|
export type JWTPayload = RemoteUser & {
|
||||||
password?: string;
|
password?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface AESPayload {
|
export interface AESPayload {
|
||||||
user: string;
|
user: string;
|
||||||
@ -96,10 +99,10 @@ export interface Profile {
|
|||||||
fullname: string;
|
fullname: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type $RequestExtend = Request & {remote_user?: any; log: Logger}
|
export type $RequestExtend = Request & { remote_user?: any; log: Logger };
|
||||||
export type $ResponseExtend = Response & {cookies?: any}
|
export type $ResponseExtend = Response & { cookies?: any };
|
||||||
export type $NextFunctionVer = NextFunction & any;
|
export type $NextFunctionVer = NextFunction & any;
|
||||||
export type $SidebarPackage = Package & {latest: any}
|
export type $SidebarPackage = Package & { latest: any };
|
||||||
|
|
||||||
export interface IAuthWebUI {
|
export interface IAuthWebUI {
|
||||||
jwtEncrypt(user: RemoteUser, signOptions: JWTSignOptions): Promise<string>;
|
jwtEncrypt(user: RemoteUser, signOptions: JWTSignOptions): Promise<string>;
|
||||||
@ -198,4 +201,3 @@ export interface Styles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type AuthorAvatar = Author & { avatar?: string };
|
export type AuthorAvatar = Author & { avatar?: string };
|
||||||
|
|
||||||
|
BIN
yarn.lock
BIN
yarn.lock
Binary file not shown.
Loading…
Reference in New Issue
Block a user