Skip to content

Commit

Permalink
feat: Add change-password endpoint to user-controller (#800)
Browse files Browse the repository at this point in the history
fixes: #801 

Co-authored-by: Fredrik Oseberg <fredrik.no@gmail.com>
  • Loading branch information
Christopher Kolstad and FredrikOseberg committed Apr 22, 2021
1 parent 1850911 commit 18f66ef
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
},
"extends": [
"airbnb-typescript/base",
"prettier",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
Expand Down
36 changes: 30 additions & 6 deletions src/lib/routes/admin-api/user.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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();

Expand All @@ -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 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 logout and redirect', t => {
test('should deny if password and confirmPassword are not equal', async t => {
t.plan(0);
const { request, base } = getSetup();
return request
.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
.get(`${base}/api/admin/user/logout`)
.expect(302)
.expect('Location', `${base}/`);
.post(`${base}/api/admin/user/change-password`)
.send({ password: 'hunter123', confirmPassword: 'hunter123' })
.expect(400);
});
61 changes: 48 additions & 13 deletions src/lib/routes/admin-api/user.ts
Original file line number Diff line number Diff line change
@@ -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<PARAM, QUERY, BODY, RESPONSE>
extends Request<PARAM, QUERY, BODY, RESPONSE> {
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<IUnleashServices, 'accessService' | 'userService'>,
) {
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<void> {
Expand All @@ -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;
}
if (req.logout) {
req.logout();
async updateUserPass(
req: UserRequest<any, any, IChangeUserRequest, any>,
res: Response,
): Promise<void> {
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();
}
res.redirect(`${this.config.server.baseUriPath}/`);
}
}

Expand Down

0 comments on commit 18f66ef

Please sign in to comment.