From f997cdec25d721018cf4508e1f3c301680bcea3b Mon Sep 17 00:00:00 2001 From: Markus Glutting Date: Mon, 31 May 2021 13:10:16 +0200 Subject: [PATCH] Use hash instead of encryption for storing passwords refs #234 --- generators/server/files.js | 1 + .../templates/server/e2e/user.e2e-spec.ts.ejs | 8 +++++--- .../server/templates/server/package.json.ejs | 2 ++ .../server/templates/server/src/config.ts.ejs | 5 ++--- .../server/src/domain/user.entity.ts.ejs | 18 ++++++++++-------- .../1570200490072-SeedUsersRoles.ts.ejs | 4 ++-- .../src/security/hash.transformer.ts.ejs | 17 +++++++++++++++++ .../server/src/service/auth.service.ts.ejs | 8 +++++--- 8 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 generators/server/templates/server/src/security/hash.transformer.ts.ejs diff --git a/generators/server/files.js b/generators/server/files.js index 9067195c..61888117 100644 --- a/generators/server/files.js +++ b/generators/server/files.js @@ -34,6 +34,7 @@ const serverFiles = { 'src/security/decorators/auth-user.decorator.ts', 'src/security/decorators/roles.decorator.ts', 'src/security/index.ts', + 'src/security/hash.transformer.ts', 'src/client/header-util.ts', 'src/client/interceptors/logging.interceptor.ts', 'src/service/auth.service.ts', diff --git a/generators/server/templates/server/e2e/user.e2e-spec.ts.ejs b/generators/server/templates/server/e2e/user.e2e-spec.ts.ejs index e7980011..0bab1b0c 100644 --- a/generators/server/templates/server/e2e/user.e2e-spec.ts.ejs +++ b/generators/server/templates/server/e2e/user.e2e-spec.ts.ejs @@ -64,10 +64,12 @@ describe('User', () => { it('/PUT update user', async () => { testUserDTO.login = 'TestUserUpdate'; - const savedUser: UserDTO = await service.save(testUserDTO); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { password: savedPassword, ...savedUser } = await service.save(testUserDTO); savedUser.firstName = 'Updated Name'; - const updatedUser: UserDTO = ( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { password: updatedPassword, ...updatedUser } = ( await request(app.getHttpServer()) .put('/api/admin/users') .send(savedUser) @@ -80,7 +82,7 @@ describe('User', () => { expect(updatedUser.firstName).toEqual(savedUser.firstName); <%_ } _%> - await service.delete(savedUser); + await service.delete(savedUser as UserDTO); }); it('/GET user with a login name', async () => { diff --git a/generators/server/templates/server/package.json.ejs b/generators/server/templates/server/package.json.ejs index 0de8df8c..100a244a 100644 --- a/generators/server/templates/server/package.json.ejs +++ b/generators/server/templates/server/package.json.ejs @@ -37,6 +37,7 @@ <%_ } _%> "@nestjs/swagger": "3.1.0", "@nestjs/typeorm": "7.1.4", + "bcrypt": "5.0.1", "class-transformer": "0.3.1", "class-validator": "0.13.1", "cloud-config-client": "1.4.2", @@ -79,6 +80,7 @@ "typeorm-encrypted": "0.5.6" }, "devDependencies": { + "@types/bcrypt": "5.0.0", "@nestjs/testing": "7.5.1", "@types/express": "4.17.1", "@types/express-serve-static-core": "4.17.3", diff --git a/generators/server/templates/server/src/config.ts.ejs b/generators/server/templates/server/src/config.ts.ejs index edd4656d..6e29d6d9 100644 --- a/generators/server/templates/server/src/config.ts.ejs +++ b/generators/server/templates/server/src/config.ts.ejs @@ -14,6 +14,7 @@ export class Config { 'jhipster.security.authentication.jwt.base64-secret' = ''; 'jhipster.security.authentication.jwt.token-validity-in-seconds' = 86400; 'jhipster.security.authentication.jwt.token-validity-in-seconds-for-remember-me' = 2592000; + 'jhipster.security.authentication.jwt.hash-salt-or-rounds' = 10; <%_ } else if (authenticationType === 'oauth2') { _%> 'jhipster.security.session.secret' = ''; 'jhipster.security.oauth2.client.provider.oidc.issuer-uri' = ''; @@ -49,9 +50,7 @@ export class Config { 'cloud.config.uri' = 'http://admin:${jhipster.registry.password}@localhost:8761/config'; 'cloud.config.name' = '<%= baseName %>'; 'cloud.config.profile' = 'prod'; - 'loud.config.label' = 'master'; - 'crypto.key' = '3772c1cdbd27c225735d116d1e4c5421a3aec26c919cc7ab457f21a4d16a1821'; - 'crypto.iv' = '54f3ad979d9262d3a2dd4489531daf34'; + 'cloud.config.label' = 'master'; constructor(properties) { this.addAll(properties); diff --git a/generators/server/templates/server/src/domain/user.entity.ts.ejs b/generators/server/templates/server/src/domain/user.entity.ts.ejs index fb72dfa8..898f10a8 100644 --- a/generators/server/templates/server/src/domain/user.entity.ts.ejs +++ b/generators/server/templates/server/src/domain/user.entity.ts.ejs @@ -1,8 +1,8 @@ import { Authority } from './authority.entity'; -import { Entity, Column <%_ if (databaseType !== 'mongodb') { _%>, ManyToMany, JoinTable <%_ } _%> } from 'typeorm'; +import { Entity, Column, BeforeInsert, BeforeUpdate <%_ if (databaseType !== 'mongodb') { _%>, ManyToMany, JoinTable <%_ } _%> } from 'typeorm'; import { BaseEntity } from './base/base.entity'; +import * as bcrypt from 'bcrypt'; import { config } from '../config'; -import { EncryptionTransformer } from "typeorm-encrypted"; @Entity('nhi_user') export class User extends BaseEntity { @@ -30,12 +30,6 @@ export class User extends BaseEntity { @Column({ type: "varchar", - transformer: new EncryptionTransformer({ - key: config.get('crypto.key'), - algorithm: 'aes-256-cbc', - ivLength: 16, - iv: config.get('crypto.iv') - }), select: false }) password: string; @@ -47,4 +41,12 @@ export class User extends BaseEntity { resetKey?: string; @Column({ nullable: true }) resetDate?: Date; + + @BeforeInsert() + @BeforeUpdate() + async hashPassword?(): Promise { + if (this.password) { + this.password = await bcrypt.hash(this.password, config.get('jhipster.security.authentication.jwt.hash-salt-or-rounds')); + } + } } diff --git a/generators/server/templates/server/src/migrations/1570200490072-SeedUsersRoles.ts.ejs b/generators/server/templates/server/src/migrations/1570200490072-SeedUsersRoles.ts.ejs index 2ec1eab9..ee3bdde5 100644 --- a/generators/server/templates/server/src/migrations/1570200490072-SeedUsersRoles.ts.ejs +++ b/generators/server/templates/server/src/migrations/1570200490072-SeedUsersRoles.ts.ejs @@ -83,8 +83,8 @@ export class SeedUsersRoles1570200490072 implements MigrationInterface { this.user3.authorities= [adminRole, userRole]; this.user4.authorities= [userRole]; - await userRepository.save([this.user1, this.user2, this.user3, this.user4]); - + await userRepository.save([this.user1, this.user2, this.user3, this.user4] + .map(u => userRepository.create(u))); <%_ } _%> } diff --git a/generators/server/templates/server/src/security/hash.transformer.ts.ejs b/generators/server/templates/server/src/security/hash.transformer.ts.ejs new file mode 100644 index 00000000..3813bdf7 --- /dev/null +++ b/generators/server/templates/server/src/security/hash.transformer.ts.ejs @@ -0,0 +1,17 @@ +import { ValueTransformer } from 'typeorm'; +import * as bcrypt from 'bcrypt'; +import { config } from '../config'; + +export class HashTransformer implements ValueTransformer { + from(value: string): string { + return value; + } + + to(value: string): string { + if (value) { + return bcrypt.hashSync(value, config.get('jhipster.security.authentication.jwt.hash-salt-or-rounds')); + } else { + return value; + } + } +} diff --git a/generators/server/templates/server/src/service/auth.service.ts.ejs b/generators/server/templates/server/src/service/auth.service.ts.ejs index 6ca5edc4..910a6c16 100644 --- a/generators/server/templates/server/src/service/auth.service.ts.ejs +++ b/generators/server/templates/server/src/service/auth.service.ts.ejs @@ -9,6 +9,7 @@ import { AuthorityRepository } from '../repository/authority.repository'; import { UserService } from '../service/user.service'; import { UserDTO } from './dto/user.dto'; import { FindManyOptions } from 'typeorm'; +import * as bcrypt from 'bcrypt'; <%_ const userIdType = getPkType(databaseType) === 'Long' ? 'number' : 'string'; @@ -31,8 +32,9 @@ export class AuthService { const loginUserName = userLogin.username; const loginPassword = userLogin.password; - const userFind = await this.userService.findByfields({ where: { login: loginUserName, password: loginPassword } }); - if (!userFind) { + const userFind = await this.userService.findByfields({ where: { login: loginUserName }, select: ['id', 'login', 'password', 'activated'] }); + const validPassword = !!userFind && await bcrypt.compare(loginPassword, userFind.password); + if (!userFind || !validPassword) { throw new HttpException('Invalid login name or password!', HttpStatus.BAD_REQUEST); } @@ -77,7 +79,7 @@ export class AuthService { if (!userFind) { throw new HttpException('Invalid login name!', HttpStatus.BAD_REQUEST); } - if (userFind.password !== currentClearTextPassword ) { + if (!(await bcrypt.compare(currentClearTextPassword, userFind.password))) { throw new HttpException('Invalid password!', HttpStatus.BAD_REQUEST); } userFind.password = newPassword;