I have a controller and service. From service, I am sending login success data to the controller. but its sending data before complete execution in service. but i am trying to send data after complete execution in loginservice.
I need to return user data after complete Login function..
userService
import { Injectable, NotFoundException, UnauthorizedException } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import * as jwt from 'jsonwebtoken';
import { Model } from 'mongoose';
import { User } from './user.model';
import * as bcrypt from 'bcrypt'
#Injectable()
export class UserService {
constructor(
#InjectModel('User') private readonly userModel: Model<User>,
) { }
async Login(email: string, password: string) {
const user = await this.userModel.findOne({ email });
if (!user) {
console.log("User does exist on the database.");
throw new UnauthorizedException();
}
await bcrypt.compare(password, user.password, function (err, result) {
if (!result) {
throw new UnauthorizedException();
}
const authJwtToken = jwt.sign({ name: user.name, email: user.email, role: user.role }, "testSecreate");
const response = { name: user.name, email: user.email, role: user.role, token: authJwtToken }
console.log(response)
return response;
});
}
}
userController
import {
Controller,
Post,
Body,
Get,
Param,
Patch,
Delete,
} from '#nestjs/common';
import { UserService } from './user.service';
#Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
#Post('/login')
async login(
#Body('email') userEmail,
#Body('password') userPassword
) {
const token = await this.userService.Login(userEmail, userPassword)
console.log(token, 'token')
return token;
}
}
since you're providing the callback func (last arg) to bcrypt.compare it will not return a promise. Please read the docs: https://www.npmjs.com/package/bcrypt#with-promises
Related
I'm building a "forgot password" controller, so my goal in "forgotPasswordController" is to generate a token and send it to my user entity, but the problem is that even following the typeorm documentation, I can't.but it still doesn't work, even though I use the const user instead of the User directly from the entity
error : (property) tokenResetPass: string Expected 3 arguments, but got 2.ts(2554) EntityManager.d.ts(208, 145): An argument for 'partialEntity' was not provided.
the problem is in
const sendToken = await AppDataSource.manager.update(User, {
tokenResetPass: token
});
forgotPassController
import { Request, Response } from "express";
import { User } from "../entities/User";
import { AppDataSource } from "../database/Config";
import jwt from 'jsonwebtoken';
import sendEmail from "../utils/mailer";
class forgotPasswordController {
async authenticate(req: Request, res: Response) {
const { email } = req.body;
const secret = process.env.JWT_SEC as string;
try {
const user = await AppDataSource.manager.findOneBy(User, {
email: email
});
if (!user) {
return res.status(401).json('Email not registered!');
};
if (user.isResetPass === true) {
return res.status(401).json('You have already made a request, check your email inbox.');
}
const token = jwt.sign({ id: user.id }, secret, {
expiresIn: process.env.EXPIRES_LOGIN,
});
if (!token) {
return res.status(401).json('Expired token, please try again.')
}
const sendToken = await AppDataSource.manager.update(User, {
tokenResetPass: token
});
sendEmail({
from: 'noreply#email.com',
to: user.email,
subject: 'Reset Password E-mail',
text: 'Token sending email to reset password',
html: `<h2>Copy the generated token below to reset your password</h2>
<h4>${token}</h4>
`,
});
return res.status(200).json('Email sent! Check your email box. ');
} catch (err) {
return res.status(500).json(err);
}
}
}
export default new forgotPasswordController();
user entity
import { BeforeInsert, BeforeUpdate, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import bcrypt from 'bcryptjs';
#Entity('users')
export class User {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
username: string;
#Column()
email: string;
#Column()
password: string;
#Column({
default: false
})
isResetPass: boolean;
#Column({
default: null
})
tokenResetPass: string;
#BeforeInsert()
#BeforeUpdate()
hashPassword() {
this.password = bcrypt.hashSync(this.password, 8);
}
}
I encountered to the issue that whenever I sign up through the postman I receive the token. However, other route is private and requires token as Authorization Bearer, but whenever I put the token I receive "Unauthorized". The validate from strategy never executes, as I understand because the token for some reasons is invalid. Important to mention, that I do not receive any errors.
jwt.strategy.ts:
import { Injectable } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.get('JWT_SECRET'),
});
}
async validate(payload: any) {
console.log({ payload: payload });
return true;
}
}
auth.module.ts
import { Module } from '#nestjs/common';
import { JwtModule } from '#nestjs/jwt';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './strategy';
#Module({
imports: [JwtModule.register({})],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
auth.service.ts where I receive token:
public async signin(body: AuthDto) {
// find the user by email
const user = await this.prisma['User'].findUnique({
where: {
email: body.email,
},
});
// if user does not exists throw exception
if (!user) {
throw new ForbiddenException('Invalid email address or password');
}
// compare passwords
const passwordMatches = await argon.verify(user.hash, body.password);
// if password is inoccrect throw exception
if (!passwordMatches) {
throw new ForbiddenException('Invalid email address or password');
}
return this.signToken(user.id, user.email);
}
auth.service.ts where I create the token:
private async signToken(userId: number, email: string): Promise<{ access_token: string }> {
const payLoad = {
sub: userId,
email,
};
const token = await this.jwt.sign(payLoad, {
expiresIn: '10m',
secret: this.config.get('JWT_SECRET'),
});
return { access_token: token };
}
}
user.controller.ts where the private route is
import { Controller, Get, UseGuards } from '#nestjs/common';
import { JwtGuard } from 'src/auth/guard';
#Controller('users')
export class UserController {
#UseGuards(JwtGuard)
#Get('me')
getMe() {
return 'Hello JWT';
}
}
jwt.guard.ts
import { AuthGuard } from '#nestjs/passport';
export class JwtGuard extends AuthGuard('jwt') {
constructor() {
super();
}
}
The code works fine, without any issues. However, the issues was with the postman. For some reasons the Authorization tab were bugged, tried to sent through headers written by hand and that worked without any problems
I'm using NestJS with Prisma ORM and kinda of struggling with the use of #Exclude() decorator because when I add it on my UserDto, it also excludes the password from the incoming requests, so Prisma does not access to the password.
For now, I've done this, but I'm sure it is not the cleanest way to do it.
User DTO
export class CreateUserDto {
// [...]
// Not #Exclude() here because otherwise I don't get Passwords from Incoming requests
#IsString()
#IsNotEmpty()
password: string;
}
export class UserDto extends CreateUserDto {
#Exclude()
password: string;
}
User Service
// [...]
async create(userData: CreateUserDto): Promise<User> {
userData.password = await argon.hash(userData.password);
try {
const createdUser = await this.prismaService.user.create({
data: userData,
});
// *** DIRTY PART ***
return plainToClass(UserDto, createdUser) as User;
// *** DIRTY PART ***
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
if (e.code === 'P2002')
throw new ConflictException('A user with this email already exists');
}
throw e;
}
}
// [...]
User Controller
// [...]
#Post('register')
async register(#Body() userData: CreateUserDto) {
console.log(userData);
return this.userService.create(userData);
}
// [...]
Thanks for your answers!
This is currently an issue being worked on by Prisma.
You can follow the issue at: https://github.com/prisma/prisma/issues/5042
There are two solutions to this problem:
Manually stripping the password from any response of type User:
async findOne(userId: string) {
const admin = await this.prismaService.user.findUnique({
where: {
id: userId,
},
});
const { id, email, phoneNumber, createdAt, updatedAt } = admin;
return { id, email, phoneNumber, createdAt, updatedAt };
}
Create a global interceptor to remove all 'password' values:
import { Injectable,
NestInterceptor,
ExecutionContext,
CallHandler } from '#nestjs/common';
import { map, Observable } from 'rxjs';
#Injectable()
export class RemovePasswordInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((value) => {
value.password = undefined;
return value;
}),
);
}
}
I have created the Nest.js app. And I use AWS Cognito to manage user authentication and authorization. I use amazon-cognito-identity-js for handling user signin/signout and #nestjs/passport / #UseGuards(AuthGuard('jwt')) for validating tokens and user access for the routes.
Now I need to get access to the current user attributes(email, phone_number...) in other routes of the app. What is the best way to do this?
auth.service.ts
import { AuthConfig } from './auth.config';
import { Injectable } from '#nestjs/common';
import {
AuthenticationDetails,
CognitoUser,
CognitoUserPool,
CognitoUserAttribute,
} from 'amazon-cognito-identity-js';
#Injectable()
export class AuthService {
private userPool: CognitoUserPool;
private sessionUserAttributes: {};
constructor(private readonly authConfig: AuthConfig) {
this.userPool = new CognitoUserPool({
UserPoolId: this.authConfig.userPoolId,
ClientId: this.authConfig.clientId,
});
}
registerUser(registerRequest: {
name: string;
email: string;
password: string;
}) {
const { name, email, password } = registerRequest;
return new Promise((resolve, reject) => {
return this.userPool.signUp(
name,
password,
[new CognitoUserAttribute({ Name: 'email', Value: email })],
null,
(err, result) => {
if (!result) {
reject(err);
} else {
resolve(result.user);
}
},
);
});
}
authenticateUser(user: { name: string; password: string }) {
const { name, password } = user;
const authenticationDetails = new AuthenticationDetails({
Username: name,
Password: password,
});
const userData = {
Username: name,
Pool: this.userPool,
};
const newUser = new CognitoUser(userData);
return new Promise((resolve, reject) => {
return newUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
resolve(result);
},
onFailure: (err) => {
reject(err);
},
});
});
}
}
jwt.strategi.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { AuthService } from './auth.service';
import { passportJwtSecret } from 'jwks-rsa';
import { AuthConfig } from './auth.config';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly authService: AuthService,
private authConfig: AuthConfig,
) {
super({
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `${authConfig.authority}/.well-known/jwks.json`,
}),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: authConfig.clientId,
issuer: authConfig.authority,
algorithms: ['RS256'],
});
}
public async validate(payload: any) {
return !!payload.sub;
}
}
app.controller.ts
import { Controller, Get, UseGuards, Header } from '#nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from '#nestjs/passport';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
#UseGuards(AuthGuard('jwt'))
#Header('Content-Type', 'text/html')
getHello(): string {
return this.appService.getHello();
}
}
user is set as a property on the request when passport successfully authenticates the user.
The request can be injected in your controller and user property then accessed.
import { Controller, Get, UseGuards, Header, Request } from '#nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from '#nestjs/passport';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
#UseGuards(AuthGuard('jwt'))
#Header('Content-Type', 'text/html')
getHello(#Request() req): string {
console.log(req.user);
return this.appService.getHello();
}
}
I am looking for a response from userService .. but it is returning a null value..
I have consoled the data in userSerice it's showing here.
may be my controller is giving a response before receiving value from userService.
how can I solve this?
usercontroller
import {
Controller,
Post,
Body,
Get,
Param,
Patch,
Delete,
} from '#nestjs/common';
import { UserService } from './user.service';
#Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
#Post('/login')
async login(
#Body('email') userEmail,
#Body('password') userPassword
) {
const token = await this.userService.Login(userEmail, userPassword)
console.log(token, 'token')
return token;
}
}
Userservice :
import { Injectable, NotFoundException, UnauthorizedException } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import * as jwt from 'jsonwebtoken';
import { Model } from 'mongoose';
import { User } from './user.model';
import * as bcrypt from 'bcrypt'
import { resolve } from 'dns';
#Injectable()
export class UserService {
constructor(
#InjectModel('User') private readonly userModel: Model<User>,
) { }
async Login(email: string, password: string) {
const user = await this.userModel.findOne({ email });
if (!user) {
console.log("User does exist on the database.");
throw new UnauthorizedException();
}
bcrypt.compare(password, user.password, function (err, result) {
if (!result) {
throw new UnauthorizedException();
}
const authJwtToken = jwt.sign({ name: user.name, email: user.email, role: user.role }, "testSecreate");
const response = { name: user.name, email: user.email, role: user.role, token: authJwtToken }
console.log(response)
return response;
});
}
}
Tushar has n all right answer, but it's still mixing promises and callbacks, which I think should be avoided if possible. You can use this instead to not have any callbacks and just use async/await and promises throughout the method
import {
Injectable,
NotFoundException,
UnauthorizedException,
} from "#nestjs/common";
import { InjectModel } from "#nestjs/mongoose";
import * as jwt from "jsonwebtoken";
import { Model } from "mongoose";
import { User } from "./user.model";
import * as bcrypt from "bcrypt";
#Injectable()
export class UserService {
constructor(#InjectModel("User") private readonly userModel: Model<User>) {}
async Login(email: string, password: string) {
const user = await this.userModel.findOne({ email });
if (!user) {
console.log("User does exist on the database.");
throw new UnauthorizedException();
}
const result = await bcrypt.compare(password, user.password);
if (!result) {
throw new UnauthorizedException();
}
const authJwtToken = await jwt.sign(
{ name: user.name, email: user.email, role: user.role },
"testSecreate"
);
const response = {
name: user.name,
email: user.email,
role: user.role,
token: authJwtToken,
};
console.log(response);
return response;
}
}
Now, the console.log(response) will fire before console.log('token', token) in your controller method, and the flow will look synchronous while actually being asynchronous in nature.
import { Injectable, NotFoundException, UnauthorizedException } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import * as jwt from 'jsonwebtoken';
import { Model } from 'mongoose';
import { User } from './user.model';
import * as bcrypt from 'bcrypt'
import { resolve } from 'dns';
#Injectable()
export class UserService {
constructor(
#InjectModel('User') private readonly userModel: Model<User>,
) { }
async Login(email: string, password: string) {
const user = await this.userModel.findOne({ email });
if (!user) {
console.log("User does exist on the database.");
throw new UnauthorizedException();
}
await bcrypt.compare(password, user.password, function (err, result) {
if (!result) {
throw new UnauthorizedException();
}
const authJwtToken = await jwt.sign({ name: user.name, email: user.email, role: user.role }, "testSecreate");
const response = { name: user.name, email: user.email, role: user.role, token: authJwtToken }
console.log(response)
return response;
});
}
}
NOTE
The asynchronous method will be executed in parallel with your main program, so your console.log will be done before the callback function inside bcrypt.compare. You will see always 'oops, it was false'.