I just started working with Nest.js and so far it's been smooth. However I've encountered an issue in which my mongoose pre save hook in the User schema isn't being triggered. This should be straightforward, but for whatever reason the password is being saved in its plain form rather than hashed. What gives?
One more minor issue - how do I define a field that references another schema when working with @Prop decorators? The profile field is supposed to be of mongoose.schema.types.objectid which would simply be profile: { type: mongoose.Schema.Types.ObjectId, ref: 'Profile' } when working without decorators.
Below are the relevant snippets.
User schema
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
@Schema({
timestamps: true,
})
export class User extends Document {
@Prop({ required: true })
fullname: string;
@Prop({ required: true, unique: true })
username: string;
@Prop({ required: true, unique: true, lowercase: true })
email: string;
@Prop({ required: true })
password: string;
@Prop({ required: true, ref: 'Profile' })
profile: string
@Prop({ required: true, enum: ['admin', 'basic' ]})
role: string
}
export const UserSchema = SchemaFactory.createForClass(User);
Users module
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import * as bcrypt from 'bcrypt';
import { User, UserSchema } from './user.model';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
imports: [
MongooseModule.forFeatureAsync([
{
name: User.name,
useFactory: () => {
const schema = UserSchema;
schema.pre<User>('save', async function (next: Function) {
const user = this;
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(user.password, salt);
user.password = hashedPassword;
next();
});
schema.methods.comparePasswords = async function (submittedPassword) {
const user = this;
await bcrypt.compare(submittedPassword, user.password);
};
return schema;
},
},
]),
],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
Users Service
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import { ProfilesService } from '../profiles/profiles.service';
import { User } from '../users/user.model';
@Injectable()
export class AuthService {
constructor(
@InjectModel(User.name) private readonly userModel: Model<User>,
private usersService: UsersService,
private profilesService: ProfilesService,
private jwtService: JwtService
) {}
async signup(signupData): Promise<any> {
const foundUser = await this.userModel.findOne({ email: signupData.email });
if (foundUser) {
throw new HttpException(
'Email is already in use',
HttpStatus.BAD_REQUEST
);
}
const createdProfile = await this.profilesService.createProfile();
const createdUser = await this.userModel.create({
...signupData,
profile: createdProfile._id,
role: 'basic',
});
const createdUserCopy = { ...createdUser.toObject() };
delete createdUserCopy.password;
delete createdUserCopy.__v;
const payload = {
username: createdUser.username,
sub: createdUser._id,
};
return {
user: createdUserCopy,
token: this.jwtService.sign(payload),
};
}
}