Controllers_SecurityController.js

const logger = require("../Logger");
const JsonResponse = require("./JsonResponse");
const crypto = require("crypto");
const config = require("../config");
const jwt = require("jsonwebtoken");
const ResetPasswordMail = require("../sms-mail/ResetPasswordMail");
const { v4: uuidv4 } = require("uuid");
const { Vonage } = require("@vonage/server-sdk");
const CrudController = require("./CrudController");
const ConnectedUser = require("../database/dto/ConnectedUser");
const vonage = new Vonage({
  apiKey: "31e8eb0c",
  apiSecret: "Lu0Ab5gRUxiNR0lJ",
});
const resetPasswordValidator = require("../Validators/ResetPasswordValidator"); 

/**
 * @extends Myintranet.Controllers.CrudController
 * @memberOf Myintranet.Controllers
 * @inheritDoc
 */
class SecurityController extends CrudController {
  attachUser(request, response, next) {
    let db = request.db;
    if (request.headers["authorization"] !== undefined) {
      let token = request.headers["authorization"].split(" ")[1];
      if (token.trim().length <= 10) {
        // token found in the browser dosent much criteria
        logger.error("Token found but incoherent");
        response
          .status(401)
          .json(new JsonResponse(false, {}, "Token found but incoherent"));
      } else {
        jwt.verify(token, config.secret, function (err, decoded) {
          if (err) {
            logger.error(err.message);
            response
              .status(401)
              .json(new JsonResponse(false, err, err.message));
          } else {
            db.User.findOne({
              where: { id: decoded.id, login: decoded.email },
              include: [db.Consultant],
            })
              .then((user) => {
                if (user === null) {
                  // token found in the browser and the user dosen't exists in database
                  logger.error("User not found with id : " + decoded.id);
                  response
                    .status(401)
                    .json(
                      new JsonResponse(
                        false,
                        {},
                        "User not found with id : " + decoded.id
                      )
                    );
                } else {
                  request.connectedUser = user;
                  request.user = user;
                  next();
                }
              })
              .catch((error) => {
                logger.error(error.message);
                response
                  .status(401)
                  .json(new JsonResponse(false, error, error.message));
              });
          }
        });
      }
    } else {
      //TODO : LOG IP ADDR ans some security stuff to be able in the futur to ban an ip or a mac address .... or to know who is the caller to a secured root whithout token
      logger.error("No authentication data");
      response
        .status(401)
        .json(
          new JsonResponse(
            false,
            ["No authentication data"],
            "No authentication data"
          )
        );
    }
  }

  login(request, response) {
    let db = request.db;
    let { email, password } = request.body;
    logger.info(`SecurityController.login  Authentication of ${email} `);
    db.User.findOne({ where: { login: email } })
      .then((foundUser) => {
        if (!foundUser) {
          logger.error(
            `SecurityController.login ${email} Not found in database`
          );
          response.json(
            new JsonResponse(
              false,
              [],
              `${email} N'existe pas dans la base de donnée`
            )
          );
        } else {
          // console.log(foundUser)
          let hash = crypto.createHmac("sha512", foundUser.salt);
          hash.update(password);
          let hasedPassword = hash.digest("hex");
          // console.log(hasedPassword, foundUser.password)
          if (hasedPassword == foundUser.password) {
            let U = foundUser.toJSON();
            U.token = this.getAuthToken(U);
            if(U.type==="Admin"){
              foundUser.getEmployeurs().then(employeurs=>{
                U.employeurs = employeurs;
                response.json(new JsonResponse(true, new ConnectedUser(U), ""));
              })
            }else{
              response.json(new JsonResponse(true, new ConnectedUser(U), ""));
            }
      

          } else {
            logger.warn(
              `${email} tries to connect with false password : ${password}`
            );
            response.json(new JsonResponse(false, [], "Password incorrect"));
          }
        }
      })
      .catch((err) => {
        // throw err;
        logger.error(err.message);
        response.json(new JsonResponse(false, err, err.message));
      });
  }

  getAuthToken(user) {
    //TODO: add environnement url and check it in the security to make the token only valid for the environnement its created on
    //todo need to check the email of the user
    let { id, login } = user;
    return jwt.sign({ id, email: login }, config.secret, {
      expiresIn: 3600 * 24 * 365,
    });
  }

  sendResetPasswordEmail(user, cb) {
    let reesetmail = new ResetPasswordMail(
      user,
      "Reinitialisation du mot de passe de My-Intranet"
    );
    return reesetmail.send();
  }

  isPhone(data) {
    // console.log("isPhone :" + new RegExp("^(?:(?:\\+|00)33|0)\\s*[1-9](?:\\d{2}){4}$").test(data),data)
    return new RegExp("^(?:(?:\\+|00)33|0)\\s*[1-9](?:\\d{2}){4}$").test(data);
  }

  async SendResetPasswordSMS(user, db, cb) {
    // console.log(user.phone);
    // const from = "Vonage APIs";//'0619629393'
    // const to = user.phone
    let url = config.siteUrl + "/api/securedLink/" + user.link.token + "/reset";
    const from = "MY-INTRANET";
    const to = user.phone.replace(/^0/, "33").replace("+", "");
    const text = `Bonjour ${user.firstname} ${user.lastname}
Réinitialisez votre mot de passe en cliquant sur le lien ci-dessous. Par mesure de sécurité, ce lien expirera dans une heure.

${url}

Si vous n'avez pas besoin de réinitialiser votre mot de passe, merci d'ignorer ce message.
        `;
    const t = await db.sequelize.transaction();
    try {
      let resp = await vonage.sms.send({ to, from, text });
      await db.Action.create(
        {
          UtilisateurId: user.id,
          meta: JSON.stringify(user),
          type: "mise a jour",
          text: `<b>${user.firstname} ${
            user.lastname
          }</b> a Fait une demande de reiinitialisation de mot de passe par SMS a : <b>${to}</b> le : ${new Date(
            Date.now()
          ).toLocaleString("fr-FR")}`,
        },
        { transaction: t }
      );
      await db.SMSHisto.create(
        {
          msisdn: from,
          text,
          to,
          type: "from My intranet",
          UtilisateurId: user.id,
          orphan: false,
        },
        { transaction: t }
      );

      if (resp.messages[0]["status"] === "0") {
        await t.commit();
        cb && cb();
      } else {
        logger.error(
          `Message failed with error: ${resp.messages[0]["error-text"]}`
        );
        await t.rollback();
      }
    } catch (e) {
      logger.error(`Message failed with error: ${e.message}`);
      await t.rollback();
      throw e;
    }
  }

  sendResetPasswordMail(request, response) {
    let db = request.db;
    if (this.isPhone(request.body.email)) {
      db.User.findOne({
        where: {
          phone: {
            [db.Sequelize.Op.like]:
              "%" + request.body.email.replace(/^33/, "").replace(/^0/, ""),
          },
        },
      }).then((user) => {
        if (!user) {
          //user not found
          // reponse positive pour eviter la verification
          response.json(
            new JsonResponse(false, { email: request.body.email }, "SMS Sent 0")
          );
        } else {
          //user found in database
          db.SecuredLink.create({
            token: uuidv4(),
            UtilisateurId: user.id,
            reason: "reset password",
          }).then((link) => {
            user.link = link;
            this.SendResetPasswordSMS(user, db, () => {
              response.json(
                new JsonResponse(
                  true,
                  { email: request.body.email },
                  "Email Sent"
                )
              );
            });
          });
        }
      });
    } else {
      db.User.findOne({ where: { login: request.body.email } }).then((user) => {
        if (!user) {
          // let userReset = user.toJSON()
          // reponse positive pour eviter la verification (un hacker peut utiliser cette methode pour demanser le reset de mot de passe)
          // meme si l'utilisateur n'existe pas
          response.json(new JsonResponse(true, [], "Email Sent"));
        } else {
          //user found in database
          db.SecuredLink.create({ token: uuidv4(), UtilisateurId: user.id })
            .then((link) => {
              user.link = link;
              this.sendResetPasswordEmail(user)
                .then((info) => {
                  response.json(
                    new JsonResponse(
                      true,
                      { email: request.body.email },
                      "Email Sent"
                    )
                  );
                })
                .catch((err) => {
                  logger.error("Error sending mail " + err.message);
                  response.json(new JsonResponse(false, err, err.message));
                });
            })
            .catch((err) => {
              logger.error(
                "Unable to create secured Link for reset password " +
                  err.message
              );
              response.json(
                new JsonResponse(
                  false,
                  err,
                  "Unable to create secured Link for reset password " +
                    err.message
                )
              );
            });
        }
      });
    }
  }

  // resetUserPassword(request, response) {
  //     let db = request.db;
  //     db.User.findOne({include: {model: db.SecuredLink, where: {token: request.params.id}}}).then(user => {
  //         if (!user) {
  //             response.json(new JsonResponse(false, {}, "User not found "));
  //         } else {
  //             let hach = this.getPasswordAndSalt(request.body.password);
  //             user.password = hach.password;
  //             user.salt = hach.salt;
  //             user.save().then(u => {
  //                 user.securedLinks[0].destroy().then(res => {
  //                     response.json(new JsonResponse(true, user, "User found with id : " + user.id));
  //                 }).catch(err => {
  //                     logger.error("Error Wile resetting password " + err.message);
  //                     response.json(new JsonResponse(false, err, err.message));
  //                 });
  //             }).catch(err => {
  //                 logger.error("Error Wile Saving the new password " + err.message);
  //                 response.json(new JsonResponse(false, err, err.message));
  //             });
  //         }
  //     });
  // }


  // resetUserPassword(request, response) {
  //   let db = request.db;
  //   db.User.findOne({include: {model: db.SecuredLink, where: {token: request.params.id}}}).then(user => {
  //     if (!user) {
  //       response.json(new JsonResponse(false, {}, "User not found "));
  //     } else {
  //       let hach = this.getPasswordAndSalt(request.body.password);
  //       user.password = hach.password;
  //       user.salt = hach.salt;
  //       user.save().then(u => {
  //         user.securedLinks[0].destroy().then(res => {
  //           response.json(new JsonResponse(true, user, "User found with id : " + user.id));
  //         }).catch(err => {
  //           logger.error("Error Wile resetting password " + err.message);
  //           response.json(new JsonResponse(false, err, err.message));
  //         });
  //       }).catch(err => {
  //         logger.error("Error Wile Saving the new password " + err.message);
  //         response.json(new JsonResponse(false, err, err.message));
  //       });
  //     }
  //   });
  // }

  resetUserPassword(request, response) {
    let db = request.db;
    let { newPass, confirmPass } = request.body;
    let validationResult = resetPasswordValidator.validate(request.body);

    if (validationResult.length > 0) {
      response.json(
        new JsonResponse(
          false,
          validationResult,
          "Validation error: " + validationResult[0].message
        )
      );
    } else {
      if (newPass === confirmPass) {
        db.User.findOne({
          include: {
            model: db.SecuredLink,
            where: { token: request.params.id },
          },
        }).then((user) => {
          if (!user) {
            response.json(new JsonResponse(false, {}, "User not found "));
          } else {
            let hach = this.getPasswordAndSalt(request.body.password);
            user.password = hach.password;
            user.salt = hach.salt;
            user
              .save()
              .then((u) => {
                user.securedLinks[0]
                  .destroy()
                  .then((res) => {
                    response.json(
                      new JsonResponse(
                        true,
                        user,
                        "User found with id: " + user.id
                      )
                    );
                  })
                  .catch((err) => {
                    logger.error(
                      "Error while resetting password: " + err.message
                    );
                    response.json(new JsonResponse(false, err, err.message));
                  });
              })
              .catch((err) => {
                logger.error(
                  "Error while saving the new password: " + err.message
                );
                response.json(new JsonResponse(false, err, err.message));
              });
          }
        });
      } else {
        logger.error("Le mot de passe et sa confirmation ne sont pas identiques");
        response.json(
            new JsonResponse(
              false,
              "",
              "Le mot de passe et sa confirmation ne sont pas identiques"
            )
          );
      }
    }
  }
}

module.exports = new SecurityController();