visit
Step 1:
OTP Generation:
Code (Part-I):
src/auth/auth.controller.ts
import {
Controller,
Post,
Req,
UseGuards,
Get,
Body,
BadRequestException,
Param,
NotFoundException,
} from "@nestjs/common";
import { JwtAuthGuard } from "./auth.guard";
import { LoggedInToken } from "../users/objects/login-user.dto";
import { AuthService } from "./auth.service";
import * as speakeasy from "speakeasy";
import { optSecret } from "../common/constants/config";
import {
UNKNOWN_PARAM,
EMAIL_NOT_FOUND,
OTP_ERROR,
EXISTS,
OTP_NOT_EXPIRED,
NEW_PASSWORD_AND_CONFIRM_NEW_PASSWORD_ERROR,
OTP_TIME_OUT,
TOKEN_ALREADY_USED,
EMAIL_ERROR,
BLOCKED_ACCOUNT_ERROR,
} from "../common/constants/string";
import { plainToClass } from "class-transformer";
import { success } from "../common/base/httpResponse.interface";
import { UserDto } from "../users/objects/create-user.dto";
import { OtpEmail, UserCycloanAccountBlockedEmail } from "../users/objects/user.registered.email";
import {
ForgetPasswordOtpEmail,
PasswordChangedAlert,
} from "../users/objects/user.registered.email";
import { EmailService } from "../email/email.service";
import { OtpService } from "./otp/otp.service";
import { RequestUser } from "../common/utils/controller.decorator";
import { UsersService } from "../users/users.service";
import { EmailDto } from "../email/objects/email.dto";
import { OtpDto } from "./otp/otp.dto";
import { InjectModel } from "@nestjs/mongoose";
import { IOtp, Otp } from "./otp/otp.schema";
import { Model } from "mongoose";
import { ForgotPasswordOtpService } from "./forgot-password-otp/forgot-password-otp.service";
import { ForgotPasswordOtp } from "./forgot-password-otp/forgot-password-otp.schema";
import { ForgotPasswordOtpDto } from "./forgot-password-otp/forgot-password-otp.dto";
import { OtpIncorrectService } from "./otpIncorrect/otpIncorrect.service";
import { OtpIncorrect } from "./otpIncorrect/otpIncorrect.schema";
import { BlockedAccountService } from "./blockedAccounts/blockedAccounts.service";
import { IBlockedAccount } from "./blockedAccounts/blockedAccounts.schema";
import { OTP_RETRY_LIMIT, Status, ROLES_ACCESS_ACTION, BLOCKED_ACCOUNT_TYPE } from "../common/constants/enum";
import { RolesService } from "../roles/roles.service";
import { OtpIncorrectForgotPasswordService } from "./otpIncorrectForgotPassword/otpIncorrectForgotPassword.service";
import { OtpIncorrectForgotPassword } from "./otpIncorrectForgotPassword/otpIncorrectForgotPassword.schema";
//@UseGuards(JwtAuthGuard)
@Controller("auth/refresh")
export class AuthController {
constructor(
private authService: AuthService,
private emailService: EmailService,
private usersService: UsersService,
private otpService: OtpService,
private forgotPasswordOtpService: ForgotPasswordOtpService,
@InjectModel("Otp") private readonly otpModel: Model,
@InjectModel("ForgotPasswordOtp")
private readonly forgotPasswordotpModel: Model,
private readonly otpIncorrectService: OtpIncorrectService,
@InjectModel("OtpIncorrect") private readonly otpIncorrectModel: Model,
private readonly blockedAccountService: BlockedAccountService,
@InjectModel("BlockedAccount") private readonly blockedAccountModel: Model,
private rolesservice: RolesService,
private otpIncorrectForgotPasswordService: OtpIncorrectForgotPasswordService,
@InjectModel("OtpIncorrectForgotPassword") private readonly otpIncorrectForgotPasswordModel: Model,
) {}
@UseGuards(JwtAuthGuard)
@Post()
public async refresh(@Req() req): Promise {
return this.authService.createJwtPayLoad(req.user);
}
//Api For generating a secret and storing it in config.ts
@Get("secret")
async getSecret() {
const secret = speakeasy.generateSecret({ length: 20 });
return secret;
}
//Api For generating a 6 digit token using the secret
@Post("generate")
async getOtp(
@Req() req,
@Body() body: { email: string; firstName: string; lastName: string }
//@RequestUser() user
) {
debugger;
let email = body.email;
let firstName = body.firstName;
let lastName = body.lastName;
var token = speakeasy.totp({
secret: optSecret,
encoding: "base32",
});
let userToAttempt: any = await this.usersService.findOneByEmail(body.email);
//Check for existing users
if (!userToAttempt) {
let _blocked: any = await this.blockedAccountService.findOneByQuery({email: email, type: BLOCKED_ACCOUNT_TYPE.USER_REGISTRATION})
if(_blocked !== null){
throw new BadRequestException(BLOCKED_ACCOUNT_ERROR(email))
}
let query = { email: email };
let _otp: any = await this.otpService.findOneByQuery(query);
let currentTime: number = Date.now();
if (_otp) {
let k: any = await this.otpModel
.find({ email: email })
.sort({ updatedTime: -1 })
.limit(1);
if (k !== undefined) {
let diff = (currentTime - k[0].expiry) / 1000;
let updateTime: number = Date.now();
let createDto: any = {
token: token,
email: email,
firstName: firstName,
lastName: lastName,
expiry: updateTime + 15 * 60 * 1000,
};
if (diff > 0) {
let _otp: any = await this.otpService.create(createDto);
let _data =
"Otp sent to registered email " +
body.email +
" " +
"token:" +
token;
await this.emailService.sendEmail(
new OtpEmail(
new EmailDto({
to: body.email,
metaData: { email, token, firstName, lastName },
})
)
);
return success(_data);
} else {
let errorData = "Otp sent yet to expire in" + diff + "seconds";
throw new BadRequestException(OTP_NOT_EXPIRED(errorData));
}
}
}
//For users requesting for the first time
let updateTime: number = Date.now();
let createDto: any = {
token: token,
email: email,
expiry: updateTime + 15 * 60 * 1000,
};
let _otp1: any = await this.otpService.create(createDto);
await this.emailService.sendEmail(
new OtpEmail(
new EmailDto({
to: body.email,
metaData: { email, token, firstName, lastName },
})
)
);
let _data1 =
"Otp sent to registered email " + body.email + " " + "token:" + token;
return success(_data1);
}
throw new BadRequestException(EXISTS, "User exists");
}
}
@Get("secret")
async getSecret() {
const secret = speakeasy.generateSecret({ length: 20 });
return secret;
}
src/common/constants/config.ts
import * as dotenv from "dotenv";
dotenv.config();
export const optSecret = "HJCCU6Z7NNAS4UCHMJFHOI3YN47UYS2C";
{
"email": "[email protected]"
}
this.otpService.findOneByQuery(query);
var token = speakeasy.totp({
secret: optSecret,
encoding: "base32",
});
let createDto: any = {
token: token,
email: email,
expiry: updateTime + 15 * 60 * 1000,
};
let _otp1: any = await this.otpService.create(createDto);
await this.emailService.sendEmail(
new OtpEmail(
new EmailDto({
to: body.email,
metaData: { email, token, firstName, lastName },
})
)
);
if (diff > 0) {
let _otp: any = await this.otpService.create(createDto);
let _data =
"Otp sent to registered email " +
body.email +
" " +
"token:" +
token;
await this.emailService.sendEmail(
new OtpEmail(
new EmailDto({
to: body.email,
metaData: { email, token, firstName, lastName },
})
)
);
return success(_data);
}
else {
let errorData = "Otp sent yet to expire in" + diff + "seconds";
throw new BadRequestException(OTP_NOT_EXPIRED(errorData));
}
Step 2:
OTP Verification:
The Otp token and the email are sent as json in the body of the request for Otp verification in the api
POST
{
"email": "[email protected]",
"otp": "124583"
}
We will verify that the email sent does not already exist in our user's database.We will then validate the token.If the token is verified, then we update the Otp record with the verified field as true
and return the success data.
var tokenValidates = speakeasy.totp.verify({
secret: optSecret,
encoding: "base32",
token: otp,
window: 30,
});
if (tokenValidates) {
update = {
isVerified: true,
};
} else {
...
}
let updated = await this.otpService.edit(_otp.id, update, updateTime);
const _data = plainToClass(OtpDto, updated, {
excludeExtraneousValues: true,
});
return success(_data);
If the Otp is incorrect, we create a OtpIncorrect record and then count for the number of OtpIncorrect records bearing the user's email, then, check for the condition ,
count is greater than the Maximum retry limit.
If the condition is true, we will block the user by creating a record in the blocked list and return "user in the blocked list" error, else we will return "Otp error"
if(otpErrorCount > OTP_RETRY_LIMIT.MAXIMUM_OTP_RETRY_LIMIT){
let _blocked: any = await this.blockedAccountService.findOneByQuery({email: email, type: BLOCKED_ACCOUNT_TYPE.USER_REGISTRATION})
if(_blocked == null){
let _blocked: any = await this.blockedAccountService.create(createBlockedAccountDto);
//console.log('Your account is added to blocked list. BLOCKED LIST BLOCKED LIST BLOCKED LIST', _blocked);
await this.emailService.sendEmail(
new UserCycloanAccountBlockedEmail(
new EmailDto({
to: body.email,
metaData: { email, //firstName, lastName
},
})
)
);
console.log('Blocked Account email sent.................');
}
console.log('Your account is added to blocked list. BLOCKED LIST BLOCKED LIST BLOCKED LIST', _blocked);
throw new BadRequestException(BLOCKED_ACCOUNT_ERROR(email))
}
throw new BadRequestException(OTP_ERROR);
}
//Api for verifying a 6 digit token using the secret
@Post("otp/:emailOrMobile")
async verifyOTP(
@Param("emailOrMobile") emailOrMobile,
@Body() body: { otp: string; email: string }
) {
debugger;
let otp = body.otp;
let email = body.email;
let updateTime: number = Date.now();
let update = {};
let _blocked: any = await this.blockedAccountService.findOneByQuery({email: email, type: BLOCKED_ACCOUNT_TYPE.USER_REGISTRATION})
console.log('_blocked','_blocked .................._blocked',_blocked);
if(_blocked !== null){
throw new BadRequestException(BLOCKED_ACCOUNT_ERROR(email))
}
const userToAttempt: any = await this.usersService.findOneByEmail(email);
if (!userToAttempt) {
let query = { token: otp, email: email };
let _otp: any = await this.otpService.findOneByQuery(query);
switch (emailOrMobile) {
case "mobile":
update = { mobile: true };
break;
case "email":
var tokenValidates = speakeasy.totp.verify({
secret: optSecret,
encoding: "base32",
token: otp,
window: 30,
});
if (tokenValidates) {
update = {
isVerified: true,
};
} else {
let updateTime: number = Date.now();
let createDto: any = {
token: otp,
email: email
};
let createBlockedAccountDto: any = {
email: email,
type: BLOCKED_ACCOUNT_TYPE.USER_REGISTRATION
}
//if (diff > 0) {
let _otp: any = await this.otpIncorrectService.create(createDto);
console.log('otp tokennnnnnnnnn errorrrr', _otp)
let otpErrorCount: any = await this.otpIncorrectModel.count({ email: email});
console.log('Otp error count',otpErrorCount, 'If the attempts of failure are greater than 10, block this account. Create blockedCollection.')
if(otpErrorCount > OTP_RETRY_LIMIT.MAXIMUM_OTP_RETRY_LIMIT){
let _blocked: any = await this.blockedAccountService.findOneByQuery({email: email, type: BLOCKED_ACCOUNT_TYPE.USER_REGISTRATION})
if(_blocked == null){
let _blocked: any = await this.blockedAccountService.create(createBlockedAccountDto);
//console.log('Your account is added to blocked list. BLOCKED LIST BLOCKED LIST BLOCKED LIST', _blocked);
await this.emailService.sendEmail(
new UserCycloanAccountBlockedEmail(
new EmailDto({
to: body.email,
metaData: { email, //firstName, lastName
},
})
)
);
console.log('Blocked Account email sent.................');
}
console.log('Your account is added to blocked list. BLOCKED LIST BLOCKED LIST BLOCKED LIST', _blocked);
throw new BadRequestException(BLOCKED_ACCOUNT_ERROR(email))
}
throw new BadRequestException(OTP_ERROR);
}
break;
default:
throw new BadRequestException(UNKNOWN_PARAM(emailOrMobile));
}
let updated = await this.otpService.edit(_otp.id, update, updateTime);
const _data = plainToClass(OtpDto, updated, {
excludeExtraneousValues: true,
});
return success(_data);
}
}
Previously published at