All files / ragchat-api/controllers Auth.js

93.44% Statements 214/229
70.96% Branches 22/31
100% Functions 8/8
93.44% Lines 214/229

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 2311x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x     2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 4x 4x 4x 1x 1x 3x 3x 4x 1x 1x 2x 2x 2x 4x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x       4x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 6x 6x 6x 5x 5x 4x 1x 1x 1x 1x 1x 6x 3x 3x 6x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x     1x 2x 2x 2x 2x     2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 1x 1x 8x 8x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x    
import { createAccessToken, createPasswordToken, createRefreshToken, createVerificationToken, deleteRefreshToken, verifyRefreshToken } from "../utils/handleTokens.js";
import { sendEmail } from "../utils/handleMailer.js";
import { findOneAndUpdate, findOneRecord, updateOneRecord } from "../utils/handleDB.js";
import User from "../models/User.js";
import bcrypt from 'bcrypt';
import { hideConfidentialFields } from "../utils/handleSchemes.js";
 
/** *******************************************************
 * SEND VERIFICATION MAIL
 */
export const sendVerificationEmail = async (req, res, next) => {
  try {
    // create verification token
    const verificationToken = createVerificationToken(req.document);
 
    let subject = "[RagChat] Account Verification";
    let to = req.document.email;
    let link = `${process.env.FRONTEND_URL}/signup/${verificationToken}`;
    let html = `<p>Hi<p><br><p>Please click on the following <a href="${link}">link</a> to process the password reset. 
    This Token is valid for ${process.env.PASSWORD_TOKEN_TTL}.</p>
    <p>${link}</p>
    <p>${verificationToken}</p>
         <br><p>If you did not request this, please ignore this email.</p>`;
    await sendEmail({ to, subject, html });
    // return msg
    return res.status(201).json({ message: 'Check your emails for the verification link.' });
  } catch (error) {
    next(error);
  }
};
 
 
/** *******************************************************
 * CONFIRM VERIFICATION
 */
export const confirmVerification = async (req, res, next) => {
  try {
    // req.document.verified = true;
    // const updatedUser = await updateOneRecord(req.document);
    const updatedUser = await findOneAndUpdate(User, { email: req.body.email }, { verified: true });
    // remember document but remove confidential info
    const document = hideConfidentialFields(User, updatedUser);
    return res.json({ message: 'Account successfully verified. You can now login.' });
  } catch (error) {
    next(error);
  }
};
 
 
/** *******************************************************
 * LOGIN
 * check for matching credentials
 * 
 * return accessToken & refreshToken
 */
export const login = async (req, res, next) => {
  let foundUser;
  // check credentials
  try {
    // search for matching document
    performance.mark('login:start');
    foundUser = await findOneRecord(User, { email: req.body.email }, '+password');
 
    // wrong login name
    if (!foundUser) {
      return res.status(401).json({ message: 'Unknown combination of login credentials.' });
    }
 
    // unverified account
    if (!foundUser.verified) {
      return res.status(401).json({ message: 'Your account is still unverified. Check your emails for the verification link.' });
    }
 
    // check for correct password
    performance.mark('bcryptComparePassword:start');
    if (await bcrypt.compare(req.body.password, foundUser.password)) {
      performance.mark('bcryptComparePassword:end');
      // remember document but remove confidential info
      const user = hideConfidentialFields(User, foundUser);
 
      // create jsonwebtoken
      performance.mark('createAccessToken:start');
      const accessToken = createAccessToken({ id: user._id, role: user.role });
      performance.mark('createAccessToken:end');
      performance.mark('createRefreshToken:start');
      const refreshToken = await createRefreshToken({ id: user._id });
      performance.mark('createRefreshToken:end');
      if (refreshToken == null) return res.status(500).json({ message: 'Error creating refresh token' });
 
      performance.mark('login:end');
      // success
      return res
        .cookie('refreshToken', refreshToken, { httpOnly: true, sameSite: 'none', secure: true })
        .json({ message: 'Successfully logged in', document: user, accessToken });
    } else {
      // wrong password
      return res.status(401).json({ message: 'Unknown combination of login credentials' });
    }
  } catch (error) {
    console.error('login error: ', error);
    next(error);
  }
};
 
 
/** *******************************************************
 * RENEW ACCESS TOKEN
 * refresh access token with the refresh token
 * 
 * return new accessToken and refreshToken
 */
export const renewAccessToken = async (req, res, next) => {
  try {
    // get token from cookie
    const refreshToken = req.cookies.refreshToken;
    if (!refreshToken) return res.status(401).json({ message: 'Refresh token is missing. Consider to re-login' });
    // verify token
    const user = await verifyRefreshToken(refreshToken);
    if (!user) {
      return res.status(403).json({ message: 'Refresh token is invalid' });
    }
    // create & return 
    const accessToken = createAccessToken({ id: user._id, role: user.role });
    return res.json({ message: 'Access token refreshed', accessToken });
  } catch (error) {
    next(error);
  }
};
 
 
/** *******************************************************
 * LOGOUT
 * deletes refresh token from DB
 */
export const logout = async (req, res, next) => {
  try {
    // delete 
    console.log("🚀 ~ logout ~ req.cookies.refreshToken:", req.cookies.refreshToken);
    if (req.cookies.refreshToken) await deleteRefreshToken(req.cookies.refreshToken);
    // return msg
    return res.status(200).json({ message: 'See you soon.' });
  } catch (error) {
    next(error);
  }
 
 
};
 
 
/**
 * REQUEST PASSWORD RESET
 * sets a token and sends this to the user
 */
export const requestPasswordReset = async (req, res, next) => {
  let foundUser;
  try {
    // search for matching document
    foundUser = await findOneRecord(User, { email: req.body.email }, '+password');
    // user found
    if (foundUser) {
      // create token
      const passwordToken = createPasswordToken(foundUser);
 
      // store token in user document
      foundUser.resetPasswordToken = passwordToken;
      await updateOneRecord(foundUser);
 
      // send info to user
      try {
        let subject = "Password Reset Token";
        let to = foundUser.email;
        let link = `${process.env.FRONTEND_URL}/reset_password/${passwordToken}`;
        let html = `<p>Hi<p><br><p>Please click on the following <a href="${link}">link</a> to process the password reset. This Token is valid for ${process.env.PASSWORD_TOKEN_TTL}.</p>
        <p>${link}</p>
        <p>${passwordToken}</p>
         <br><p>If you did not request this, please ignore this email.</p>`;
        await sendEmail({ to, subject, html });
 
      } catch (error) {
        next(error);
      }
    }
    // fin
    return res.json({ message: `If the email **${req.body.email}** is correct you will receive an eMail with further instructions.` });
 
  } catch (error) {
    next(error);
  }
};
 
/** *******************************************************
 * PASSWORD RESET
 * resets the password and removes the token
 */
export const passwordReset = async (req, res, next) => {
  try {
    // set new password & remove token
    req.document.password = req.body.password;
    req.document.resetPasswordToken = undefined;
    // save
    const updatedRecord = await updateOneRecord(req.document);
    return res.json({ message: 'Password successfully reset. You can now login.' });
  } catch (error) {
    next(error);
  }
};
 
 
 
/**
 * VERIFY ACCESS RIGHTS
 * check if user is alllowed to access route
 */
export const gateKeeper = async (req, res, next) => {
  // admins are allowed to access anything
  if (global.currentUserRole >= 4) return next();
 
  // FEATURE 
  // - check for custom field role (which has to be created via settings before)
  // - create a access config, which exports an array of allowed roles for each route
  // - fetch array of called route an compare 
  // const allowed = [];
  // if (allowed.includes(pb.authStore.model.role)) {
  //   return next();
  // }
 
  // deny access for others
  return res.status(403).json({ message: 'Access Forbidden' });
};