diff --git a/app/controllers/establishment.controller.js b/app/controllers/establishment.controller.js index 46c7ba4..53247a2 100644 --- a/app/controllers/establishment.controller.js +++ b/app/controllers/establishment.controller.js @@ -1123,6 +1123,85 @@ exports.requestOTPForLogin = async (req, res) => { message: "Internal Server Error", }); } +}; + +exports.verifyOTPForLogin = async (req, res) => { + try { + const { registered_email, otp } = req.body; + + // Validation + if (!registered_email || !otp) { + return res.status(400).json({ + status: "failed", + message: "Email and OTP are required" + }); + } + + // Find user from either model + let user = await EstablishmentUser.scope("withSensitive").findOne({ + where: { email: registered_email } + }); + + if (!user) { + user = await User.scope("withSensitive").findOne({ + where: { email: registered_email, is_active: true } + }); + } + + // User validation + if (!user) { + return res.status(404).json({ + status: "failed", + message: "User not found" + }); + } + + if (!user.login_otp) { + return res.status(404).json({ + status: "failed", + message: "Verification code not found or invalid user" + }); + } + + // Check OTP expiry + if (new Date() > new Date(user.login_otp_expires_at)) { + return res.status(400).json({ + status: "failed", + message: "Verification code has expired. Please request a new one" + }); + } + + // Verify OTP + const isOtpValid = await bcrypt.compare(otp, user.login_otp); + if (!isOtpValid) { + return res.status(400).json({ + status: "failed", + message: "Invalid verification code" + }); + } + + // Clear OTP after successful verification + await user.update({ + login_otp: null, + login_otp_expires_at: null, + }); + + const safeEmail = sanitizeForLog(registered_email); + logger.info(`OTP verification successful for user: ${safeEmail}`); + + return res.status(200).json({ + status: "success", + message: "Email verified successfully!", + }); + + } catch (err) { + logger.error(`OTP verification error: ${err.message}`); + logger.error(`Stack trace: ${err.stack}`); + return res.status(500).json({ + status: "failed", + message: "Internal server error" + }); + } }; const GENERIC_ERROR_MSG = diff --git a/app/routes/routes.js b/app/routes/routes.js index f0979ba..ef89a19 100644 --- a/app/routes/routes.js +++ b/app/routes/routes.js @@ -3079,6 +3079,39 @@ router.get("/manufacturing/getManufacturingMonthlyOverview", [verifySignature, v */ router.post("/auth/request-otp",[verifySignature], establishmentController.requestOTPForLogin); +/** + * @swagger + * /api/auth/verify-otp: + * post: + * summary: Verify OTP to signin + * tags: [Admin And Establishments User Auth] + * security: + * - appSignature: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - registered_email + * - otp + * properties: + * registered_email: { type: string, example: "contact@abcindustries.com" } + * otp: { type: string, example: "123456" } + * responses: + * 200: + * description: Otp verified successfully + * 400: + * description: Invalid OTP + * 404: + * description: User or OTP not found + * 500: + * description: Server error + */ +router.post("/auth/verify-otp",[verifySignature], establishmentController.verifyOTPForLogin); + + diff --git a/server.js b/server.js index 40d09dc..77e68ef 100644 --- a/server.js +++ b/server.js @@ -241,7 +241,8 @@ app.use((req, res, next) => { "/api/forgot-password/request-otp", "/api/forgot-password/verify-otp", "/api/csrf-token", - "/api/auth/request-otp" + "/api/auth/request-otp", + "/api/auth/verify-otp" ]; if (csrfExcludedPaths.includes(req.path)) {