Sequelize: beforeCreate hook not updating hashed password - mysql

I am trying to save the hashed password using beforeCreate hook. However, the hashed that I generate doesn't get saved but instead saves the plain text version.
This what my UserAuth model looks like
interface IUserAuthAttributes {
user_auth_id: number;
username: string;
password: string;
full_name: string;
disable_user: number;
user_level_id: number;
created_modified: string | Date;
}
interface IUserAuthCreationAttributes
extends Optional<IUserAuthAttributes, 'user_auth_id' | 'disable_user' | 'user_level_id' | 'created_modified'> {
username: string;
password: string;
full_name: string;
}
export class UserAuth
extends Model<IUserAuthAttributes, IUserAuthCreationAttributes>
implements IUserAuthAttributes {
public user_auth_id!: number;
public username!: string;
public password!: string;
public full_name!: string;
public disable_user: number;
public user_level_id!: number;
public created_modified: string | Date;
public toUserJSON: () => UserAuth;
public generateAccessToken: (payload: IUser) => string;
public generateRefreshToken: (payload: IUser) => string;
public passwordMatch: (pw: string, cb: (err: any, isMatch?: any) => void) => void;
public getRole: () => 'meter_reader' | 'evaluator' | null;
}
UserAuth.init({
user_auth_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
username: {
type: DataTypes.STRING(20),
allowNull: false,
defaultValue: ""
},
password: {
type: DataTypes.STRING(100),
allowNull: false,
defaultValue: ""
},
full_name: {
type: DataTypes.STRING(100),
allowNull: false,
defaultValue: ""
}
// ... other
},
{
sequelize: DBInstance,
tableName: 'user_auth',
timestamps: false,
});
This is how I defined the hook:
UserAuth.beforeCreate((user, option) => {
const salt = bcrypt.genSaltSync();
// Using hashSync throws an error "Illegal arguments: undefined, string"
// const hash = bcrypt.hashSync(user.password, salt);
bcrypt.hash("password", salt, (err, hash) => {
if (err) throw new Error(err.message);
console.log('HASH -------', hash);
user.password = hash;
});
});
When I create a user:
const { username, password, full_name } = req.body;
const user = await UserAuth.create({
username, password, full_name
});
Upon logging the hashed value to the console, I indeed generate one successfully
HASH ------- $2a$10$KN.OSRXR7Od8WajjuD3hyutqk1tGS/Be.V9NDrm3F7fyZWxYAbJ/2

Finally found the solution.
In my previous code I was using a callback for the generating salt and hash. Also from the previous code
const hash = bcrypt.hashSync(user.getDataValue('password'), salt); it was throwing an error Illegal arguments: undefined, string because user.password returns undefined from the Instance, so instead, I get the value of the password using getDataValue method of the instance then using setDataValue to set the hashed password instead of using an assignment operation user.password = hash
UserAuth.beforeCreate((user, option) => {
if (user.isNewRecord) {
const salt = bcrypt.genSaltSync();
const hash = bcrypt.hashSync(user.getDataValue('password'), salt);
// user.password = hash; Not working
user.setDataValue('password', hash); // use this instead
}
})

Related

TypeORM error with relations. How to resolve such problem?

I am new to TypeORM. I'm using MySQL DB and ExpressJS app.
I have 2 entities: User and Client. There's one-to-one relationship between them. Client has foreign key.
I get following error when I save a client:
Cannot perform update query because update values are not defined. Call "qb.set(...)" method to specify updated values
User Entity:
export class User extends BaseEntity {
#PrimaryGeneratedColumn()
id: number
#Column()
role: string
#Column()
email: string
#Column()
password: string
#Column({ default: '' })
avatar: string
#Column()
firstName: string
#Column()
lastName: string
#Column()
fullName: string
#Column({ default: '' })
phone: string
#Column({ type: 'text', nullable: true })
description: string
#Column({ nullable: true })
age: number
#OneToOne(_type => Freelancer, freelancer => freelancer.user, { nullable: true })
freelancer: Freelancer
#OneToOne(_type => Client, client => client.user, { nullable: true })
client: Client
}
Client Entity:
#Entity()
export class Client extends BaseEntity {
#PrimaryGeneratedColumn()
id: number
#Column()
companyName: string
#ManyToOne(_type => Position, position => position.clients)
position: Position
#OneToOne(_type => ClientReview, clientReview => clientReview.client, { nullable: true })
#JoinColumn()
review: ClientReview
#OneToMany(_type => Project, project => project.client, { nullable: true })
projects: Project[]
#OneToOne(_type => User, user => user.client)
#JoinColumn()
user: User
}
Code in auth.service, where I save client. Just overview:
const user = userRepository.create({
email,
password: hashedPassword,
role,
description,
firstName,
lastName,
fullName: `${firstName} ${lastName}`,
phone
})
const clientRepository = getRepository(Client)
const positionRepository = getRepository(Position)
const positionEntity = await positionRepository.findOne({ id: position.id })
const client = clientRepository.create({
companyName,
position: positionEntity,
user
})
await userRepository.save(user)
await clientRepository.save(client)
The problem is in user column of Client entity, as when I remove it, everything works, but Client and User are saved separately and do not have any relation between each other, obviously.
So, my questions are What I did wrong and How should I fix it?
Thanks for the answer beforehand

How to save 2 entities that are related Typeorm?

I have a NestJS project that has an entity called Users. In this entity there are login related information about a user. I also have another entity called Profile, that will hold the user's name, pictures etc...
These 2 entities relates to each other, the problem is, when the user registers, I will not have any profile information yet. How can I make this happen on TypeOrm? I tried just saving the user information but when I create the profile it doesn't relate to the user.
Thanks
Here is my entities:
User Entity
import {
BaseEntity,
Column,
Entity,
JoinColumn,
OneToOne,
PrimaryGeneratedColumn,
Unique,
} from 'typeorm';
import * as bcrypt from 'bcrypt';
import { UserProfile } from './profile/user-profile.entity';
#Entity()
#Unique(['email'])
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
email: string;
#Column()
password: string;
#Column()
salt: string;
async validatePassword(password: string): Promise<boolean> {
const hash = await bcrypt.hash(password, this.salt);
return hash === this.password;
}
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
registration_date: Date;
#Column({ nullable: true })
login_method: string;
#Column()
role: string;
#OneToOne(() => UserProfile, (profile) => profile.user)
#JoinColumn()
profile: UserProfile;
}
User Repository (Save method)
async newUser(authCred: AuthCredentialsDTO): Promise<User> {
const { password } = authCred;
const userCred = new User();
Object.assign(userCred, authCred);
userCred.salt = await bcrypt.genSalt();
userCred.password = await this.hashPassword(password, userCred.salt);
try {
await userCred.save();
return userCred;
} catch (error) {
throw new InternalServerErrorException(error);
}
}
Profile Entity
import { BaseEntity, Column, Entity, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
import { User } from '../user.entity';
#Entity()
export class UserProfile extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ nullable: true })
firstName: string;
#Column({ nullable: true })
lastName: string;
#Column({ nullable: true })
address: string;
#Column({ nullable: true })
address_comp: string;
#Column({ nullable: true })
city: string;
#Column({ nullable: true })
province_state: string;
#Column({ nullable: true })
postal_code: string;
#Column({ nullable: true })
picture: string;
#OneToOne(() => User, (user) => user.profile)
user: User;
}
I rewrite the User entity to simplify it and remove unnecessary code.
Moreover I've made the following changes:
Set unique: true directly in the email column decorator.
registration_date is now #CreateDateColumn({ type: 'timestamp', update: false }) that automatically set the creation date.
Saving the salt with the password is a bad practice. See this.
Set cascade: true in profile column so when you save a User entity and profile field is "populated" is also saved. See this.
Move validatePassword(...) in another place. Don't pollute your entity.
In you DB store only the password hash.
To compare a plain password with the hash stored in the DB use the bcrypt.compare(plainPassword, hashPassword) function.
Use .create(...) method to "create" an instance of an entity and set its values.
User Entity:
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ unique: true })
email: string;
#Column()
password: string;
#CreateDateColumn({ type: 'timestamp' })
registration_date: Date;
#Column({ nullable: true })
login_method: string;
#Column()
role: string;
#OneToOne(() => UserProfile, (profile) => profile.user, { cascade: true })
#JoinColumn()
profile: UserProfile;
}
User Repository:
async newUser(authCred: AuthCredentialsDTO): Promise<User> {
const entityManager = getManager();
const { password } = authCred;
const user = entityManager.create(User, {
password: await hashPassword(password, await bcrypt.genSalt()),
profile: {}
});
try {
return (await entityManager.save(User, user));
} catch (error) {
throw new InternalServerErrorException(error);
}
}

Select desired columns only on eager loading Many-to-Many relation typeorm

I have 2 entities user and trip having many-to-many relationship with eager loading.
When I load trip I don't want all of user details to be return like I don't want the password or created and updated date etc just wanted name and id.
How can I do that?
Below are my two entities.
Trip entity
#Entity()
export class Trip {
#PrimaryGeneratedColumn()
id: number;
#Column()
#IsNotEmpty()
#Index({ unique: true })
name: string
#ManyToOne(type => User)
#JoinColumn()
owner: User
#Column()
#IsNotEmpty()
destination: string
#ManyToMany(type => User, {
eager: true
})
#JoinTable()
buddies: User[]
}
User entity
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
#IsNotEmpty()
#Index({ unique: true })
username: string
#Column()
#IsNotEmpty()
firstName: string
#Column({ default: null })
lastName: string
#Column({ unique: true, default: null })
#Length(10, 12)
phonenumber: string
#Column()
#Length(8, 16)
password: string
#Column()
#CreateDateColumn()
createdAt: Date;
#Column()
#UpdateDateColumn()
updatedAt: Date;
#BeforeUpdate()
#BeforeInsert()
hashPassword() {
if (this.password) {
this.password = bcrypt.hashSync(this.password, 8);
}
}
checkIfUnencryptedPasswordIsValid(unencryptedPassword: string) {
return bcrypt.compareSync(unencryptedPassword, this.password);
}
}
Using it as
public doGetTripById = async (req: Request, res: Response) => {
const tripId = req.params.tripId;
const tripRepository = getRepository(Trip);
let trip: Trip;
try {
trip = await tripRepository.findOneOrFail({ where: { id: tripId } });
res.status(200).json(trip)
} catch (error) {
return res.status(400).json({ message: "Oops! somethig went haywire, possibly trip doesn't exist" })
}
}
I am able to get the required response with below changes but I am not satisfied with it.
trip.buddies.forEach(buddy => {
buddy.password = undefined
buddy.updatedAt = undefined
buddy.createdAt = undefined
buddy.phonenumber = undefined
})
I think this could solve the problem
You must create a QueryBuilder object and add the relation assigning an alias to the foreign table, then you can choose the columns you want.
tripRepository.createQueryBuilder("Trip")
.innerJoinAndSelect("Trip.buddies", "Buddie")
.select(["Trip.id", "Trip.name", "Buddie.firstName", "Buddie.lastName"])
.where([...])
.getMany()
regards,

beforeCreate hook changes are not saved to database

I am using the beforeCreate to encrypt password before saving to database.
When I do:
const user = await User.create({name, email, password});
res.json(user);
I see the encrypted password in response. But in the database the password is not encrypted. If I do user.reload() and then send, I see what's stored in the database(unencrypted password).
This is the model:
User.init({
name: {
type: DataTypes.STRING,
allowNull: false
},
...
},{
sequelize,
hooks: {
beforeCreate: (user, options) => {
return bcrypt.genSalt(10)
.then(salt => {
bcrypt.hash(user.password, salt)
.then(hashedPassword => {
user.password = hashedPassword;
console.log(user.password, "FS");
return user.password;
})
.catch(err => console.log(err));
})
.catch(err => console.log(err));
}
}
})
This is the controller:
try{
const {name, email, password} = req.body;
if(isEmpty(name) || isEmpty(email) || isEmpty(password)){
res.status(400).json({errMessage: 'Enter name, email and password'});
}
const user = await User.create({name, email, password});
res.json(user); //data with encrypted password is sent, but not saved in db
}
The beforeCreate hook does not need to return a value, the return value type of the function signature as follows:
export type HookReturn = Promise<void> | void;
Besides, you forgot to add return before bcrypt.hash(user.password, salt) statement causes the beforeCreate function not to wait for the encryption asynchronous operation to complete.
Here is a working example:
import { sequelize } from '../../db';
import { Model, DataTypes } from 'sequelize';
import bcrypt from 'bcrypt';
class User extends Model {
password!: string;
name!: string;
}
User.init(
{
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
modelName: 'users',
hooks: {
beforeCreate: (user: User) => {
return bcrypt
.genSalt(10)
.then((salt) => {
return bcrypt
.hash(user.password, salt)
.then((hashedPassword) => {
user.password = hashedPassword;
})
.catch((err) => console.log(err));
})
.catch((err) => console.log(err));
},
},
},
);
(async function() {
try {
await sequelize.sync({ force: true });
await User.create({ name: 'ab', email: 'test#gmail.com', password: '123456' });
} catch (error) {
console.log(error);
} finally {
await sequelize.close();
}
})();
The execution log:
Executing (default): DROP TABLE IF EXISTS "users" CASCADE;
Executing (default): DROP TABLE IF EXISTS "users" CASCADE;
Executing (default): CREATE TABLE IF NOT EXISTS "users" ("id" SERIAL , "name" VARCHAR(255) NOT NULL, "email" VARCHAR(255) NOT NULL, "password" VARCHAR(255) NOT NULL, PRIMARY KEY ("id"));
Executing (default): SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND t.relkind = 'r' and t.relname = 'users' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;
Executing (default): INSERT INTO "users" ("id","name","email","password") VALUES (DEFAULT,$1,$2,$3) RETURNING *;
Check data record in the database:
node-sequelize-examples=# select * from users;
id | name | email | password
----+------+----------------+--------------------------------------------------------------
1 | ab | test#gmail.com | $2b$10$XQb89m.b6ie8ImokS6JPdurWfIH4Cq19y.XGhb7LpWYUklp5jaYh2
(1 row)

TypeORM createQueryBuilder can't manage a JOIN

I'm new with TypeORM.
I'm working with node, typescript, TypeORM, and MySQL.
I can't figure out how JOIN and createQueryBuilder works in TypeORM.
I have a Response table with a user_id field, that matchs with an User table an its id field.
The relation is response.user_id = user.id, in a One to Many relation.
And I don't know how it works with TypeORM
import { Entity, Column, PrimaryColumn, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
import { User } from '../User/User';
#Entity("response")
export class Response {
#PrimaryColumn("varchar", { length: 80, unique: true, primary: true })
public id: string;
#PrimaryGeneratedColumn()
public bk_id: number;
#Column("varchar", { length: 80 })
public user_id: string;
#Column("varchar", { length: 80 })
public parent_request_id: string;
#Column("text")
public description: string;
#Column("integer")
public status_id: number;
#Column("integer")
public deleted: number;
#Column("timestamp")
public created_on: Date;
#Column("timestamp")
public last_update: Date;
#OneToMany(type => User, user => user.id)
users: User[];
}
And User:
import { Entity, Column, PrimaryColumn, ManyToOne } from 'typeorm';
import { Response } from '../Response/Response';
#Entity("user")
export class User {
#PrimaryColumn("varchar", { length: 50, unique: true, primary: true })
public id: string;
#Column("varchar", { length: 255, unique: true })
public username: string;
#Column("varchar", { length: 255 })
public password: string;
#Column("varchar", { length: 150 })
public first_name: string;
#Column("varchar", { length: 255 })
public last_name: string;
#Column("varchar", { length: 355, unique: true })
public email: string;
#Column("timestamp")
public created_on: Date;
#Column("integer")
public userstatus_id: number;
#Column("integer")
public deleted: number;
#Column("timestamp")
public last_login: Date;
#ManyToOne(type => Response, response => response.user_id)
response: Response;
}
And my Sql sentence looks like this:
const result = await this.entityManager
.createQueryBuilder('Response')
.leftJoinAndSelect(
'Response.user_id',
'user',
'user.id = :id',
{ id: 'user_id' },
)
.where('response.parent_request_id = :parent_request_id', { parentRequestId });
I'm getting the error:
"Response" alias was not found. Maybe you forgot to join it?
Appreciate any help.
Thank you very much.
Probably because of:
const result = await this.entityManager
.createQueryBuilder('Response') // << entity with '' and lacking alias
.leftJoinAndSelect(
'Response.user_id',
'user',
'user.id = :id',
{ id: 'user_id' }, // < with '' would set as string (user_id)
)
.where('response.parent_request_id = :parent_request_id', { parentRequestId }); // < you forgot to declare param
You can try the following:
const result = await this.entityManager
.createQueryBuilder(Response, 'response')
.leftJoinAndSelect(
'response.user_id',
'user',
'user.id = :id',
{ id: user_id }, // assuming you're using user_id as variable
)
.where('response.parent_request_id = :parent_request_id', { parent_request_id: parentRequestId });
I have finally used the Sql Query, and not the Query builder.
I think is very tricky the construction of the query builder, so I prefer to have all in my hand. This is my solution:
this.entityManager.query(
`SELECT r.*, u.first_name, u.last_name, u.email, u.deleted, u.username, u.userstatus_id FROM knowhow.response as r
LEFT JOIN user as u on u.id = r.user_id
WHERE r.parent_request_id = '${parentRequestId}'`