1
0
mirror of https://github.com/Foltik/Shimapan synced 2024-11-10 23:53:31 -05:00
shimapan/app/routes/auth.js

173 lines
5.5 KiB
JavaScript
Raw Normal View History

2018-07-25 01:45:05 -04:00
'use strict';
2017-10-11 10:15:19 -04:00
2018-07-25 01:45:05 -04:00
const express = require('express');
const router = express.Router();
2017-10-11 10:15:19 -04:00
2018-07-25 01:45:05 -04:00
const User = require('../models/User.js');
const Invite = require('../models/Invite.js');
2017-10-11 10:15:19 -04:00
2018-07-25 01:45:05 -04:00
const passport = require('passport');
2018-07-25 01:45:05 -04:00
const async = require('async');
function memoize(fn) {
let cache = {};
return async function() {
let args = JSON.stringify(arguments);
cache[args] = cache[args] || fn.apply(this, arguments);
return cache[args];
};
}
2018-07-25 01:45:05 -04:00
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next);
};
// Normalizes, decomposes, and lowercases a utf-8 string
const canonicalizeUsername = username => username.normalize('NFKD').toLowerCase();
// Check if a canonical name is valid
async function validateUsername(username, canonicalName, sanitize) {
if (canonicalName.length > 36)
return {valid: false, message: 'Username too long.'};
if (canonicalName !== sanitize(canonicalName).replace(/\s/g, ''))
return {valid: false, message: 'Username contains invalid characters.'};
const count = await User.countDocuments({canonicalname: canonicalName});
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.'};
if (invite.exp < Date.now())
return {valid: false, message: 'Invite expired.'};
return {valid: true, invite: invite};
2017-10-11 12:55:46 -04:00
}
// Authenticates and creates the required session variables
function setupSession(username, req, res, cb) {
// Body needs to contain canonical name for proper authentication
2018-07-25 01:45:05 -04:00
req.body.canonicalname = canonicalizeUsername(req.body.username);
passport.authenticate('local')(req, res, function () {
req.session.save(function (err) {
if (!err) {
2018-01-15 17:13:35 -05:00
req.session.passport.username = username;
2018-07-25 01:45:05 -04:00
req.session.passport.canonicalname = canonicalizeUsername(username);
}
cb(err);
});
});
}
2018-07-25 01:45:05 -04:00
router.post('/register', asyncMiddleware(async (req, res, next) => {
const reqUsername = req.body.username;
const reqPassword = req.body.password;
const reqInviteCode = req.body.invite;
const canonicalName = canonicalizeUsername(reqUsername);
// memoized verification functions
const checkInvite = memoize(async () => validateInvite(reqInviteCode));
const checkUsername = memoize(async () => validateUsername(reqUsername, canonicalName, req.sanitize));
// Validate the invite and username
const [inviteStatus, usernameStatus] = await Promise.all([checkInvite(), checkUsername()]);
// Make sure invite was valid
if (!inviteStatus.valid)
return res.status(422).json({'message': inviteStatus.message});
// Make sure the username was valid
if (!usernameStatus.valid)
return res.status(422).json({'message': usernameStatus.message});
// Create the new user object
const user = new User({
username: reqUsername,
canonicalname: canonicalName,
scope: inviteStatus.invite.scope,
date: Date.now()
2017-10-11 12:55:46 -04:00
});
2018-07-25 01:45:05 -04:00
// memoized password setting, user saving, and invite updating functions
const updateInvite = memoize(async () =>
Invite.updateOne({code: inviteStatus.invite.code}, {recipient: canonicalName, used: Date.now()}));
const setPassword = memoize(async () => user.setPassword(reqPassword));
const saveUser = memoize(async () => {
await setPassword();
return user.save();
});
// Set the password, save the user, and update the invite code
await Promise.all([updateInvite(), setPassword(), saveUser()]);
res.status(200).json({'message': 'Registration successful.'});
}));
2017-10-11 10:15:19 -04:00
2017-10-18 13:31:08 -04:00
router.post('/login', function (req, res, next) {
// Take 'username' from the form and canonicalize it for authentication.
2018-07-25 01:45:05 -04:00
req.body.canonicalname = canonicalizeUsername(req.body.username);
async.waterfall([
function (cb) {
passport.authenticate('local', function(err, user, info) {
cb(err, user, info);
})(req, res, next);
},
function (user, info, cb) {
if (!user)
cb(info);
else
req.logIn(user, cb);
},
function (cb) {
2018-01-15 17:13:35 -05:00
req.session.passport.username = req.body.username;
2018-07-25 01:45:05 -04:00
req.session.passport.canonicalname = canonicalizeUsername(req.body.username);
cb();
}
], function (err) {
if (err)
res.status(401).json({'message': err});
else
res.status(200).json({'message': 'Login successful.'});
});
2017-10-11 10:15:19 -04:00
});
2017-10-18 13:31:08 -04:00
router.get('/logout', function (req, res) {
req.logout();
res.status(200).json({'message': 'Logged out.'});
2017-10-14 17:49:11 -04:00
});
router.get('/session', function (req, res) {
2018-01-15 17:13:35 -05:00
console.log(req.session.passport);
if (req.session.passport.canonicalname) {
User.findOne({canonicalname: req.session.passport.canonicalname}, function (err, user) {
res.status(200).json({
username: user.username,
canonicalname: user.canonicalname,
scope: user.scope
});
});
} else {
res.status(401).json({'message': 'Unauthorized.'});
}
2017-10-18 13:31:08 -04:00
});
2017-10-11 10:15:19 -04:00
module.exports = router;