diff --git a/.changeset/thick-geese-wash.md b/.changeset/thick-geese-wash.md new file mode 100644 index 000000000..ec7b55894 --- /dev/null +++ b/.changeset/thick-geese-wash.md @@ -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 diff --git a/packages/api/src/user.ts b/packages/api/src/user.ts index be83dde85..31f27d304 100644 --- a/packages/api/src/user.ts +++ b/packages/api/src/user.ts @@ -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`, diff --git a/packages/api/src/v1/token.ts b/packages/api/src/v1/token.ts index bba19880a..fc14948f8 100644 --- a/packages/api/src/v1/token.ts +++ b/packages/api/src/v1/token.ts @@ -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, diff --git a/packages/api/test/integration/_helper.ts b/packages/api/test/integration/_helper.ts index a265a1e09..839f4d27a 100644 --- a/packages/api/test/integration/_helper.ts +++ b/packages/api/test/integration/_helper.ts @@ -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); } diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts index 75efdcad3..525459099 100644 --- a/packages/auth/src/auth.ts +++ b/packages/auth/src/auth.ts @@ -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, diff --git a/packages/auth/test/auth-utils.spec.ts b/packages/auth/test/auth-utils.spec.ts index 1d00bb1ac..ad3e8fe11 100644 --- a/packages/auth/test/auth-utils.spec.ts +++ b/packages/auth/test/auth-utils.spec.ts @@ -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', + ]); }); }); }); diff --git a/packages/config/src/security.ts b/packages/config/src/security.ts index 9849aa67e..493f4901e 100644 --- a/packages/config/src/security.ts +++ b/packages/config/src/security.ts @@ -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: {}, }; diff --git a/packages/config/src/user.ts b/packages/config/src/user.ts index 8857f715e..fdeb9bf2e 100644 --- a/packages/config/src/user.ts +++ b/packages/config/src/user.ts @@ -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, diff --git a/packages/core/core/src/constants.ts b/packages/core/core/src/constants.ts index ea3a2f32f..781167c17 100644 --- a/packages/core/core/src/constants.ts +++ b/packages/core/core/src/constants.ts @@ -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', diff --git a/packages/core/url/tests/getPublicUrl.spec.ts b/packages/core/url/tests/getPublicUrl.spec.ts index 26d36acd6..0b89df31d 100644 --- a/packages/core/url/tests/getPublicUrl.spec.ts +++ b/packages/core/url/tests/getPublicUrl.spec.ts @@ -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">'; const req = httpMocks.createRequest({ diff --git a/packages/middleware/src/middleware.ts b/packages/middleware/src/middleware.ts index 28f0a61ee..ad0cbad79 100644 --- a/packages/middleware/src/middleware.ts +++ b/packages/middleware/src/middleware.ts @@ -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 = ''; } - const _cookie = req.headers.cookie; + const _cookie = req.get('cookie'); if (_.isNil(_cookie) === false) { req.headers.cookie = ''; } @@ -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; diff --git a/packages/plugins/local-storage/package.json b/packages/plugins/local-storage/package.json index 26ce7310c..e8fa3e216 100644 --- a/packages/plugins/local-storage/package.json +++ b/packages/plugins/local-storage/package.json @@ -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", diff --git a/packages/web/package.json b/packages/web/package.json index 260aa0bd7..8b0fc9e6b 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -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", diff --git a/packages/web/src/api/user.ts b/packages/web/src/api/user.ts index cb3751b4a..f689e1055 100644 --- a/packages/web/src/api/user.ts +++ b/packages/web/src/api/user.ts @@ -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, diff --git a/packages/web/src/middleware/render-web.ts b/packages/web/src/middleware/render-web.ts index 43563d844..854e86a8a 100644 --- a/packages/web/src/middleware/render-web.ts +++ b/packages/web/src/middleware/render-web.ts @@ -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' ); diff --git a/packages/web/src/renderHTML.ts b/packages/web/src/renderHTML.ts index c3711bddc..28c570007 100644 --- a/packages/web/src/renderHTML.ts +++ b/packages/web/src/renderHTML.ts @@ -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'); diff --git a/packages/web/test/api.user.test.ts b/packages/web/test/api.user.test.ts index 364d548c0..760a1893a 100644 --- a/packages/web/test/api.user.test.ts +++ b/packages/web/test/api.user.test.ts @@ -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(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a6376b47..f7d3ad8c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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==}