Untitled

 avatar
Anis
plain_text
2 months ago
11 kB
1
Indexable
Never
const sendOTPVerification = require("../../services/sendOTPVerification");
const User = require("../../models/User");
const {
  exclude,
  generateSignature,
  generatePassword,
  generateSalt,
  validatePassword,
  generateVerificationCode,
  checkOptValidity,
  checkTimeValidity,
} = require("../../helpers");
const bcrypt = require("bcrypt");
const moment = require("moment");

const EXPIRE_TIME = 60 * 60 * 24 * 29 * 1000; // 29 Days

// Create New User
const userRegister = async (userInputs) => {
  try {
    const { phone, password, provider, country } = userInputs;

    let existingUser = await User.findOne({ phone });

    if (existingUser) {
      // Case 1: User exists, phone unverified, delete the existing user
      if (existingUser?.phone_verified === 0) {
        await User.deleteOne({ phone: phone });
        existingUser = null;
      }

      // Case 2: User exists, phone verified, and phone provider
      if (
        existingUser?.phone_verified === 1 &&
        existingUser?.provider === "phone" &&
        provider === "phone"
      ) {
        return { status: false, message: "This phone number already exists!" };
      }

      // Case 3: User exists, phone verified, different provider (prevent manual registration)
      // if (
      //   existingUser?.phone_verified === 1 &&
      //   existingUser?.provider !== "phone" &&
      //   provider === "phone"
      // ) {
      //   return {
      //     status: false,
      //     message: `Please Try, Sign Up with ${capitalizeFirstLetter(
      //       existingUser.provider
      //     )}!`,
      //   };
      // }
    }

    const salt = await generateSalt();
    const hashedPassword = await generatePassword(password, salt);

    let newUser = existingUser;

    if (!newUser) {
      // Create a new user if not exists
      newUser = new User({
        phone,
        password: hashedPassword,
        salt,
        provider,
        country,
      });

      await newUser.save();
    }

    if (provider === "phone") {
      // Case 4: User is registered with email provider
      const otp = generateVerificationCode(6);
      const hashedOtp = await bcrypt.hash(otp, 10);

      // Save verify code
      await newUser.updateOne({ verify_code: hashedOtp });

      // Send OTP
      const otpResponse = await sendOTPVerification(phone, otp);

      if (otpResponse === "0") {
        return {
          status: true,
          message: "OTP sent successfully!",
        };
      } else if (otpResponse === "6") {
        return {
          status: false,
          message: "Your are provided wrong number!",
        };
      } else {
        return {
          status: false,
          message: "Something went wrong!",
        };
      }
    }

    ///////////===== Other Login Providers Not Handled Right Now  =====///////////

    // Case 5: User registered via social provider
    const accessToken = await generateSignature(
      {
        phone: newUser.phone,
      },
      60 * 60 * 24 * 30 // 30 Days
    );

    const refreshToken = await generateSignature(
      {
        phone: newUser.phone,
      },
      60 * 60 * 24 * 60 // 30 Days
    );

    const user = exclude(newUser.toObject(), [
      "_id",
      "__v",
      "password",
      "salt",
      "verify_code",
      "provider",
      "forget_code",
      "createdAt",
      "updatedAt",
      "favorites",
    ]);

    return {
      status: true,
      message: "Login successfully!",
      data: {
        accessToken,
        refreshToken,
        expiresIn: new Date().setTime(new Date().getTime() + EXPIRE_TIME),
        ...user,
        role: "user",
      },
    };
  } catch (error) {
    console.error("Error", error);
    if (error.code === 11000 && error.keyPattern && error.keyPattern.email) {
      throw new Error("Email is already exist!");
    }
    throw new Error("Failed to create user!");
  }
};

// User Sign In with Phone
const signIn = async (userInfo) => {
  try {
    const { phone, password } = userInfo;
    let existingUser = await User.findOne({ phone });

    if (!existingUser) {
      return {
        status: false,
        message: "Your credentials are incorrect!",
      };
    } else {
      const validPassword = await validatePassword(
        password,
        existingUser.password,
        existingUser.salt
      );

      if (validPassword) {
        const accessToken = await generateSignature(
          {
            phone: existingUser.phone,
          },
          60 * 60 * 24 * 30 // 30 Days
        );

        const refreshToken = await generateSignature(
          {
            phone: existingUser.phone,
          },
          60 * 60 * 24 * 60 // 60 Days
        );

        const user = exclude(existingUser.toObject(), [
          "_id",
          "__v",
          "password",
          "salt",
          "verify_code",
          "provider",
          "forget_code",
          "createdAt",
          "updatedAt",
        ]);

        return {
          status: true,
          message: "Login successfully!",
          data: {
            accessToken,
            refreshToken,
            expiresIn: new Date().setTime(new Date().getTime() + EXPIRE_TIME),
            ...user,
            role: "user",
          },
        };
      } else {
        return {
          status: false,
          message: "Your credentials are incorrect!",
        };
      }
    }
  } catch (error) {
    console.error("Error in Sign In:", error);
    throw new Error("Failed to user Sign In!");
  }
};

// Verify Phone
const verifyPhoneOtp = async (optInfo) => {
  try {
    const { phone, otp } = optInfo;
    const findUser = await User.findOne({ phone });

    if (!findUser) {
      return { status: false, message: "OTP is expired or incorrect!" };
    }

    const hashedOtp = findUser.verify_code;
    let isValidOtp = false;
    const isValidTime = checkTimeValidity(findUser.updatedAt);

    if (!!hashedOtp) {
      isValidOtp = await checkOptValidity(otp, hashedOtp);
    }

    if (isValidOtp && isValidTime) {
      const userData = {
        phone_verified: 1,
        status: 1,
        verify_code: null,
      };

      const verifiedUser = await User.findByIdAndUpdate(
        findUser._id,
        userData,
        { new: true }
      );

      const accessToken = await generateSignature(
        {
          phone: verifiedUser.phone,
        },
        60 * 60 * 24 * 30 // 30 Days
      );

      const refreshToken = await generateSignature(
        {
          phone: verifiedUser.phone,
        },
        60 * 60 * 24 * 60 // 60 Days
      );

      const user = exclude(verifiedUser.toObject(), [
        "password",
        "salt",
        "verify_code",
        "provider",
        "forget_code",
        "createdAt",
        "updatedAt",
        "_id",
        "__v",
      ]);

      return {
        status: true,
        data: {
          ...user,
          accessToken,
          refreshToken,
          expiresIn: new Date().setTime(new Date().getTime() + EXPIRE_TIME),
          role: "user",
        },
        message: "Otp validated & sign in successfully!",
      };
    } else {
      return { status: false, message: "OTP is expired or incorrect!" };
    }
  } catch (error) {
    console.error("Error", error);
    throw new Error("Failed");
  }
};

// Get Access Token
const getAccessToken = async (userInfo) => {
  try {
    const accessToken = await generateSignature(
      {
        email: userInfo.email,
        role: userInfo.role,
      },
      60 * 60 * 24 // 1 Day
    );

    const refreshToken = await generateSignature(
      {
        email: userInfo.email,
        role: userInfo.role,
      },
      60 * 60 * 24 * 7 // 7 Days
    );

    return {
      status: true,
      message: "Access Token refresh successfully!",
      data: {
        accessToken,
        refreshToken,
        expiresIn: new Date().setTime(new Date().getTime() + EXPIRE_TIME),
      },
    };
  } catch (error) {
    console.error("Error in Sign In:", error);
    throw new Error("Failed to Sign In user");
  }
};

// Resend OTP (Handle With Context - 1. Verify Code 2. Forget Code)
const resendOTP = async (userInfo) => {
  try {
    const { phone, context } = userInfo;

    const existingUser = await User.findOne({ phone });

    if (!existingUser) {
      return { status: false, message: "User not found!" };
    }

    if (existingUser.phone_verified !== 0 && context === "verify_code") {
      return { status: false, message: "Phone is already verified!" };
    }

    const otp = generateVerificationCode(6);
    const hashedOtp = await bcrypt.hash(otp, 10);

    // 2 minutes block time for each request
    if (!existingUser.resend_otp_block_timestamp) {
      const time = moment().add(2, "minutes").unix();

      if (context === "verify_code") {
        await existingUser.updateOne({
          verify_code: hashedOtp,
          resend_otp_block_timestamp: time,
        });
      } else {
        await existingUser.updateOne({
          forget_code: hashedOtp,
          resend_otp_block_timestamp: time,
        });
      }
    } else {
      const currentTime = moment().unix();
      const isBlocked = moment(currentTime).isBefore(
        existingUser.resend_otp_block_timestamp
      );

      if (isBlocked) {
        return {
          status: false,
          message: "Try again after waiting for up to 2 minutes!",
        };
      } else {
        const time = moment().add(2, "minutes").unix();

        if (context === "verify_code") {
          await existingUser.updateOne({
            verify_code: hashedOtp,
            resend_otp_block_timestamp: time,
          });
        } else {
          await existingUser.updateOne({
            forget_code: hashedOtp,
            resend_otp_block_timestamp: time,
          });
        }
      }
    }

    // Send OTP
    const otpResponse = await sendOTPVerification(phone, otp);

    if (otpResponse === "0") {
      return {
        status: true,
        message: "New OTP sent successfully!",
      };
    } else if (otpResponse === "6") {
      return {
        status: false,
        message: "Your are provided wrong number!",
      };
    } else {
      return {
        status: false,
        message: "Something went wrong!",
      };
    }
  } catch (error) {
    console.error("Error in Resend Verification Email:", error);
    throw new Error("Failed to resend verification email");
  }
};
Leave a Comment