From fd3ad1e546f5293e862d767f23b3714e6dd5dc8c Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Mon, 20 Dec 2021 23:11:17 +0100 Subject: [PATCH] feat: add cache-control header to endpoints (#2791) Add no cache to endpoints that returns tokens in the body --- src/api/endpoint/api/user.ts | 5 ++-- src/api/endpoint/api/v1/token.ts | 3 ++- src/api/web/endpoint/user.ts | 4 +-- src/lib/constants.ts | 1 + test/unit/modules/api/api.spec.ts | 5 ++-- test/unit/modules/api/token.spec.ts | 34 +++++++++++--------------- test/unit/modules/web/api.web.spec.ts | 35 +++++++++------------------ 7 files changed, 36 insertions(+), 51 deletions(-) diff --git a/src/api/endpoint/api/user.ts b/src/api/endpoint/api/user.ts index 39f24dcc6..6f408474c 100644 --- a/src/api/endpoint/api/user.ts +++ b/src/api/endpoint/api/user.ts @@ -4,7 +4,7 @@ import Cookies from 'cookies'; import { Config, RemoteUser } from '@verdaccio/types'; import { Response, Router } from 'express'; import { ErrorCode } from '../../../lib/utils'; -import { API_ERROR, API_MESSAGE, HTTP_STATUS } from '../../../lib/constants'; +import { API_ERROR, API_MESSAGE, HEADERS, HTTP_STATUS } from '../../../lib/constants'; import { createRemoteUser, createSessionToken, getApiToken, getAuthenticatedMessage, validatePassword } from '../../../lib/auth-utils'; import { logger } from '../../../lib/logger'; @@ -33,7 +33,7 @@ export default function (route: Router, auth: IAuth, config: Config): void { const token = await getApiToken(auth, config, restoredRemoteUser, password); res.status(HTTP_STATUS.CREATED); - + res.set(HEADERS.CACHE_CONTROL, 'no-cache, no-store'); return next({ ok: getAuthenticatedMessage(req.remote_user.name), token, @@ -60,6 +60,7 @@ export default function (route: Router, auth: IAuth, config: Config): void { req.remote_user = user; res.status(HTTP_STATUS.CREATED); + res.set(HEADERS.CACHE_CONTROL, 'no-cache, no-store'); return next({ ok: `user '${req.body.name}' created`, token, diff --git a/src/api/endpoint/api/v1/token.ts b/src/api/endpoint/api/v1/token.ts index 84d5c5446..d0cb2d263 100644 --- a/src/api/endpoint/api/v1/token.ts +++ b/src/api/endpoint/api/v1/token.ts @@ -2,7 +2,7 @@ import _ from 'lodash'; import buildDebug from 'debug'; import { Response, Router } from 'express'; import { Config, RemoteUser, Token } from '@verdaccio/types'; -import { HTTP_STATUS, SUPPORT_ERRORS } from '../../../../lib/constants'; +import { HEADERS, HTTP_STATUS, SUPPORT_ERRORS } from '../../../../lib/constants'; import { ErrorCode, mask } from '../../../../lib/utils'; import { getApiToken } from '../../../../lib/auth-utils'; import { stringToMD5 } from '../../../../lib/crypto-utils'; @@ -89,6 +89,7 @@ export default function (route: Router, auth: IAuth, storage: IStorageHandler, c await storage.saveToken(saveToken); debug('token %o was created for user %o', key, name); + res.set(HEADERS.CACHE_CONTROL, 'no-cache, no-store'); return next( normalizeToken({ token, diff --git a/src/api/web/endpoint/user.ts b/src/api/web/endpoint/user.ts index 3745f487a..0a101388f 100644 --- a/src/api/web/endpoint/user.ts +++ b/src/api/web/endpoint/user.ts @@ -7,7 +7,7 @@ import _ from 'lodash'; import { Router, Response, Request } from 'express'; import { Config, RemoteUser, JWTSignOptions } from '@verdaccio/types'; -import { API_ERROR, APP_ERROR, HTTP_STATUS } from '../../../lib/constants'; +import { API_ERROR, APP_ERROR, HEADERS, HTTP_STATUS } from '../../../lib/constants'; import { IAuth, $NextFunctionVer } from '../../../../types'; import { ErrorCode } from '../../../lib/utils'; import { getSecurity, validatePassword } from '../../../lib/auth-utils'; @@ -23,7 +23,7 @@ function addUserAuthApi(route: Router, auth: IAuth, config: Config): void { } else { req.remote_user = user; const jWTSignOptions: JWTSignOptions = getSecurity(config).web.sign; - + res.set(HEADERS.CACHE_CONTROL, 'no-cache, no-store'); next({ token: await auth.jwtEncrypt(user, jWTSignOptions), username: req.remote_user.name, diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 10f383b9d..a13f8c59d 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -26,6 +26,7 @@ export const HEADERS = { JSON: 'application/json', CONTENT_TYPE: 'Content-type', CONTENT_LENGTH: 'content-length', + CACHE_CONTROL: 'Cache-Control', TEXT_PLAIN: 'text/plain', TEXT_HTML: 'text/html', AUTHORIZATION: 'authorization', diff --git a/test/unit/modules/api/api.spec.ts b/test/unit/modules/api/api.spec.ts index 6037689be..2f7f64976 100644 --- a/test/unit/modules/api/api.spec.ts +++ b/test/unit/modules/api/api.spec.ts @@ -200,7 +200,7 @@ describe('endpoint unit test', () => { const token = res.body.token; expect(typeof token).toBe('string'); expect(res.body.ok).toMatch(`user '${credentials.name}' created`); - + expect(res.get(HEADERS.CACHE_CONTROL)).toEqual('no-cache, no-store'); // testing JWT auth headers with token // we need it here, because token is required request(app) @@ -272,6 +272,7 @@ describe('endpoint unit test', () => { if (err) { return done(err); } + expect(res.get(HEADERS.CACHE_CONTROL)).toEqual('no-cache, no-store'); expect(res.body).toBeTruthy(); done(); }); @@ -1078,7 +1079,7 @@ describe('endpoint unit test', () => { await Promise.all([ putPackage(request(app), `/${pkgName}`, generatePackageMetadata(pkgName, '2.0.0'), token), putPackage(request(app), `/${pkgName}`, generatePackageMetadata(pkgName, '2.0.1'), token), - putPackage(request(app), `/${pkgName}`, generatePackageMetadata(pkgName, '2.0.2'), token) + putPackage(request(app), `/${pkgName}`, generatePackageMetadata(pkgName, '2.0.2'), token), ]); const pkg = generatePackageMetadata(pkgName, '2.0.3'); diff --git a/test/unit/modules/api/token.spec.ts b/test/unit/modules/api/token.spec.ts index 7355cc2cc..b01fc9fd7 100644 --- a/test/unit/modules/api/token.spec.ts +++ b/test/unit/modules/api/token.spec.ts @@ -6,18 +6,12 @@ import _ from 'lodash'; import configDefault from '../../partials/config'; import endPointAPI from '../../../../src/api'; -import { - HEADERS, - HTTP_STATUS, - HEADER_TYPE, - TOKEN_BEARER, - API_ERROR, - SUPPORT_ERRORS -} from '../../../../src/lib/constants'; +import { HEADERS, HTTP_STATUS, HEADER_TYPE, TOKEN_BEARER, API_ERROR, SUPPORT_ERRORS } from '../../../../src/lib/constants'; import { mockServer } from '../../__helper/mock'; import { DOMAIN_SERVERS } from '../../../functional/config.functional'; import { getNewToken } from '../../__helper/api'; import { buildToken } from '../../../../src/lib/utils'; +import { expectJson } from '../../../../src/api/middleware'; require('../../../../src/lib/logger').setup([{ type: 'stdout', format: 'pretty', level: 'trace' }]); @@ -69,17 +63,17 @@ describe('endpoint unit test', () => { { auth: { htpasswd: { - file: './test-storage-token-spec/.htpasswd-token' - } + file: './test-storage-token-spec/.htpasswd-token', + }, }, storage: store, self_path: store, uplinks: { npmjs: { - url: `http://${DOMAIN_SERVERS}:${mockServerPort}` - } + url: `http://${DOMAIN_SERVERS}:${mockServerPort}`, + }, }, - logs: [{ type: 'stdout', format: 'pretty', level: 'trace' }] + logs: [{ type: 'stdout', format: 'pretty', level: 'trace' }], }, 'token.spec.yaml' ); @@ -117,11 +111,12 @@ describe('endpoint unit test', () => { }); test('should generate one token', async (done) => { - await generateTokenCLI(app, token, { + const [, resp] = await generateTokenCLI(app, token, { password: credentials.password, readonly: false, - cidr_whitelist: [] + cidr_whitelist: [], }); + expect(resp.get(HEADERS.CACHE_CONTROL)).toEqual('no-cache, no-store'); request(app) .get('/-/npm/v1/tokens') @@ -134,7 +129,6 @@ describe('endpoint unit test', () => { } const { objects, urls } = resp.body; - expect(objects).toHaveLength(1); const [tokenGenerated] = objects; expect(tokenGenerated.user).toEqual(credentials.name); @@ -152,7 +146,7 @@ describe('endpoint unit test', () => { const res = await generateTokenCLI(app, token, { password: credentials.password, readonly: false, - cidr_whitelist: [] + cidr_whitelist: [], }); const t = res[1].body.token; @@ -182,7 +176,7 @@ describe('endpoint unit test', () => { await generateTokenCLI(app, token, { password: 'wrongPassword', readonly: false, - cidr_whitelist: [] + cidr_whitelist: [], }); done(); } catch (e) { @@ -198,7 +192,7 @@ describe('endpoint unit test', () => { try { const res = await generateTokenCLI(app, token, { password: credentials.password, - cidr_whitelist: [] + cidr_whitelist: [], }); expect(res[0]).toBeNull(); @@ -213,7 +207,7 @@ describe('endpoint unit test', () => { try { const res = await generateTokenCLI(app, token, { password: credentials.password, - readonly: false + readonly: false, }); expect(res[0]).toBeNull(); diff --git a/test/unit/modules/web/api.web.spec.ts b/test/unit/modules/web/api.web.spec.ts index b1573d976..80aec3897 100644 --- a/test/unit/modules/web/api.web.spec.ts +++ b/test/unit/modules/web/api.web.spec.ts @@ -7,13 +7,7 @@ import publishMetadata from '../../partials/publish-api'; import forbiddenPlace from '../../partials/forbidden-place'; import endPointAPI from '../../../../src/api'; -import { - HEADERS, - API_ERROR, - HTTP_STATUS, - HEADER_TYPE, - DIST_TAGS -} from '../../../../src/lib/constants'; +import { HEADERS, API_ERROR, HTTP_STATUS, HEADER_TYPE, DIST_TAGS } from '../../../../src/lib/constants'; import { DOMAIN_SERVERS } from '../../../functional/config.functional'; import { mockServer } from '../../__helper/mock'; import { addUser } from '../../__helper/api'; @@ -34,16 +28,16 @@ describe('endpoint web unit test', () => { { auth: { htpasswd: { - file: './web-api-storage/.htpasswd-web-api' - } + file: './web-api-storage/.htpasswd-web-api', + }, }, storage: store, uplinks: { npmjs: { - url: `http://${DOMAIN_SERVERS}:${mockServerPort}` - } + url: `http://${DOMAIN_SERVERS}:${mockServerPort}`, + }, }, - self_path: store + self_path: store, }, 'api.web.spec.yaml' ); @@ -60,17 +54,9 @@ describe('endpoint web unit test', () => { describe('Registry WebUI endpoints', () => { beforeAll(async () => { - await request(app) - .put('/@scope%2fpk1-test') - .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON) - .send(JSON.stringify(publishMetadata)) - .expect(HTTP_STATUS.CREATED); + await request(app).put('/@scope%2fpk1-test').set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON).send(JSON.stringify(publishMetadata)).expect(HTTP_STATUS.CREATED); - await request(app) - .put('/forbidden-place') - .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON) - .send(JSON.stringify(forbiddenPlace)) - .expect(HTTP_STATUS.CREATED); + await request(app).put('/forbidden-place').set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON).send(JSON.stringify(forbiddenPlace)).expect(HTTP_STATUS.CREATED); }); describe('Packages', () => { @@ -209,7 +195,7 @@ describe('endpoint web unit test', () => { .post('/-/verdaccio/login') .send({ username: credentials.name, - password: credentials.password + password: credentials.password, }) .expect(HTTP_STATUS.OK) .end(function (err, res) { @@ -217,6 +203,7 @@ describe('endpoint web unit test', () => { expect(res.body.token).toBeDefined(); expect(res.body.token).toBeTruthy(); expect(res.body.username).toMatch(credentials.name); + expect(res.get(HEADERS.CACHE_CONTROL)).toEqual('no-cache, no-store'); done(); }); }); @@ -227,7 +214,7 @@ describe('endpoint web unit test', () => { .send( JSON.stringify({ username: 'fake', - password: 'fake' + password: 'fake', }) ) // FIXME: there should be 401