0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-03-11 02:15:57 -05:00

chore: move improvements from v5 to v6 (#3461)

* chore: migrate #3158 to v6

* chore: migrate #3151 to v6k

* chore: migrate #2787 to v6

* chore: migrate #2791 #2205 to v6

* chore: add changeset
This commit is contained in:
Juan Picado 2022-10-28 23:38:22 +02:00 committed by GitHub
parent 88a419a966
commit 4b29d715b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 176 additions and 40 deletions

View file

@ -0,0 +1,21 @@
---
'@verdaccio/api': minor
'@verdaccio/auth': minor
'@verdaccio/config': minor
'@verdaccio/core': minor
'@verdaccio/url': minor
'@verdaccio/middleware': minor
'@verdaccio/local-storage': minor
'@verdaccio/web': minor
---
chore: move improvements from v5 to v6
Migrate improvements form v5 to v6:
- https://github.com/verdaccio/verdaccio/pull/3158
- https://github.com/verdaccio/verdaccio/pull/3151
- https://github.com/verdaccio/verdaccio/pull/2271
- https://github.com/verdaccio/verdaccio/pull/2787
- https://github.com/verdaccio/verdaccio/pull/2791
- https://github.com/verdaccio/verdaccio/pull/2205

View file

@ -4,7 +4,14 @@ import { Response, Router } from 'express';
import { getApiToken } from '@verdaccio/auth';
import { Auth } from '@verdaccio/auth';
import { createRemoteUser } from '@verdaccio/config';
import { API_ERROR, API_MESSAGE, HTTP_STATUS, errorUtils, validatioUtils } from '@verdaccio/core';
import {
API_ERROR,
API_MESSAGE,
HEADERS,
HTTP_STATUS,
errorUtils,
validatioUtils,
} from '@verdaccio/core';
import { logger } from '@verdaccio/logger';
import { Config, RemoteUser } from '@verdaccio/types';
import { getAuthenticatedMessage, mask } from '@verdaccio/utils';
@ -75,6 +82,7 @@ export default function (route: Router, auth: Auth, config: Config): void {
}
res.status(HTTP_STATUS.CREATED);
res.set(HEADERS.CACHE_CONTROL, 'no-cache, no-store');
const message = getAuthenticatedMessage(req.remote_user.name);
debug('login: created user message %o', message);
@ -124,6 +132,7 @@ export default function (route: Router, auth: Auth, config: Config): void {
req.remote_user = user;
res.status(HTTP_STATUS.CREATED);
res.set(HEADERS.CACHE_CONTROL, 'no-cache, no-store');
debug('adduser: user has been created');
return next({
ok: `user '${req.body.name}' created`,

View file

@ -3,7 +3,7 @@ import _ from 'lodash';
import { getApiToken } from '@verdaccio/auth';
import { Auth } from '@verdaccio/auth';
import { HTTP_STATUS, SUPPORT_ERRORS, errorUtils } from '@verdaccio/core';
import { HEADERS, HTTP_STATUS, SUPPORT_ERRORS, errorUtils } from '@verdaccio/core';
import { logger } from '@verdaccio/logger';
import { Storage } from '@verdaccio/store';
import { Config, RemoteUser, Token } from '@verdaccio/types';
@ -102,6 +102,7 @@ export default function (route: Router, auth: Auth, storage: Storage, config: Co
await storage.saveToken(saveToken);
logger.debug({ key, name }, 'token @{key} was created for user @{name}');
res.set(HEADERS.CACHE_CONTROL, 'no-cache, no-store');
return next(
normalizeToken({
token,

View file

@ -39,6 +39,7 @@ export function createUser(app, name: string, password: string): supertest.Test
password: password,
})
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HEADERS.CACHE_CONTROL, 'no-cache, no-store')
.expect(HTTP_STATUS.CREATED);
}

View file

@ -574,7 +574,9 @@ class Auth implements IAuthMiddleware, TokenEncryption, pluginUtils.IBasicAuth {
const { real_groups, name, groups } = user;
debug('jwt encrypt %o', name);
const realGroupsValidated = _.isNil(real_groups) ? [] : real_groups;
const groupedGroups = _.isNil(groups) ? real_groups : groups.concat(realGroupsValidated);
const groupedGroups = _.isNil(groups)
? real_groups
: Array.from(new Set([...groups.concat(realGroupsValidated)]));
const payload: RemoteUser = {
real_groups: realGroupsValidated,
name,

View file

@ -77,8 +77,8 @@ describe('Auth utilities', () => {
const spyNotCalled = jest.spyOn(auth, methodNotBeenCalled);
const user: RemoteUser = {
name: username,
real_groups: [],
groups: [],
real_groups: ['test', '$all', '$authenticated', '@all', '@authenticated', 'all'],
groups: ['company-role1', 'company-role2'],
};
const token = await getApiToken(auth, config, user, password);
expect(spy).toHaveBeenCalled();
@ -93,7 +93,25 @@ describe('Auth utilities', () => {
const payload = verifyPayload(token, secret);
expect(payload.name).toBe(user);
expect(payload.groups).toBeDefined();
expect(payload.groups).toEqual([
'company-role1',
'company-role2',
'test',
'$all',
'$authenticated',
'@all',
'@authenticated',
'all',
]);
expect(payload.real_groups).toBeDefined();
expect(payload.real_groups).toEqual([
'test',
'$all',
'$authenticated',
'@all',
'@authenticated',
'all',
]);
};
const verifyAES = (token: string, user: string, password: string, secret: string) => {
@ -219,6 +237,30 @@ describe('Auth utilities', () => {
});
});
describe('createRemoteUser', () => {
test('create remote user', () => {
expect(createRemoteUser('test', [])).toEqual({
name: 'test',
real_groups: [],
groups: ['$all', '$authenticated', '@all', '@authenticated', 'all'],
});
});
test('create remote user with groups', () => {
expect(createRemoteUser('test', ['group1', 'group2'])).toEqual({
name: 'test',
real_groups: ['group1', 'group2'],
groups: ['group1', 'group2', '$all', '$authenticated', '@all', '@authenticated', 'all'],
});
});
test('create anonymous remote user', () => {
expect(createAnonymousRemoteUser()).toEqual({
name: undefined,
real_groups: [],
groups: ['$all', '$anonymous', '@all', '@anonymous'],
});
});
});
describe('getApiToken test', () => {
test('should sign token with aes and security missing', async () => {
const token = await getTokenByConfiguration(
@ -445,15 +487,13 @@ describe('Auth utilities', () => {
security,
'12345',
buildToken(TOKEN_BEARER, 'fakeToken')
);
) as RemoteUser;
expect(credentials).toBeDefined();
// @ts-ignore
expect(credentials.name).not.toBeDefined();
// @ts-ignore
expect(credentials.real_groups).toBeDefined();
// @ts-ignore
expect(credentials.real_groups).toEqual([]);
expect(credentials.groups).toEqual(['$all', '$anonymous', '@all', '@anonymous']);
});
test('should return anonymous whether token and scheme are corrupted', () => {
@ -485,14 +525,29 @@ describe('Auth utilities', () => {
security,
secret,
buildToken(TOKEN_BEARER, token)
);
) as RemoteUser;
expect(credentials).toBeDefined();
// @ts-ignore
expect(credentials.name).toEqual(user);
// @ts-ignore
expect(credentials.real_groups).toBeDefined();
// @ts-ignore
expect(credentials.real_groups).toEqual([]);
expect(credentials.real_groups).toEqual([
'test',
'$all',
'$authenticated',
'@all',
'@authenticated',
'all',
]);
expect(credentials.groups).toEqual([
'company-role1',
'company-role2',
'test',
'$all',
'$authenticated',
'@all',
'@authenticated',
'all',
]);
});
});
});

View file

@ -1,11 +1,12 @@
import { APITokenOptions, JWTOptions, Security } from '@verdaccio/types';
export const TIME_EXPIRATION_7D = '7d';
// TODO: get this from core package
export const TIME_EXPIRATION_1H = '1h';
const defaultWebTokenOptions: JWTOptions = {
sign: {
// The expiration token for the website is 7 days
expiresIn: TIME_EXPIRATION_7D,
expiresIn: TIME_EXPIRATION_1H,
},
verify: {},
};

View file

@ -29,7 +29,9 @@ export const defaultNonLoggedUserRoles = [
*/
export function createRemoteUser(name: string, pluginGroups: string[]): RemoteUser {
const isGroupValid: boolean = Array.isArray(pluginGroups);
const groups = (isGroupValid ? pluginGroups : []).concat([...defaultLoggedUserRoles]);
const groups = Array.from(
new Set((isGroupValid ? pluginGroups : []).concat([...defaultLoggedUserRoles]))
);
return {
name,

View file

@ -2,7 +2,7 @@ import httpCodes from 'http-status-codes';
export const DEFAULT_PASSWORD_VALIDATION = /.{3}$/;
export const TIME_EXPIRATION_24H = '24h';
export const TIME_EXPIRATION_7D = '7d';
export const TIME_EXPIRATION_1H = '1h';
export const DIST_TAGS = 'dist-tags';
export const LATEST = 'latest';
export const USERS = 'users';
@ -35,6 +35,7 @@ export const HEADERS = {
TEXT_HTML_UTF8: 'text/html; charset=utf-8',
TEXT_HTML: 'text/html',
AUTHORIZATION: 'authorization',
CACHE_CONTROL: 'Cache-Control',
// only set with proxy that setup HTTPS
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
FORWARDED_PROTO: 'X-Forwarded-Proto',

View file

@ -295,6 +295,27 @@ describe('env variable', () => {
delete process.env.VERDACCIO_PUBLIC_URL;
});
test('with the VERDACCIO_FORWARDED_PROTO undefined', () => {
process.env.VERDACCIO_FORWARDED_PROTO = undefined;
const req = httpMocks.createRequest({
method: 'GET',
headers: {
host: 'some.com',
[HEADERS.FORWARDED_PROTO]: 'https',
},
url: '/',
});
expect(
getPublicUrl('/test/', {
host: req.hostname,
headers: req.headers as any,
protocol: req.protocol,
})
).toEqual('http://some.com/test/');
delete process.env.VERDACCIO_FORWARDED_PROTO;
});
test('with a invalid X-Forwarded-Proto https and host injection with invalid host', () => {
process.env.VERDACCIO_PUBLIC_URL = 'http://injection.test.com"><svg onload="alert(1)">';
const req = httpMocks.createRequest({

View file

@ -104,10 +104,7 @@ export function media(expect: string | null): any {
next(
errorUtils.getCode(
HTTP_STATUS.UNSUPPORTED_MEDIA,
'wrong content-type, expect: ' +
expect +
', got: ' +
req.headers[HEADER_TYPE.CONTENT_TYPE]
'wrong content-type, expect: ' + expect + ', got: ' + req.get[HEADER_TYPE.CONTENT_TYPE]
)
);
} else {
@ -141,7 +138,7 @@ export function expectJson(
export function antiLoop(config: Config): Function {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
if (req.headers.via != null) {
if (req?.headers?.via != null) {
const arr = req.headers.via.split(',');
for (let i = 0; i < arr.length; i++) {
@ -264,7 +261,7 @@ export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFuncti
req.headers.authorization = '<Classified>';
}
const _cookie = req.headers.cookie;
const _cookie = req.get('cookie');
if (_.isNil(_cookie) === false) {
req.headers.cookie = '<Classified>';
}
@ -298,7 +295,7 @@ export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFuncti
};
const log = function (): void {
const forwardedFor = req.headers['x-forwarded-for'];
const forwardedFor = req.get('x-forwarded-for');
const remoteAddress = req.connection.remoteAddress;
const remoteIP = forwardedFor ? `${forwardedFor} via ${remoteAddress}` : remoteAddress;
let message;

View file

@ -47,7 +47,7 @@
"sanitize-filename": "1.6.3",
"lodash": "4.17.21",
"lowdb": "1.0.0",
"lru-cache": "6.0.0"
"lru-cache": "7.14.0"
},
"devDependencies": {
"@types/minimatch": "3.0.5",

View file

@ -40,7 +40,7 @@
"debug": "4.3.4",
"express": "4.18.2",
"lodash": "4.17.21",
"lru-cache": "6.0.0"
"lru-cache": "7.14.0"
},
"devDependencies": {
"@types/node": "16.11.65",

View file

@ -6,6 +6,7 @@ import { Auth } from '@verdaccio/auth';
import {
API_ERROR,
APP_ERROR,
HEADERS,
HTTP_STATUS,
VerdaccioError,
errorUtils,
@ -33,7 +34,7 @@ function addUserAuthApi(auth: Auth, config: Config): Router {
} else {
req.remote_user = user as RemoteUser;
const jWTSignOptions: JWTSignOptions = config.security.web.sign;
res.set(HEADERS.CACHE_CONTROL, 'no-cache, no-store');
next({
token: await auth.jwtEncrypt(user as RemoteUser, jWTSignOptions),
username: req.remote_user.name,

View file

@ -18,8 +18,9 @@ export async function loadTheme(config: any) {
const plugin = await asyncLoadPlugin(
config.theme,
{ config, logger },
function (plugin) {
return typeof plugin === 'string';
// TODO: add types { staticPath: string; manifest: unknown; manifestFiles: unknown }
function (plugin: any) {
return plugin.staticPath && plugin.manifest && plugin.manifestFiles;
},
config?.serverSettings?.pluginPrefix ?? 'verdaccio-theme'
);

View file

@ -12,7 +12,7 @@ import { hasLogin, validatePrimaryColor } from './utils/web-utils';
const pkgJSON = require('../package.json');
const DEFAULT_LANGUAGE = 'es-US';
const cache = new LRU({ max: 500, maxAge: 1000 * 60 * 60 });
const cache = new LRU({ max: 500, ttl: 1000 * 60 * 60 });
const debug = buildDebug('verdaccio:web:render');

View file

@ -55,6 +55,7 @@ describe('test web server', () => {
)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HEADERS.CACHE_CONTROL, 'no-cache, no-store')
.expect(HTTP_STATUS.OK)
.then((res) => {
expect(res.body.error).toBeUndefined();

38
pnpm-lock.yaml generated
View file

@ -741,7 +741,7 @@ importers:
lockfile: 1.0.4
lodash: 4.17.21
lowdb: 1.0.0
lru-cache: 6.0.0
lru-cache: 7.14.0
minimatch: 3.1.2
sanitize-filename: 1.6.3
dependencies:
@ -754,7 +754,7 @@ importers:
lockfile: 1.0.4
lodash: 4.17.21
lowdb: 1.0.0
lru-cache: 6.0.0
lru-cache: 7.14.0
sanitize-filename: 1.6.3
devDependencies:
'@types/minimatch': 3.0.5
@ -1329,7 +1329,7 @@ importers:
express: 4.18.2
jsdom: 20.0.1
lodash: 4.17.21
lru-cache: 6.0.0
lru-cache: 7.14.0
nock: 13.2.9
node-html-parser: 4.1.5
supertest: 6.3.0
@ -1352,7 +1352,7 @@ importers:
debug: 4.3.4
express: 4.18.2
lodash: 4.17.21
lru-cache: 6.0.0
lru-cache: 7.14.0
devDependencies:
'@types/node': 16.11.65
'@verdaccio/api': link:../api
@ -9286,6 +9286,10 @@ packages:
peerDependencies:
'@typescript-eslint/parser': ^5.0.0
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/parser': 5.41.0_eslint@8.26.0+typescript@4.8.4
'@typescript-eslint/scope-manager': 5.41.0
@ -9297,24 +9301,28 @@ packages:
regexpp: 3.2.0
semver: 7.3.8
tsutils: 3.21.0_typescript@4.8.4
typescript: 4.8.4
transitivePeerDependencies:
- supports-color
- typescript
/@typescript-eslint/parser/5.41.0_eslint@8.26.0+typescript@4.8.4:
resolution: {integrity: sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/scope-manager': 5.41.0
'@typescript-eslint/types': 5.41.0
'@typescript-eslint/typescript-estree': 5.41.0_typescript@4.8.4
debug: 4.3.4
eslint: 8.26.0
typescript: 4.8.4
transitivePeerDependencies:
- supports-color
- typescript
/@typescript-eslint/scope-manager/5.33.1:
resolution: {integrity: sha512-8ibcZSqy4c5m69QpzJn8XQq9NnqAToC8OdH/W6IXPXv83vRyEDPYLdjAlUx8h/rbusq6MkW4YdQzURGOqsn3CA==}
@ -9336,15 +9344,19 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: '*'
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 5.41.0_typescript@4.8.4
'@typescript-eslint/utils': 5.41.0_eslint@8.26.0+typescript@4.8.4
debug: 4.3.4
eslint: 8.26.0
tsutils: 3.21.0_typescript@4.8.4
typescript: 4.8.4
transitivePeerDependencies:
- supports-color
- typescript
/@typescript-eslint/types/5.33.1:
resolution: {integrity: sha512-7K6MoQPQh6WVEkMrMW5QOA5FO+BOwzHSNd0j3+BlBwd6vtzfZceJ8xJ7Um2XDi/O3umS8/qDX6jdy2i7CijkwQ==}
@ -9379,6 +9391,11 @@ packages:
/@typescript-eslint/typescript-estree/5.41.0_typescript@4.8.4:
resolution: {integrity: sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/types': 5.41.0
'@typescript-eslint/visitor-keys': 5.41.0
@ -9387,9 +9404,9 @@ packages:
is-glob: 4.0.3
semver: 7.3.8
tsutils: 3.21.0_typescript@4.8.4
typescript: 4.8.4
transitivePeerDependencies:
- supports-color
- typescript
/@typescript-eslint/utils/5.33.1_eslint@8.26.0+typescript@4.8.4:
resolution: {integrity: sha512-uphZjkMaZ4fE8CR4dU7BquOV6u0doeQAr8n6cQenl/poMaIyJtBu8eys5uk6u5HiDH01Mj5lzbJ5SfeDz7oqMQ==}
@ -17235,6 +17252,11 @@ packages:
dependencies:
yallist: 4.0.0
/lru-cache/7.14.0:
resolution: {integrity: sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==}
engines: {node: '>=12'}
dev: false
/lunr/2.3.9:
resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}