2018-07-25 01:45:05 -04:00
|
|
|
const express = require('express');
|
|
|
|
const router = express.Router();
|
2018-07-26 17:34:47 -04:00
|
|
|
const config = require('config');
|
2018-01-15 15:29:32 -05:00
|
|
|
|
2018-07-26 19:40:42 -04:00
|
|
|
const ModelPath = '../models/';
|
|
|
|
const User = require(ModelPath + 'User.js');
|
|
|
|
const Invite = require(ModelPath + 'Invite.js');
|
|
|
|
|
|
|
|
const passport = require('passport');
|
|
|
|
|
2018-07-25 18:45:38 -04:00
|
|
|
const canonicalizeRequest = require('../util/canonicalize').canonicalizeRequest;
|
2018-07-27 14:08:02 -04:00
|
|
|
const requireAuth = require('../util/requireAuth');
|
2018-07-26 21:52:47 -04:00
|
|
|
const wrap = require('../util/wrap.js');
|
2018-07-25 01:45:05 -04:00
|
|
|
|
2018-07-25 18:45:38 -04:00
|
|
|
// Wraps passport.authenticate to return a promise
|
|
|
|
function authenticate(req, res, next) {
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
passport.authenticate('local', (err, user) => {
|
|
|
|
resolve(user);
|
|
|
|
})(req, res, next);
|
|
|
|
});
|
|
|
|
}
|
2018-07-25 01:45:05 -04:00
|
|
|
|
2018-07-25 18:45:38 -04:00
|
|
|
// Wraps passport session creation for async usage
|
|
|
|
function login(user, req) {
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
req.login(user, resolve);
|
|
|
|
});
|
|
|
|
}
|
2018-07-25 01:45:05 -04:00
|
|
|
|
2018-07-26 19:40:42 -04:00
|
|
|
// Check if the requested username is valid
|
|
|
|
async function validateUsername(username, sanitize) {
|
|
|
|
if (username.length > config.get('User.Username.maxLength'))
|
2018-07-25 01:45:05 -04:00
|
|
|
return {valid: false, message: 'Username too long.'};
|
|
|
|
|
2018-07-26 17:34:47 -04:00
|
|
|
const restrictedRegex = new RegExp(config.get('User.Username.restrictedChars'), 'g');
|
2018-07-26 19:40:42 -04:00
|
|
|
if (username !== sanitize(username).replace(restrictedRegex, ''))
|
2018-07-25 01:45:05 -04:00
|
|
|
return {valid: false, message: 'Username contains invalid characters.'};
|
|
|
|
|
2018-07-26 19:40:42 -04:00
|
|
|
const count = await User.countDocuments({username: username});
|
2018-07-25 01:45:05 -04:00
|
|
|
|
|
|
|
if (count !== 0)
|
|
|
|
return {valid: false, message: 'Username in use.'};
|
|
|
|
|
|
|
|
return {valid: true};
|
2017-10-12 12:50:02 -04:00
|
|
|
}
|
|
|
|
|
2018-07-25 01:45:05 -04:00
|
|
|
// Query the database for a valid invite code. An error message property is set if invalid.
|
|
|
|
async function validateInvite(code) {
|
|
|
|
const invite = await Invite.findOne({code: code});
|
|
|
|
|
|
|
|
if (!invite)
|
|
|
|
return {valid: false, message: 'Invalid invite code.'};
|
|
|
|
|
|
|
|
if (invite.used)
|
|
|
|
return {valid: false, message: 'Invite already used.'};
|
|
|
|
|
2018-07-27 14:23:23 -04:00
|
|
|
if (invite.expires != null && invite.expires < Date.now())
|
2018-07-25 01:45:05 -04:00
|
|
|
return {valid: false, message: 'Invite expired.'};
|
|
|
|
|
|
|
|
return {valid: true, invite: invite};
|
2017-10-11 12:55:46 -04:00
|
|
|
}
|
|
|
|
|
2018-07-25 01:45:05 -04:00
|
|
|
|
2018-07-26 19:40:42 -04:00
|
|
|
router.post('/register', canonicalizeRequest, wrap(async (req, res) => {
|
2018-07-25 01:45:05 -04:00
|
|
|
// Validate the invite and username
|
2018-07-25 18:45:38 -04:00
|
|
|
const [inviteStatus, usernameStatus] =
|
|
|
|
await Promise.all([
|
|
|
|
validateInvite(req.body.invite),
|
2018-07-26 19:40:42 -04:00
|
|
|
validateUsername(req.body.username, req.sanitize)
|
2018-07-25 18:45:38 -04:00
|
|
|
]);
|
2018-07-25 01:45:05 -04:00
|
|
|
|
2018-07-25 18:45:38 -04:00
|
|
|
// Error if validation failed
|
2018-07-25 01:45:05 -04:00
|
|
|
if (!inviteStatus.valid)
|
|
|
|
return res.status(422).json({'message': inviteStatus.message});
|
|
|
|
if (!usernameStatus.valid)
|
|
|
|
return res.status(422).json({'message': usernameStatus.message});
|
|
|
|
|
2018-07-25 18:45:38 -04:00
|
|
|
// Update the database
|
|
|
|
await Promise.all([
|
|
|
|
User.register({
|
|
|
|
username: req.body.username,
|
2018-07-26 19:40:42 -04:00
|
|
|
displayname: req.body.displayname,
|
2018-07-25 18:45:38 -04:00
|
|
|
scope: inviteStatus.invite.scope,
|
|
|
|
date: Date.now()
|
|
|
|
}, req.body.password),
|
2018-07-26 19:40:42 -04:00
|
|
|
Invite.updateOne({code: inviteStatus.invite.code}, {recipient: req.body.username, used: Date.now()})
|
2018-07-25 18:45:38 -04:00
|
|
|
]);
|
2018-07-25 01:45:05 -04:00
|
|
|
|
|
|
|
res.status(200).json({'message': 'Registration successful.'});
|
|
|
|
}));
|
2017-10-11 10:15:19 -04:00
|
|
|
|
2018-07-25 21:34:16 -04:00
|
|
|
router.post('/login', canonicalizeRequest, wrap(async (req, res, next) => {
|
2018-07-25 18:45:38 -04:00
|
|
|
// Authenticate
|
|
|
|
const user = await authenticate(req, res, next);
|
|
|
|
if (!user)
|
|
|
|
return res.status(401).json({'message': 'Unauthorized.'});
|
|
|
|
|
|
|
|
// Create session
|
|
|
|
await login(user, req);
|
|
|
|
|
2018-07-26 13:15:54 -04:00
|
|
|
// Set session vars
|
2018-07-26 19:40:42 -04:00
|
|
|
req.session.passport.displayname = user.displayname;
|
2018-07-25 18:45:38 -04:00
|
|
|
req.session.passport.scope = user.scope;
|
|
|
|
|
|
|
|
res.status(200).json({'message': 'Logged in.'});
|
|
|
|
}));
|
2017-10-11 10:15:19 -04:00
|
|
|
|
2018-07-26 13:15:54 -04:00
|
|
|
router.post('/logout', function (req, res) {
|
2017-10-18 13:31:08 -04:00
|
|
|
req.logout();
|
|
|
|
res.status(200).json({'message': 'Logged out.'});
|
2017-10-14 17:49:11 -04:00
|
|
|
});
|
|
|
|
|
2018-07-26 13:15:54 -04:00
|
|
|
router.get('/whoami', requireAuth(), (req, res) => {
|
2018-07-25 18:45:38 -04:00
|
|
|
res.status(200).json({
|
2018-07-26 19:40:42 -04:00
|
|
|
user: req.username,
|
|
|
|
display: req.displayname,
|
|
|
|
scope: req.scope,
|
|
|
|
key: req.key
|
2018-07-25 18:45:38 -04:00
|
|
|
});
|
2017-10-18 13:31:08 -04:00
|
|
|
});
|
2017-10-11 10:15:19 -04:00
|
|
|
|
|
|
|
module.exports = router;
|