From 18f66ef73230ebe9f41ea0a7dcd6e47d4c239811 Mon Sep 17 00:00:00 2001 From: Christopher Kolstad Date: Thu, 22 Apr 2021 16:05:59 +0200 Subject: [PATCH] feat: Add change-password endpoint to user-controller (#800) fixes: #801 Co-authored-by: Fredrik Oseberg --- .eslintrc | 2 +- src/lib/routes/admin-api/user.test.js | 38 ++++++++++++++--- src/lib/routes/admin-api/user.ts | 61 +++++++++++++++++++++------ 3 files changed, 80 insertions(+), 21 deletions(-) diff --git a/.eslintrc b/.eslintrc index bf48f61c49..1218df91cb 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,7 @@ }, "extends": [ "airbnb-typescript/base", - "prettier", + "prettier" ], "parser": "@typescript-eslint/parser", "parserOptions": { diff --git a/src/lib/routes/admin-api/user.test.js b/src/lib/routes/admin-api/user.test.js index 315a0821ff..90214832b8 100644 --- a/src/lib/routes/admin-api/user.test.js +++ b/src/lib/routes/admin-api/user.test.js @@ -17,6 +17,8 @@ const currentUser = new User({ email: 'test@mail.com' }); function getSetup() { const base = `/random${Math.round(Math.random() * 1000)}`; const stores = store.createStores(); + stores.userStore.insert(currentUser); + const config = createTestConfig({ preHook: a => { a.use((req, res, next) => { @@ -30,12 +32,12 @@ function getSetup() { const app = getApp(config, stores, services, eventBus); return { base, - strategyStore: stores.strategyStore, + userStore: stores.userStore, request: supertest(app), }; } -test.only('should return current user', t => { +test('should return current user', t => { t.plan(1); const { request, base } = getSetup(); @@ -47,13 +49,35 @@ test.only('should return current user', t => { t.is(res.body.user.email, currentUser.email); }); }); +const owaspPassword = 't7GTx&$Y9pcsnxRv6'; -test('should logout and redirect', t => { +test('should allow user to change password', async t => { + t.plan(2); + const { request, base, userStore } = getSetup(); + const before = await userStore.get(currentUser); + t.falsy(before.passwordHash); + await request + .post(`${base}/api/admin/user/change-password`) + .send({ password: owaspPassword, confirmPassword: owaspPassword }) + .expect(200); + const updated = await userStore.get(currentUser); + t.truthy(updated.passwordHash); +}); + +test('should deny if password and confirmPassword are not equal', async t => { t.plan(0); const { request, base } = getSetup(); - return request - .get(`${base}/api/admin/user/logout`) - .expect(302) - .expect('Location', `${base}/`); + .post(`${base}/api/admin/user/change-password`) + .send({ password: owaspPassword, confirmPassword: 'somethingelse' }) + .expect(400); +}); + +test('should deny if password does not fulfill owasp criteria', async t => { + t.plan(0); + const { request, base } = getSetup(); + return request + .post(`${base}/api/admin/user/change-password`) + .send({ password: 'hunter123', confirmPassword: 'hunter123' }) + .expect(400); }); diff --git a/src/lib/routes/admin-api/user.ts b/src/lib/routes/admin-api/user.ts index d4e4fd7bef..3d04a375e3 100644 --- a/src/lib/routes/admin-api/user.ts +++ b/src/lib/routes/admin-api/user.ts @@ -1,24 +1,47 @@ 'use strict'; -import { Response } from 'express'; +import { Request, Response } from 'express'; import { IAuthRequest } from '../unleash-types'; import Controller from '../controller'; import { AccessService } from '../../services/access-service'; import { IUnleashConfig } from '../../types/option'; +import { IUnleashServices } from '../../types/services'; +import UserService from '../../services/user-service'; +import User from '../../user'; +import { Logger } from '../../logger'; +import { handleErrors } from './util'; -interface IService { - accessService: AccessService; +interface IChangeUserRequest { + password: string; + confirmPassword: string; +} + +interface UserRequest + extends Request { + user: User; } class UserController extends Controller { private accessService: AccessService; - constructor(config: IUnleashConfig, { accessService }: IService) { + private userService: UserService; + + private logger: Logger; + + constructor( + config: IUnleashConfig, + { + accessService, + userService, + }: Pick, + ) { super(config); this.accessService = accessService; + this.userService = userService; + this.logger = config.getLogger('lib/routes/admin-api/user.ts'); this.get('/', this.getUser); - this.get('/logout', this.logout); + this.post('/change-password', this.updateUserPass); } async getUser(req: IAuthRequest, res: Response): Promise { @@ -36,15 +59,27 @@ class UserController extends Controller { return res.status(404).end(); } - // Deprecated, use "/logout" instead. Will be removed in v4. - logout(req: IAuthRequest, res: Response): void { - if (req.session) { - req.session = null; + async updateUserPass( + req: UserRequest, + res: Response, + ): Promise { + const { user } = req; + if (user) { + const { password, confirmPassword } = req.body; + try { + if (password === confirmPassword) { + this.userService.validatePassword(password); + await this.userService.changePassword(user.id, password); + res.status(200).end(); + } else { + res.status(400).end(); + } + } catch (e) { + handleErrors(res, this.logger, e); + } + } else { + res.status(401).end(); } - if (req.logout) { - req.logout(); - } - res.redirect(`${this.config.server.baseUriPath}/`); } }