chore: add otp based login
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
const User = require("../models/User");
|
||||
const { body, validationResult } = require("express-validator");
|
||||
const { rateLimit } = require("express-rate-limit");
|
||||
const bcrypt = require("bcryptjs");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const crypto = require("crypto");
|
||||
@@ -17,6 +18,9 @@ const fs = require("fs");
|
||||
const nodemailer = require("nodemailer");
|
||||
const Category = require("../models/Category");
|
||||
const Logger = require("../models/Logger");
|
||||
const cacheService = require("../plugins/cache");
|
||||
const { sms } = require("../plugins/sms");
|
||||
|
||||
////// variables
|
||||
const profileWidth = 500;
|
||||
const profileHeight = 500;
|
||||
@@ -25,11 +29,19 @@ const hostURL = "localhost:6688";
|
||||
const limit = 20;
|
||||
const select = "-password -token";
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs: 10 * 60 * 1000, // 10 minutes
|
||||
limit: 10,
|
||||
standardHeaders: "draft-7",
|
||||
legacyHeaders: false,
|
||||
});
|
||||
|
||||
/** @todo add private to query */
|
||||
|
||||
/** auth --------------------------------------------- */
|
||||
/////////////////////////////////////////////////////////////////////// login
|
||||
module.exports.login = [
|
||||
limiter,
|
||||
[
|
||||
body("username")
|
||||
.if((value, { req }) => {
|
||||
@@ -138,6 +150,170 @@ module.exports.login = [
|
||||
}
|
||||
},
|
||||
];
|
||||
/** otp --------------------------------------------- */
|
||||
/////////////////////////////////////////////////////////////////////// otp
|
||||
module.exports.otp = [
|
||||
limiter,
|
||||
[
|
||||
body("phone")
|
||||
.notEmpty()
|
||||
.withMessage(_sr["fa"].required.phone_number)
|
||||
.bail()
|
||||
.isMobilePhone("fa-IR")
|
||||
.withMessage(_sr["fa"].required.phone_number),
|
||||
// .isLength({ min: 4 })
|
||||
// .withMessage(_sr["fa"].min_char.min4),
|
||||
],
|
||||
async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty())
|
||||
return res.status(422).json({ validation: errors.mapped() });
|
||||
|
||||
const { phone } = req.body;
|
||||
const user = await User.findOne({ mobileNumber: phone });
|
||||
if (!user)
|
||||
return res.status(406).json({
|
||||
message: "شماره موبایل نامعتبر می باشد",
|
||||
});
|
||||
const existOtp = cacheService.get(`OTP:LOGIN:${phone}`);
|
||||
if (existOtp)
|
||||
return res.status(400).json({
|
||||
message: "کد به شماره موبایل شما ارسال شده است",
|
||||
ttl: (cacheService.getTtl(`OTP:LOGIN:${phone}`) - Date.now()) / 1000,
|
||||
});
|
||||
const otp = crypto.randomInt(10000, 99999);
|
||||
const message = `کد ورود شما ${otp} میباشد. استانداری لغو11`;
|
||||
/** save otp in cache */
|
||||
cacheService.set(`OTP:LOGIN:${phone}`, otp, 120);
|
||||
/** send sms to user */
|
||||
const smsResult = await sms(phone, message);
|
||||
|
||||
if (!smsResult.IsSuccessful) {
|
||||
console.log(smsResult);
|
||||
return res500(res, `مشکلی در ارسال پیامک پیش آمده است`);
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
message: "کد به شماره موبایل شما ارسال شد",
|
||||
});
|
||||
} catch (error) {
|
||||
return res500(res, error?.message);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
/////////////////////////////////////////////////////////////////////// verify otp
|
||||
module.exports.verifyOtp = [
|
||||
limiter,
|
||||
[
|
||||
body("phone")
|
||||
.notEmpty()
|
||||
.withMessage(_sr["fa"].required.phone_number)
|
||||
.bail()
|
||||
.isMobilePhone("fa-IR")
|
||||
.withMessage(_sr["fa"].required.phone_number),
|
||||
|
||||
body("code")
|
||||
.notEmpty()
|
||||
.withMessage(_sr["fa"].required.otp_code)
|
||||
.bail()
|
||||
.isLength({ min: 5, max: 5 })
|
||||
.withMessage(_sr["fa"].format.otp_length),
|
||||
],
|
||||
async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty())
|
||||
return res.status(422).json({ validation: errors.mapped() });
|
||||
|
||||
const { phone, code } = req.body;
|
||||
|
||||
// Check if the OTP exists in the cache
|
||||
const cachedOtp = cacheService.get(`OTP:LOGIN:${phone}`);
|
||||
if (!cachedOtp) {
|
||||
return res.status(400).json({
|
||||
message: "کد وارد شده منقضی شده است یا نامعتبر میباشد",
|
||||
});
|
||||
}
|
||||
|
||||
// Verify if the provided OTP matches the cached OTP
|
||||
if (cachedOtp.toString() !== code.trim()) {
|
||||
return res.status(422).json({
|
||||
validation: { code: { msg: "کد وارد شده نامعتبر میباشد" } },
|
||||
});
|
||||
}
|
||||
|
||||
// Find the user by phone number
|
||||
const user = await User.findOne({ mobileNumber: phone });
|
||||
if (!user) {
|
||||
return res.status(406).json({
|
||||
message: "شماره موبایل نامعتبر می باشد",
|
||||
});
|
||||
}
|
||||
|
||||
// Check if the user's registration is complete
|
||||
if (!user.registration_done) {
|
||||
return res.status(403).json({
|
||||
message: `اکانت شما غیرفعال می باشد لطفا با ادمین تماس بگیرید`,
|
||||
});
|
||||
}
|
||||
|
||||
// Create or update login logger
|
||||
const logger = await Logger.findOne({ user: user._id });
|
||||
if (logger) {
|
||||
await Logger.findOneAndUpdate(
|
||||
{ user: user._id },
|
||||
{ $set: { LastLogin: new Date() } },
|
||||
{ new: true }
|
||||
);
|
||||
} else {
|
||||
const newLogger = new Logger({
|
||||
user: user,
|
||||
LastLogin: new Date(),
|
||||
});
|
||||
await newLogger.save();
|
||||
}
|
||||
|
||||
// Check if the user already has a valid token
|
||||
if (user.token) {
|
||||
jwt.verify(
|
||||
user.token.replace(/^Bearer\s/, ""),
|
||||
secretKey,
|
||||
async (err, decoded) => {
|
||||
if (err || !decoded) {
|
||||
// Generate a new token if the existing one is invalid
|
||||
const token = await jwt.sign({ _id: user._id }, secretKey, {
|
||||
expiresIn: "2h",
|
||||
});
|
||||
user.token = token;
|
||||
await user.save();
|
||||
// Remove the OTP from the cache
|
||||
cacheService.del(`OTP:LOGIN:${phone}`);
|
||||
return res.json({ token: user.token });
|
||||
} else {
|
||||
// Remove the OTP from the cache
|
||||
cacheService.del(`OTP:LOGIN:${phone}`);
|
||||
return res.json({ token: user.token });
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Generate a new token if none exists
|
||||
const token = await jwt.sign({ _id: user._id }, secretKey, {
|
||||
expiresIn: "1h",
|
||||
});
|
||||
user.token = token;
|
||||
await user.save();
|
||||
// Remove the OTP from the cache
|
||||
cacheService.del(`OTP:LOGIN:${phone}`);
|
||||
return res.json({ token: user.token });
|
||||
}
|
||||
} catch (error) {
|
||||
return res500(res, error?.message);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
/////////////////////////////////////////////////////////////////////// logout
|
||||
module.exports.logout = [
|
||||
|
||||
Reference in New Issue
Block a user