Merge pull request #1935 from hedgedoc/feature/verify-password-change

This commit is contained in:
Yannick Bungers 2022-01-04 10:36:09 +01:00 committed by GitHub
commit 9ecf7ba2be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 44 additions and 9 deletions

View file

@ -9,10 +9,10 @@ import {
ConflictException, ConflictException,
Controller, Controller,
Delete, Delete,
NotFoundException,
Post, Post,
Put, Put,
Req, Req,
UnauthorizedException,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { Session } from 'express-session'; import { Session } from 'express-session';
@ -70,6 +70,10 @@ export class AuthController {
@Body() changePasswordDto: UpdatePasswordDto, @Body() changePasswordDto: UpdatePasswordDto,
): Promise<void> { ): Promise<void> {
try { try {
await this.identityService.checkLocalPassword(
user,
changePasswordDto.currentPassword,
);
await this.identityService.updateLocalPassword( await this.identityService.updateLocalPassword(
user, user,
changePasswordDto.newPassword, changePasswordDto.newPassword,
@ -77,7 +81,9 @@ export class AuthController {
return; return;
} catch (e) { } catch (e) {
if (e instanceof NotInDBError) { if (e instanceof NotInDBError) {
throw new NotFoundException(e.message); throw new UnauthorizedException(
'Verifying your identity with the current password did not work.',
);
} }
throw e; throw e;
} }

View file

@ -102,9 +102,9 @@ describe('IdentityService', () => {
) as Identity; ) as Identity;
identity.passwordHash = await hashPassword(password); identity.passwordHash = await hashPassword(password);
user.identities = Promise.resolve([identity]); user.identities = Promise.resolve([identity]);
await expect( await expect(service.checkLocalPassword(user, password)).resolves.toEqual(
service.loginWithLocalIdentity(user, password), undefined,
).resolves.toEqual(undefined); );
}); });
describe('fails', () => { describe('fails', () => {
it('when user has no local identity', async () => { it('when user has no local identity', async () => {

View file

@ -73,20 +73,20 @@ export class IdentityService {
* @param {string} password - the password to use * @param {string} password - the password to use
* @throws {NotInDBError} the specified user can't be logged in * @throws {NotInDBError} the specified user can't be logged in
*/ */
async loginWithLocalIdentity(user: User, password: string): Promise<void> { async checkLocalPassword(user: User, password: string): Promise<void> {
const internalIdentity: Identity | undefined = const internalIdentity: Identity | undefined =
await getFirstIdentityFromUser(user, ProviderType.LOCAL); await getFirstIdentityFromUser(user, ProviderType.LOCAL);
if (internalIdentity === undefined) { if (internalIdentity === undefined) {
this.logger.debug( this.logger.debug(
`The user with the username ${user.username} does not have a internal identity.`, `The user with the username ${user.username} does not have a internal identity.`,
'loginWithLocalIdentity', 'checkLocalPassword',
); );
throw new NotInDBError(); throw new NotInDBError();
} }
if (!(await checkPassword(password, internalIdentity.passwordHash ?? ''))) { if (!(await checkPassword(password, internalIdentity.passwordHash ?? ''))) {
this.logger.debug( this.logger.debug(
`Password check for ${user.username} did not succeed.`, `Password check for ${user.username} did not succeed.`,
'loginWithLocalIdentity', 'checkLocalPassword',
); );
throw new NotInDBError(); throw new NotInDBError();
} }

View file

@ -30,7 +30,7 @@ export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
const user = await this.userService.getUserByUsername(username, [ const user = await this.userService.getUserByUsername(username, [
UserRelationEnum.IDENTITIES, UserRelationEnum.IDENTITIES,
]); ]);
await this.identityService.loginWithLocalIdentity(user, password); await this.identityService.checkLocalPassword(user, password);
return user; return user;
} catch (e) { } catch (e) {
if (e instanceof NotInDBError) { if (e instanceof NotInDBError) {

View file

@ -6,6 +6,8 @@
import { IsString } from 'class-validator'; import { IsString } from 'class-validator';
export class UpdatePasswordDto { export class UpdatePasswordDto {
@IsString()
currentPassword: string;
@IsString() @IsString()
newPassword: string; newPassword: string;
} }

View file

@ -112,6 +112,7 @@ describe('Auth', () => {
it('works', async () => { it('works', async () => {
// Change password // Change password
const changePasswordDto: UpdatePasswordDto = { const changePasswordDto: UpdatePasswordDto = {
currentPassword: password,
newPassword: newPassword, newPassword: newPassword,
}; };
await request(testSetup.app.getHttpServer()) await request(testSetup.app.getHttpServer())
@ -133,6 +134,7 @@ describe('Auth', () => {
cookie = response.get('Set-Cookie')[0]; cookie = response.get('Set-Cookie')[0];
// Reset password // Reset password
const changePasswordBackDto: UpdatePasswordDto = { const changePasswordBackDto: UpdatePasswordDto = {
currentPassword: newPassword,
newPassword: password, newPassword: password,
}; };
await request(testSetup.app.getHttpServer()) await request(testSetup.app.getHttpServer())
@ -146,6 +148,7 @@ describe('Auth', () => {
testSetup.configService.get('authConfig').local.enableLogin = false; testSetup.configService.get('authConfig').local.enableLogin = false;
// Try to change password // Try to change password
const changePasswordDto: UpdatePasswordDto = { const changePasswordDto: UpdatePasswordDto = {
currentPassword: password,
newPassword: newPassword, newPassword: newPassword,
}; };
await request(testSetup.app.getHttpServer()) await request(testSetup.app.getHttpServer())
@ -177,6 +180,29 @@ describe('Auth', () => {
.send(JSON.stringify(loginOldPasswordDto)) .send(JSON.stringify(loginOldPasswordDto))
.expect(201); .expect(201);
}); });
it('fails, when old password is wrong', async () => {
// Try to change password
const changePasswordDto: UpdatePasswordDto = {
currentPassword: 'wrong',
newPassword: newPassword,
};
await request(testSetup.app.getHttpServer())
.put('/api/private/auth/local')
.set('Content-Type', 'application/json')
.set('Cookie', cookie)
.send(JSON.stringify(changePasswordDto))
.expect(401);
// old password still does work for login
const loginOldPasswordDto: LoginDto = {
password: password,
username: username,
};
await request(testSetup.app.getHttpServer())
.post('/api/private/auth/local/login')
.set('Content-Type', 'application/json')
.send(JSON.stringify(loginOldPasswordDto))
.expect(201);
});
}); });
describe('POST /auth/local/login', () => { describe('POST /auth/local/login', () => {

View file

@ -114,6 +114,7 @@ describe('Register and Login', () => {
.set('Content-Type', 'application/json') .set('Content-Type', 'application/json')
.send( .send(
JSON.stringify({ JSON.stringify({
currentPassword: PASSWORD,
newPassword: 'newPassword', newPassword: 'newPassword',
}), }),
) )