Untitled

 avatar
unknown
plain_text
a year ago
6.7 kB
6
Indexable
import React from "react";
import Users from "@/models/users";
import CryptoJS from "crypto-js";
import jwt, { Secret } from "jsonwebtoken";
import { Request, Response } from "express";
import dayjs from "dayjs";
import { render } from "@react-email/render";
import {
  REQUIRED_VALUE_EMPTY,
  UNKNOWN_ERROR_OCCURRED,
} from "@/common/constants";
import { encryptKey, signKey } from "@/common/config";
import OtpEmailTemplate from "@/common/templates/OtpEmailTemplate";
import { sendOtpEmail } from "@/common/utils/sendOtpEmail";
import { sendOtpSms } from "@/common/utils/sendOtpSMS";

// Function to render the email to HTML
const renderOtpEmail = (otp: string) => {
  return render(React.createElement(OtpEmailTemplate, { otp }));
};

const IS_DEV_MODE = process.env.NODE_ENV === "development";

export const login = async (req: Request, res: Response) => {
  const { email, password, rememberMe } = req.body;

  if (!email || !password) {
    return res.status(400).json({
      error: true,
      message: REQUIRED_VALUE_EMPTY,
    });
  }

  try {
    const user = await Users.findOne({ email });
    if (!user || user.deletedAt) {
      throw new Error("Account does not exist in our system");
    }
    if (user.blockedAt) {
      throw new Error("Account was prohibited to login due to violations");
    }

    const decryptPassword = CryptoJS.AES.decrypt(
      user.password as string,
      encryptKey as string
    );
    const originalPassword = decryptPassword.toString(CryptoJS.enc.Utf8);

    if (originalPassword !== password) {
      throw new Error("Email or password is invalid");
    }

    if (IS_DEV_MODE) {
      const token = jwt.sign(
        { _id: user._id, email: user.email, role: user.role, rememberMe: rememberMe },
        signKey as string,
        { expiresIn: "1d" }
      );
      return res.json({
        error: false,
        message: null,
        item: token,
        itemCount: null,
      });
    }

    // If not in development mode, proceed with OTP.
    // Correct password, generate OTP
    const otp = Math.floor(100000 + Math.random() * 900000).toString(); // 6-digit OTP
    user.otp = otp;
    user.otpExpiresAt = new Date(Date.now() + 10 * 60000); // OTP expires in 10 minutes
    await user.save();

    return res.json({
      error: false,
      otpRequired: true,
      methodSelectionRequired: true,
      message: "OTP sent to email. Please verify to continue.",
    });
  } catch (err: any) {
    const message = err.message || UNKNOWN_ERROR_OCCURRED;
    return res.status(500).json({
      error: true,
      message: message,
    });
  }
};

export const verify = async (req: Request, res: Response) => {
  const bearerHeader = req.headers["authorization"];
  if (bearerHeader) {
    const bearer = bearerHeader.split(" ");
    const bearerToken = bearer[1];
    try {
      const { _id, email, expiresIn, rememberMe }: any = jwt.verify(
        bearerToken as string,
        signKey as Secret
      );
      
      const user = await Users.findOne({
        email,
      }).populate('organisation')
      const isTokenExpired = dayjs().isAfter(expiresIn);
      if (user && !isTokenExpired) {
        res.json({
          error: false,
          message: null,
          item: {
            token: req.params.token,
            _id: user._id,
            email: user.email,
            role: user.role,
            organisation: user.organisation
          },
        });
      } else {
        if(rememberMe) {
          const token = jwt.sign(
            { _id: user._id, email: user.email, role: user.role, rememberMe: rememberMe },
            signKey as string,
            { expiresIn: "1d" }
          );
          res.json({
            error: false,
            message: null,
            item: {
              token: token,
              _id: user._id,
              email: user.email,
              role: user.role,
              organisation: user.organisation
            },
          });
        } else {
          res.json({
            error: true,
            message: "Authentication is expired or invalid",
            items: null,
            itemCount: null,
          });
        }
      }
    } catch (error) {
      res.json({
        error: true,
        message: String(error),
        items: null,
        itemCount: null,
      });
    }
  } else {
    res.json({
      error: true,
      message: REQUIRED_VALUE_EMPTY,
      items: null,
      itemCount: null,
    });
  }
};

export const verifyOtp = async (req: Request, res: Response) => {
  const { email, otp } = req.body;

  try {
    const user = await Users.findOne({
      email,
      otp,
      otpExpiresAt: { $gt: Date.now() },
    });

    if (!user) {
      return res.json({
        error: true,
        message: "OTP is invalid or expired",
      });
    }

    // OTP is correct, and not expired
    user.otp = ""; // clear OTP
    user.otpExpiresAt = undefined; // clear OTP expiry
    await user.save();

    // Now create the JWT token since OTP is verified
    const token = jwt.sign(
      { id: user._id, email: user.email, role: user.role },
      signKey as string,
      { expiresIn: "1d" }
    );

    // Send a success response
    return res.json({
      error: false,
      message: "OTP verified, user logged in successfully",
      item: token,
      itemCount: null,
    });
  } catch (error) {
    // Catch any other errors and respond accordingly
    return res.json({
      error: true,
      message: String(error),
      items: null,
      itemCount: null,
    });
  }
};

export const selectOtpMethod = async (req: Request, res: Response) => {
  const { email, method } = req.body;

  try {
    const user = await Users.findOne({ email });
    if (!user) {
      throw new Error("User not found");
    }

    // Check if OTP is available
    const otp = user.otp;
    if (!otp) {
      throw new Error("OTP not found or expired");
    }

    if (method === "sms") {
      // Check if phone number is available
      if (!user.phoneNumber) {
        throw new Error("Phone number not available");
      }
      await sendOtpSms(user.phoneNumber, otp);
    } else {
      const htmlEmailContent = renderOtpEmail(otp);
      await sendOtpEmail(email, htmlEmailContent);
    }

    return res.json({
      error: false,
      message: `OTP sent via ${method}. Please verify to continue.`,
    });
  } catch (error) {
    return res.status(500).json({
      error: true,
      message: String(error),
    });
  }
};
Editor is loading...
Leave a Comment