2021-03-04 21:28:02 +02:00
|
|
|
const User = require("../../models/User");
|
|
|
|
const config = require("../../config");
|
|
|
|
const secret = require("../../secret");
|
2021-03-14 20:14:32 +02:00
|
|
|
const { authenticateEndpoint } = require("./../../common/auth/authfunctions");
|
2021-03-04 21:28:02 +02:00
|
|
|
|
|
|
|
// 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");
|
|
|
|
|
2021-03-14 20:14:32 +02:00
|
|
|
const app = express.Router();
|
2021-03-04 21:28:02 +02:00
|
|
|
const createAccountLimiter = rateLimit({
|
|
|
|
windowMs: 60 * 60 * 1000, // 1 hour window
|
2021-09-06 18:53:53 +03:00
|
|
|
max: 10, // start blocking after 10 requests
|
2021-03-04 21:28:02 +02:00
|
|
|
});
|
|
|
|
|
2021-03-14 20:14:32 +02:00
|
|
|
mongoose.connect(config.mongoUrl, {useNewUrlParser: true, useUnifiedTopology: true});
|
|
|
|
|
2021-03-04 21:28:02 +02:00
|
|
|
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) => {
|
2021-08-21 21:30:02 +03:00
|
|
|
if (!config.policies.allowAccountCreation) return res.status(403).json({ error: true, message: "ERROR_FORBIDDEN_BY_POLICY" });
|
|
|
|
|
2021-03-04 21:28:02 +02:00
|
|
|
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,
|
2021-10-21 22:22:35 +03:00
|
|
|
role: startingRole
|
2021-03-04 21:28:02 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
const userObject = await user.getPublicObject();
|
|
|
|
|
2021-10-21 22:15:39 +03:00
|
|
|
if (config.logAccountCreation) console.log("users: user created", userObject);
|
2021-03-04 21:28:02 +02:00
|
|
|
|
|
|
|
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) => {
|
2021-08-21 21:30:02 +03:00
|
|
|
if (!config.policies.allowLogin) return res.status(403).json({ error: true, message: "ERROR_FORBIDDEN_BY_POLICY" });
|
|
|
|
|
2021-03-04 21:28:02 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-08-21 21:30:02 +03:00
|
|
|
jwt.sign({ username }, secret.jwtPrivateKey, { expiresIn: config.tokenExpiresIn }, async (err, token) => {
|
2021-03-04 21:28:02 +02:00
|
|
|
if (err) {
|
|
|
|
res.status(500).json({
|
|
|
|
error: true,
|
|
|
|
message: "INTERNAL_SERVER_ERROR"
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const userObject = await existingUser.getPublicObject();
|
|
|
|
|
2021-10-21 22:15:39 +03:00
|
|
|
if (config.logAccountCreation) console.log("users: token created", userObject);
|
2021-03-04 21:28:02 +02:00
|
|
|
|
|
|
|
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",
|
2021-08-21 21:30:02 +03:00
|
|
|
user: userObject
|
2021-03-04 21:28:02 +02:00
|
|
|
});
|
|
|
|
}, 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));
|
|
|
|
|
2021-03-26 16:59:24 +02:00
|
|
|
module.exports = app;
|