'TypeORM / NestJS - @BeforeUpdate hook not working
I'm having problems using TypeOrm hook "BeforeUpdate"
I'm trying to update the user entity password, by passing a simple string and calling the save method to trigger the beforeUpdate hook and then hash the password, but this hook is not working while calling the save method.
This is what i have
user.service.ts
async update(id: number, updateUserDto: UpdateUserDto) {
const roles =
updateUserDto.roles &&
(await Promise.all(
updateUserDto.roles.map((name) => this.preloadRoleByName(name))
));
const user = await this.userRepository.findOneOrFail(id);
if (!user) {
throw new NotFoundException(`User with ID #${id} not found`);
}
const toSaveUser = {
...user,
...updateUserDto,
roles,
};
return await this.userRepository.save(toSaveUser);
}
user.entity.ts
.
.
.
@Column()
@Exclude()
password: string;
@BeforeInsert()
@BeforeUpdate()
private async hashPassword() {
const rounds = 10;
const salt = await bcrypt.genSalt(rounds);
this.password = await bcrypt.hash(this.password, salt);
}
user.controller.ts
@Patch(":id")
@UseInterceptors(ClassSerializerInterceptor)
async update(@Param("id") id: string, @Body() updateUserDto: UpdateUserDto) {
return await this.usersService.update(+id, updateUserDto);
}
What i'm doing wrong?
BeforeInsert hook works or if i call userRepository.preload() method to update it works but it doesn't replace the roles relationship, that's why i take this approach.
Any ideas?
Solution 1:[1]
You need to create DTO(Data Transfer Object) first and then update. this code is for update temporary password.
How create DTO:
this.<YOUR_REPOSITORY_NAME>.create(<INPUT_OBJECT>)
Example:
async updatePassword(id: number, tempPassword: string): Promise<boolean> {
let newUser = { tempPassword: tempPassword };
const userDto = this.userAccountRepository.create(newUser)
const userAccount = this.userAccountRepository.update(
{
userAccountId: id,
},
userDto
);
if (userAccount) {
return true;
} else {
return false;
}
}
This is my entity:
import {BeforeInsert, BeforeUpdate,Column,Entity,JoinColumn,OneToMany,OneToOne,PrimaryColumn,} from 'typeorm';
import { Users } from '../users/users.entity';
const crypto = require('crypto');
@Entity()
export class UserAccount {
@PrimaryColumn()
userAccountId: number;
@OneToOne(() => Users, { cascade: true })
@JoinColumn({ name: 'userAccountId' })
@Column({ nullable: true })
tempPassword: string;
@BeforeInsert()
@BeforeUpdate()
async hashPassword(): Promise<void> {
if (!!this.password) {
this.password = crypto.createHmac('sha256', this.password).digest('hex');
}
if (!!this.tempPassword) {
this.tempPassword = crypto
.createHmac('sha256', this.tempPassword)
.digest('hex');
}
}
}
Solution 2:[2]
It's a thing to be aware of when using these triggers in TypeOrm, you have to use repository api to create the instance you call .save on.
const toSaveUser = this.userRepository.create({
...user,
...updateUserDto,
roles,
});
return await this.userRepository.save(toSaveUser);
And you should be good. Calling .save on a DTO object or a Entity instance not created from repository.create will not trigger the event functions.
Solution 3:[3]
Two inputs from my side,
- Separate function for encrypting updated password -
@BeforeUpdate() async hashPasswordBeforeUpdate() { this.password = await bcrypt.hash(this.password, 10); }
- Try creating
PUTrequest instead of aPATCHrequest
I am able to generate a valid update query, sharing just for reference-
query: UPDATE `users` SET `levelId` = ?, `updatedAt` = ?, `password` = ? WHERE `id` IN (?) -- PARAMETERS: [null,"2021-05-07T07:27:47.198Z","$2b$10$uQOMNv57BZLB/W/9SWPbke6/OMdIDWxv3i25A8rUhA0/vEMloWb2W",1]
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 |
