Ctrl K
ComunidadGuías…

server/api/UserRoute.js

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
const jwt = require("jsonwebtoken");
const request = require("../modules/safeRequest");
const rateLimit = require("express-rate-limit");

const UserController = require("../controllers/UserController");
const verifyUser = require("../modules/verifyUser");
const verifyToken = require("../modules/verifyToken");
const userResponse = require("../modules/userResponse");

const apiLimiter = (max = 10) => {
  return rateLimit({
    windowMs: 60 * 1000, // 1 minute
    max,
  });
};

module.exports = (app) => {
  const userController = new UserController();

  const tokenizeUser = ((user, res) => {
    const userToken = {
      id: user.id,
      email: user.email,
      sessionVersion: user.sessionVersion,
    };
    jwt.sign(userToken, app.settings.encryptionKey, {
      expiresIn: 2592000 // a month
    }, (err, token) => {
      if (err) return res.status(400).send(err);
      const tokenizedResponse = userResponse(user);
      tokenizedResponse.token = token;
      return res.status(200).send(tokenizedResponse);
    });
  });

  /*
  ** [MASTER] Route to get all the users
  */
  app.get("/user", verifyToken, (req, res) => {
    if (!req.user.admin) {
      return res.status(401).send({ error: "Not Authorized" });
    }

    return userController.findAll()
      .then((users) => {
        return res.status(200).send(users);
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to get the user by id
  */
  app.get("/user/:id", verifyUser, (req, res) => {
    userController.findById(req.params.id)
      .then((user) => {
        return res.status(200).send(userResponse(user));
      })
      .catch((error) => {
        if (error === "404") return res.status(404).send("The user is not found");
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route for creating a new user
  */
  app.post("/user", async (req, res) => {
    if (!req.body.email || !req.body.password) return res.status(400).send("no email or password");

    if (app.settings.signupRestricted === "1") {
      try {
        const areThereAnyUsers = await userController.areThereAnyUsers();
        if (areThereAnyUsers) {
          return res.status(401).send("Signups are restricted");
        }
      } catch (e) {
        // do nothing
      }
    }

    const icon = req.body.name.substring(0, 2);
    const userObj = {
      name: req.body.name,
      email: req.body.email,
      password: req.body.password,
      active: false,
      icon: icon.toUpperCase(),
    };

    return userController.createUser(userObj)
      .then((newUser) => {
        return tokenizeUser(newUser, res);
      })
      .catch((error) => {
        if (error.message === "409") return res.status(409).send("The email is already used");
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to delete a user
  */
  app.delete("/user/:id", verifyToken, (req, res) => {
    if (req.user.id !== parseInt(req.params.id, 10)) return res.status(401).send("Unauthorised user");
    return userController.deleteUser(req.user.id)
      .then(() => {
        return res.status(200).send({});
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to process invitations
  */
  app.post("/user/invited", async (req, res) => {
    if (!req.body.email || !req.body.password) return res.status(400).send("no email or password");
    if (!req.body.inviteToken) return res.status(400).send("no invite token");

    // check if the invite token is valid and has a team_id
    const { inviteToken } = req.body;
    const decodedToken = await jwt.verify(inviteToken, app.settings.encryptionKey);
    if (!decodedToken?.team_id) return res.status(400).send("invalid invite token");

    const icon = req.body.name.substring(0, 2);

    const userObj = {
      email: req.body.email,
      password: req.body.password,
      name: req.body.name,
      icon: icon.toUpperCase(),
      active: true,
    };

    return userController.createUser(userObj, { createOwnedTeam: false })
      .then((newUser) => {
        return tokenizeUser(newUser, res);
      })
      .catch((error) => {
        if (error.message === "409") return res.status(409).send("The email is already used");
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to verify new users (not used atm)
  */
  app.get("/user/:id/verify", verifyUser, (req, res) => {
    return userController.update(req.params.id, { "active": true })
      .then((user) => {
        return res.status(200).send({
          id: user.id, name: user.name, active: user.active, token: req.token
        });
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to login users
  */
  app.post("/user/login", apiLimiter(10), (req, res) => {
    if (!req.body.email || !req.body.password) return res.status(400).send("fields are missing");
    let user = {};
    return userController.login(req.body.email, req.body.password)
      .then((data) => {
        if (data?.method_id) {
          return data;
        }

        user = data;
        return userController.update(user.id, { lastLogin: new Date() });
      })
      .then((data) => {
        if (data?.method_id) {
          return res.status(200).json(data);
        }
        return tokenizeUser(user, res);
      })
      .catch((error) => {
        if (error.message === "401") {
          return res.status(401).json({ message: "The credentials are incorrect" });
        }

        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to relog users based on their tokens
  */
  app.post("/user/relog", verifyToken, (req, res) => {
    return userController.update(req.user.id, { lastLogin: new Date() })
      .then(() => {
        return res.status(200).send(req.user);
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to modify users' fields
  */
  app.put("/user/:id", verifyUser, (req, res) => {
    if (!req.body || !req.params.id) return res.status(400).send("Missing fields");
    const profileUpdate = {};
    ["name", "icon", "tutorials"].forEach((field) => {
      if (req.body[field] !== undefined) profileUpdate[field] = req.body[field];
    });
    return userController.update(req.params.id, profileUpdate)
      .then((user) => {
        return res.status(200).send(userResponse(user));
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to get all team invites for the user
  */
  app.get("/user/:id/teamInvites", verifyToken, (req, res) => {
    if (req.user.id !== parseInt(req.params.id, 10)) return res.status(401).send("unauthorised user");
    return userController.findById(req.params.id)
      .then((user) => {
        return userController.getTeamInvitesByUser(user.email);
      })
      .then((invites) => {
        return res.status(200).send(invites);
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to send a new feedback email
  */
  app.post("/user/feedback", (req, res) => {
    let { from } = req.body;
    if (!from) from = "Anonymous";

    const message = {
      "from": { "email": "info@depomo.com" },
      "subject": `${from} left us a feedback - ${req.body.email}`,
      "personalizations": [{
        "to": [{ email: "info@depomo.com" }]
      }],
      "content": [
        {
          "type": "text/plain",
          "value": req.body.data
        }
      ]
    };

    const options = {
      method: "POST",
      url: `${app.settings.sendgridHost}/mail/send`,
      body: JSON.stringify(message),
      headers: {
        authorization: `Bearer ${app.settings.sendgridKey}`,
        "content-type": "application/json"
      }
    };

    return request(options)
      .then(() => res.status(200).send({}))
      .catch(() => res.status(400).send({ error: "Feedback could not be sent. Try again." }));
  });
  // --------------------------------------

  /*
  ** Route to request a password reset email
  */
  app.post("/user/password/reset", apiLimiter(10), (req, res) => {
    userController.requestPasswordReset(req.body?.email).catch(() => {
      // Keep account lookup and mail failures private while handling the rejection.
    });
    return res.status(200).send({ "success": true });
  });
  // --------------------------------------

  /*
  ** Route to change the password with a password reset link info
  */
  app.put("/user/password/change", apiLimiter(10), (req, res) => {
    return userController.changePassword(req.body)
      .then((result) => {
        return res.status(200).send(result);
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to check if there are any users registered in the database
  */
  app.get("/app/users", (req, res) => {
    return userController.areThereAnyUsers()
      .then((result) => {
        return res.status(200).send({ areThereAnyUsers: result });
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to request a new email verification email
  */
  app.post("/user/:id/email/verify", apiLimiter(10), verifyUser, (req, res) => {
    if (!req.body.email) return res.status(400).send("Missing fields");

    return userController.requestEmailUpdate(req.params.id, req.body.email)
      .then(() => {
        return res.status(200).send({ "success": true });
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to update the email of a user with the verification link info
  */
  app.put("/user/:id/email/update", verifyUser, (req, res) => {
    if (!req.body.token) return res.status(400).send("Missing fields");
    return userController.updateEmail(req.params.id, req.body.token)
      .then((user) => {
        return res.status(200).send(userResponse(user));
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to set up 2FA app for a user
  */
  app.post("/user/:id/2fa/app", apiLimiter(5), verifyToken, (req, res) => {
    return userController.setup2faApp(req.user.id)
      .then((qrUrl) => {
        return res.status(200).send({ qrUrl });
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to verify the 2FA app for a user
  */
  app.post("/user/:id/2fa/app/verify", apiLimiter(5), verifyToken, (req, res) => {
    return userController.verify2faApp(req.user.id, req.body)
      .then((backupCodes) => {
        return res.status(200).send({ backupCodes });
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to validate 2fa login method
  */
  app.post("/user/:id/2fa/:method_id/login", apiLimiter(5), (req, res) => {
    let user;
    return userController.validate2FaLogin(req.params.id, req.body.method_id, req.body.token)
      .then((data) => {
        user = data;
        return userController.update(user.id, { lastLogin: new Date() });
      })
      .then(() => {
        return tokenizeUser(user, res);
      })
      .catch((error) => {
        if (error.message === "401") return res.status(401).send("The credentials are incorrect");
        if (error.message === "404") return res.status(404).send("The email is not registreded");
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to get all 2fa methods for a user
  */
  app.get("/user/:id/2fa", verifyToken, (req, res) => {
    return userController.get2faMethods(req.user.id)
      .then((methods) => {
        return res.status(200).send(methods);
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------

  /*
  ** Route to remove a 2fa method
  */
  app.delete("/user/:id/2fa/:method_id", verifyToken, (req, res) => {
    return userController.remove2faMethod(req.user.id, req.params.method_id, req.body.password)
      .then((result) => {
        return res.status(200).send({ removed: result });
      })
      .catch((err) => {
        return res.status(400).send(err);
      });
  });
  // -------------------------------------

  /*
  ** Route to pin a dashboard
  */
  app.post("/user/:id/pin", verifyToken, (req, res) => {
    if (!req.body.project_id) return res.status(400).send("Missing fields");

    return userController.pinDashboard(req.body.project_id, req.user.id)
      .then((pinnedDashboard) => {
        return res.status(200).send(pinnedDashboard);
      })
      .catch(() => res.status(403).send("Cannot pin this dashboard."));
  });
  // -------------------------------------------

  /*
  ** Route to unpin a dashboard
  */
  app.delete("/user/:id/pin/:pin_id", verifyToken, (req, res) => {
    return userController.unpinDashboard(req.params.pin_id, req.user.id)
      .then(() => {
        return res.status(200).send({ removed: true });
      });
  });
  // -------------------------------------------

  return (req, res, next) => {
    next();
  };
};