forked from hippoz/brainlet
192 lines
No EOL
6.2 KiB
JavaScript
Executable file
192 lines
No EOL
6.2 KiB
JavaScript
Executable file
const User = require('../../models/User');
|
|
const config = require('../../config');
|
|
const secret = require('../../secret');
|
|
|
|
const { authenticateEndpoint } = require('./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, query, param, validationResult } = require('express-validator');
|
|
const express = require('express');
|
|
const jwt = require('jsonwebtoken');
|
|
|
|
const app = express.Router();
|
|
mongoose.connect(config.mongoUrl, {useNewUrlParser: true, useUnifiedTopology: true});
|
|
|
|
const rateLimit = require("express-rate-limit");
|
|
|
|
const createAccountLimiter = rateLimit({
|
|
windowMs: 60 * 60 * 1000, // 1 hour window
|
|
max: 10, // start blocking after 5 requests
|
|
message: "You are being rate limited"
|
|
});
|
|
|
|
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 })
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
res.status(400).json({ error: true, message: 'ERROR_REQUEST_INVALID_DATA', errors: errors.array() });
|
|
return;
|
|
}
|
|
|
|
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
|
|
});
|
|
|
|
res.status(200).json({
|
|
error: false,
|
|
message: 'SUCCESS_USER_CREATED',
|
|
user: {
|
|
_id: user._id,
|
|
username: user.username,
|
|
email: user.email,
|
|
role: user.role,
|
|
permissionLevel: config.roleMap[user.role]
|
|
}
|
|
});
|
|
} 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) => {
|
|
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: '3h' }, (err, token) => {
|
|
if (err) {
|
|
res.status(500).json({
|
|
error: true,
|
|
message: 'INTERNAL_SERVER_ERROR'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// TODO: Ugly fix for setting httponly cookies
|
|
if (req.body.alsoSetCookie) {
|
|
res.cookie('token', token, {
|
|
maxAge: 3 * 60 * 60 * 1000, httpOnly: true, domain: config.address,
|
|
});
|
|
}
|
|
|
|
res.status(200).json({
|
|
error: false,
|
|
message: 'SUCCESS_TOKEN_CREATED',
|
|
user: {
|
|
_id: existingUser._id,
|
|
username: existingUser.username,
|
|
email: existingUser.email,
|
|
role: existingUser.role,
|
|
permissionLevel: config.roleMap[existingUser.role]
|
|
},
|
|
token
|
|
});
|
|
});
|
|
});
|
|
|
|
app.get('/current/info', authenticateEndpoint((req, res, user) => {
|
|
res.status(200).json({
|
|
error: false,
|
|
message: 'SUCCESS_USER_DATA_FETCHED',
|
|
user: {
|
|
_id: user._id,
|
|
username: user.username,
|
|
email: user.email,
|
|
role: user.role,
|
|
permissionLevel: config.roleMap[user.role],
|
|
token: req.cookies.token // TODO: Passing the token like this is *terribly* insecure
|
|
},
|
|
});
|
|
}, undefined, 0));
|
|
|
|
app.get('/user/:userid/info', [
|
|
param('userid').not().isEmpty().trim().escape().isLength({ min: 24, max: 24 })
|
|
], authenticateEndpoint(async (req, res, user) => {
|
|
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: {
|
|
_id: otherUser._id,
|
|
username: otherUser.username,
|
|
role: otherUser.role
|
|
},
|
|
});
|
|
}));
|
|
|
|
app.post('/browser/token/clear', authenticateEndpoint((req, res, user) => {
|
|
res.clearCookie('token');
|
|
res.sendStatus(200);
|
|
}));
|
|
|
|
module.exports = app; |