mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-13 06:34:39 -04:00
feat: check password strength for local login
Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
parent
d7c58b9de5
commit
6a56ce5541
2 changed files with 38 additions and 3 deletions
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -13,6 +13,7 @@ import authConfigMock from '../config/mock/auth.config.mock';
|
||||||
import {
|
import {
|
||||||
InvalidCredentialsError,
|
InvalidCredentialsError,
|
||||||
NoLocalIdentityError,
|
NoLocalIdentityError,
|
||||||
|
PasswordTooWeakError,
|
||||||
} from '../errors/errors';
|
} from '../errors/errors';
|
||||||
import { LoggerModule } from '../logger/logger.module';
|
import { LoggerModule } from '../logger/logger.module';
|
||||||
import { User } from '../users/user.entity';
|
import { User } from '../users/user.entity';
|
||||||
|
@ -25,7 +26,7 @@ describe('IdentityService', () => {
|
||||||
let service: IdentityService;
|
let service: IdentityService;
|
||||||
let user: User;
|
let user: User;
|
||||||
let identityRepo: Repository<Identity>;
|
let identityRepo: Repository<Identity>;
|
||||||
const password = 'test123';
|
const password = 'AStrongPasswordToStartWith123';
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
@ -81,7 +82,7 @@ describe('IdentityService', () => {
|
||||||
user.identities = Promise.resolve([identity]);
|
user.identities = Promise.resolve([identity]);
|
||||||
});
|
});
|
||||||
it('works', async () => {
|
it('works', async () => {
|
||||||
const newPassword = 'newPassword';
|
const newPassword = 'ThisIsAStrongNewP@ssw0rd';
|
||||||
const identity = await service.updateLocalPassword(user, newPassword);
|
const identity = await service.updateLocalPassword(user, newPassword);
|
||||||
await checkPassword(newPassword, identity.passwordHash ?? '').then(
|
await checkPassword(newPassword, identity.passwordHash ?? '').then(
|
||||||
(result) => expect(result).toBeTruthy(),
|
(result) => expect(result).toBeTruthy(),
|
||||||
|
@ -94,6 +95,11 @@ describe('IdentityService', () => {
|
||||||
NoLocalIdentityError,
|
NoLocalIdentityError,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('fails, when new password is too weak', async () => {
|
||||||
|
await expect(
|
||||||
|
service.updateLocalPassword(user, 'password1'),
|
||||||
|
).rejects.toThrow(PasswordTooWeakError);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loginWithLocalIdentity', () => {
|
describe('loginWithLocalIdentity', () => {
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
*/
|
*/
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { zxcvbnAsync, zxcvbnOptions } from '@zxcvbn-ts/core';
|
||||||
|
import zxcvbnCommonPackage from '@zxcvbn-ts/language-common';
|
||||||
|
import zxcvbnEnPackage from '@zxcvbn-ts/language-en';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import authConfiguration, { AuthConfig } from '../config/auth.config';
|
import authConfiguration, { AuthConfig } from '../config/auth.config';
|
||||||
|
@ -12,6 +15,7 @@ import {
|
||||||
InvalidCredentialsError,
|
InvalidCredentialsError,
|
||||||
NoLocalIdentityError,
|
NoLocalIdentityError,
|
||||||
NotInDBError,
|
NotInDBError,
|
||||||
|
PasswordTooWeakError,
|
||||||
} from '../errors/errors';
|
} from '../errors/errors';
|
||||||
import { ConsoleLoggerService } from '../logger/console-logger.service';
|
import { ConsoleLoggerService } from '../logger/console-logger.service';
|
||||||
import { User } from '../users/user.entity';
|
import { User } from '../users/user.entity';
|
||||||
|
@ -30,6 +34,15 @@ export class IdentityService {
|
||||||
private authConfig: AuthConfig,
|
private authConfig: AuthConfig,
|
||||||
) {
|
) {
|
||||||
this.logger.setContext(IdentityService.name);
|
this.logger.setContext(IdentityService.name);
|
||||||
|
const options = {
|
||||||
|
dictionary: {
|
||||||
|
...zxcvbnCommonPackage.dictionary,
|
||||||
|
...zxcvbnEnPackage.dictionary,
|
||||||
|
},
|
||||||
|
graphs: zxcvbnCommonPackage.adjacencyGraphs,
|
||||||
|
translations: zxcvbnEnPackage.translations,
|
||||||
|
};
|
||||||
|
zxcvbnOptions.setOptions(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -119,6 +132,7 @@ export class IdentityService {
|
||||||
*/
|
*/
|
||||||
async createLocalIdentity(user: User, password: string): Promise<Identity> {
|
async createLocalIdentity(user: User, password: string): Promise<Identity> {
|
||||||
const identity = Identity.create(user, ProviderType.LOCAL, false);
|
const identity = Identity.create(user, ProviderType.LOCAL, false);
|
||||||
|
await this.checkPasswordStrength(password);
|
||||||
identity.passwordHash = await hashPassword(password);
|
identity.passwordHash = await hashPassword(password);
|
||||||
return await this.identityRepository.save(identity);
|
return await this.identityRepository.save(identity);
|
||||||
}
|
}
|
||||||
|
@ -144,6 +158,7 @@ export class IdentityService {
|
||||||
);
|
);
|
||||||
throw new NoLocalIdentityError('This user has no internal identity.');
|
throw new NoLocalIdentityError('This user has no internal identity.');
|
||||||
}
|
}
|
||||||
|
await this.checkPasswordStrength(newPassword);
|
||||||
internalIdentity.passwordHash = await hashPassword(newPassword);
|
internalIdentity.passwordHash = await hashPassword(newPassword);
|
||||||
return await this.identityRepository.save(internalIdentity);
|
return await this.identityRepository.save(internalIdentity);
|
||||||
}
|
}
|
||||||
|
@ -174,4 +189,18 @@ export class IdentityService {
|
||||||
throw new InvalidCredentialsError('Password is not correct');
|
throw new InvalidCredentialsError('Password is not correct');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @async
|
||||||
|
* Check if the password is strong enough.
|
||||||
|
* This check is performed against the minimalPasswordStrength of the {@link AuthConfig}.
|
||||||
|
* @param {string} password - the password to check
|
||||||
|
* @throws {PasswordTooWeakError} the password is too weak
|
||||||
|
*/
|
||||||
|
private async checkPasswordStrength(password: string): Promise<void> {
|
||||||
|
const result = await zxcvbnAsync(password);
|
||||||
|
if (result.score < this.authConfig.local.minimalPasswordStrength) {
|
||||||
|
throw new PasswordTooWeakError();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue