From a183446000f683591fa96d0ea6b79b286de410f2 Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Sat, 3 Feb 2024 12:07:15 +0100 Subject: [PATCH] fix: profile v1 endpoint and tests --- src/api/endpoint/api/v1/profile.ts | 10 +- test/unit/modules/api/config/profile.yaml | 27 ++++++ test/unit/modules/api/profile.spec.ts | 111 ++++++++++++++++++++++ 3 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 test/unit/modules/api/config/profile.yaml create mode 100644 test/unit/modules/api/profile.spec.ts diff --git a/src/api/endpoint/api/v1/profile.ts b/src/api/endpoint/api/v1/profile.ts index 7d30d91ba..2b9e97a84 100644 --- a/src/api/endpoint/api/v1/profile.ts +++ b/src/api/endpoint/api/v1/profile.ts @@ -2,7 +2,7 @@ import { Response, Router } from 'express'; import _ from 'lodash'; import { Auth } from '@verdaccio/auth'; -import { validatioUtils } from '@verdaccio/core'; +import { errorUtils, validatioUtils } from '@verdaccio/core'; import { rateLimit } from '@verdaccio/middleware'; import { ConfigYaml } from '@verdaccio/types'; @@ -71,15 +71,17 @@ export default function (router: Router, auth: Auth, config: ConfigYaml) { /* eslint new-cap:off */ } + if (_.isEmpty(password.old)) { + return next(errorUtils.getBadRequest('old password is required')); + } + auth.changePassword( name, password.old, password.new, (err, isUpdated): $NextFunctionVer => { if (_.isNull(err) === false) { - return next( - ErrorCode.getCode(err.status, err.message) || ErrorCode.getConflict(err.message) - ); + return next(errorUtils.getForbidden(err.message)); } if (isUpdated) { diff --git a/test/unit/modules/api/config/profile.yaml b/test/unit/modules/api/config/profile.yaml new file mode 100644 index 000000000..b9e32184a --- /dev/null +++ b/test/unit/modules/api/config/profile.yaml @@ -0,0 +1,27 @@ +auth: + htpasswd: + file: ./htpasswd-profile +web: + enable: true + title: verdaccio + +uplinks: + +log: { type: stdout, format: pretty, level: trace } + +packages: + '@*/*': + access: $all + publish: $all + unpublish: $all + proxy: npmjs + 'verdaccio': + access: $all + publish: $all + '**': + access: $all + publish: $all + unpublish: $all + proxy: npmjs + +_debug: true diff --git a/test/unit/modules/api/profile.spec.ts b/test/unit/modules/api/profile.spec.ts new file mode 100644 index 000000000..cbeafc522 --- /dev/null +++ b/test/unit/modules/api/profile.spec.ts @@ -0,0 +1,111 @@ +import supertest from 'supertest'; + +import { HEADERS, HEADER_TYPE, HTTP_STATUS, TOKEN_BEARER } from '@verdaccio/core'; +import { buildToken } from '@verdaccio/utils'; + +import { createUser, initializeServer } from './_helper'; + +describe('profile ', () => { + describe('get profile ', () => { + test('should return Unauthorized if header token is missing', async () => { + const app = await initializeServer('profile.yaml'); + return supertest(app) + .get('/-/npm/v1/user') + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.UNAUTHORIZED); + }); + + test('should return user details', async () => { + const app = await initializeServer('profile.yaml'); + const credentials = { name: 'test', password: 'test' }; + const response = await createUser(app, credentials.name, credentials.password); + return supertest(app) + .get('/-/npm/v1/user') + .set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, response.body.token)) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.OK); + }); + }); + describe('post profile ', () => { + test('should return Unauthorized if header token is missing', async () => { + const app = await initializeServer('profile.yaml'); + return supertest(app) + .post('/-/npm/v1/user') + .send({}) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.UNAUTHORIZED); + }); + + test('should return handle to short new password', async () => { + const app = await initializeServer('profile.yaml'); + const credentials = { name: 'test', password: 'test' }; + const response = await createUser(app, credentials.name, credentials.password); + return supertest(app) + .post('/-/npm/v1/user') + .send({ password: { new: '_' } }) + .set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, response.body.token)) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.UNAUTHORIZED); + }); + + test('should return handle to missing old password', async () => { + const app = await initializeServer('profile.yaml'); + const credentials = { name: 'test', password: 'test' }; + const response = await createUser(app, credentials.name, credentials.password); + return supertest(app) + .post('/-/npm/v1/user') + .send({ password: { new: 'fooooo', old: undefined } }) + .set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, response.body.token)) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.BAD_REQUEST); + }); + + test('should return handle to missing password', async () => { + const app = await initializeServer('profile.yaml'); + const credentials = { name: 'test', password: 'test' }; + const response = await createUser(app, credentials.name, credentials.password); + return supertest(app) + .post('/-/npm/v1/user') + .send({ another: '_' }) + .set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, response.body.token)) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.INTERNAL_ERROR); + }); + + test('should return handle change password', async () => { + const app = await initializeServer('profile.yaml'); + const credentials = { name: 'test', password: 'test' }; + const response = await createUser(app, credentials.name, credentials.password); + return supertest(app) + .post('/-/npm/v1/user') + .send({ password: { new: 'good password_.%#@$@#$@#', old: 'test' } }) + .set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, response.body.token)) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.OK); + }); + + test('should return handle change password failure', async () => { + const app = await initializeServer('profile.yaml'); + const credentials = { name: 'test', password: 'test' }; + const response = await createUser(app, credentials.name, credentials.password); + return supertest(app) + .post('/-/npm/v1/user') + .send({ password: { new: 'good password_.%#@$@#$@#', old: 'test_do_not_match' } }) + .set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, response.body.token)) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.FORBIDDEN); + }); + + test('should handle tfa ( two factor auth) disabled', async () => { + const app = await initializeServer('profile.yaml'); + const credentials = { name: 'test', password: 'test' }; + const response = await createUser(app, credentials.name, credentials.password); + return supertest(app) + .post('/-/npm/v1/user') + .send({ tfa: '_' }) + .set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, response.body.token)) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.SERVICE_UNAVAILABLE); + }); + }); +});