const User = require("../../models/User"); const config = require("../../config"); const secret = require("../../secret"); const { authenticateEndpoint } = require("./../../common/auth/authfunctions"); // TODO: Might want to use something else (https://blog.benpri.me/blog/2019/01/13/why-you-shouldnt-be-using-bcrypt-and-scrypt/) const bcrypt = require("bcrypt"); const mongoose = require("mongoose"); const { body, param, validationResult } = require("express-validator"); const express = require("express"); const jwt = require("jsonwebtoken"); const rateLimit = require("express-rate-limit"); const app = express.Router(); const createAccountLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour window max: 10, // start blocking after 10 requests }); mongoose.connect(config.mongoUrl, {useNewUrlParser: true, useUnifiedTopology: true}); app.get("/account/create/info", async (req, res) => { let requiresCode = false; if (config.restrictions) { const restrictions = config.restrictions.signup; if (restrictions && restrictions.specialCode) { requiresCode = true; } } res.json({ error: false, message: "SUCCESS_ACCOUNT_CREATE_INFO_FETCH", requiresSpecialCode: requiresCode }); }); app.post("/account/create", [ createAccountLimiter, body("username").not().isEmpty().trim().isLength({ min: 3, max: 32 }).isAlphanumeric(), body("email").not().isEmpty().isEmail().normalizeEmail(), body("password").not().isEmpty().isLength({ min: 8, max: 128 }), body("specialCode").optional().isLength({ min: 12, max: 12 }).isAlphanumeric() ], async (req, res) => { if (!config.policies.allowAccountCreation) return res.status(403).json({ error: true, message: "ERROR_FORBIDDEN_BY_POLICY" }); try { const errors = validationResult(req); if (!errors.isEmpty()) { res.status(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA", errors: errors.array() }); return; } if (config.restrictions) { const restrictions = config.restrictions.signup; if (restrictions && restrictions.specialCode) { const passedSpecialCode = req.body.specialCode; const specialCode = restrictions.specialCode; if (passedSpecialCode && specialCode) { if (specialCode !== passedSpecialCode) { res.status(401).json({ error: true, message: "ERROR_REQUEST_SPECIAL_CODE_MISSING", errors: [{ msg: "No specialCode passed", param: "specialCode", location: "body" }] }); return false; } } else { res.status(401).json({ error: true, message: "ERROR_REQUEST_SPECIAL_CODE_MISSING", errors: [{ msg: "No specialCode passed", param: "specialCode", location: "body" }] }); return false; } } } const username = req.body.username; const existingUser = await User.findByUsername(username); if (existingUser) { res.status(400).json({ error: true, message: "ERROR_REQUEST_USERNAME_EXISTS", errors: [{ value: username, msg: "Username exists", param: "username", location: "body" }] }); return; } const unhashedPassword = req.body.password; const email = req.body.email; const startingRole = "USER"; const hashedPassword = await bcrypt.hash(unhashedPassword, config.bcryptRounds); const user = await User.create({ username, email, password: hashedPassword, role: startingRole }); const userObject = await user.getPublicObject(); if (config.logAccountCreation) console.log("users: user created", userObject); res.status(200).json({ error: false, message: "SUCCESS_USER_CREATED", user: userObject }); } catch (e) { console.error("Internal server error", e); res.status(500).json({ error: true, message: "INTERNAL_SERVER_ERROR" }); return; } }); app.post("/token/create", [ createAccountLimiter, body("username").not().isEmpty().trim().isAlphanumeric(), body("password").not().isEmpty() ], async (req, res) => { if (!config.policies.allowLogin) return res.status(403).json({ error: true, message: "ERROR_FORBIDDEN_BY_POLICY" }); const errors = validationResult(req); if (!errors.isEmpty()) { res.status(400).json({ error: true, message: "ERROR_REQUEST_LOGIN_INVALID" }); return; } const username = req.body.username; const existingUser = await User.findByUsername(username); if (!existingUser) { res.status(403).json({ error: true, message: "ERROR_REQUEST_LOGIN_INVALID" }); return; } const password = req.body.password; let passwordCheck; try { passwordCheck = await bcrypt.compare(password, existingUser.password); } catch(e) { passwordCheck = false; } if (!passwordCheck) { res.status(403).json({ error: true, message: "ERROR_REQUEST_LOGIN_INVALID" }); return; } jwt.sign({ username }, secret.jwtPrivateKey, { expiresIn: config.tokenExpiresIn }, async (err, token) => { if (err) { res.status(500).json({ error: true, message: "INTERNAL_SERVER_ERROR" }); return; } const userObject = await existingUser.getPublicObject(); if (config.logAccountCreation) console.log("users: token created", userObject); res.status(200).json({ error: false, message: "SUCCESS_TOKEN_CREATED", user: userObject, token }); }); }); app.get("/current/info", authenticateEndpoint(async (req, res, user) => { const userObject = await user.getPublicObject(); res.status(200).json({ error: false, message: "SUCCESS_USER_DATA_FETCHED", user: userObject }); }, undefined, 0)); app.get("/user/:userid/info", [ param("userid").not().isEmpty().trim().escape().isLength({ min: 24, max: 24 }) ], authenticateEndpoint(async (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { res.status(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA", errors: errors.array() }); return; } const userid = req.params.userid; if (!userid) { res.sendStatus(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA" }); return; } const otherUser = await User.findById(userid); res.status(200).json({ error: false, message: "SUCCESS_USER_DATA_FETCHED", user: await otherUser.getPublicObject(), }); }, undefined, config.roleMap.USER)); module.exports = app;