mirror of
https://github.com/Foltik/Shimapan
synced 2024-11-13 00:26:55 -05:00
Properly async-ify registration code
This commit is contained in:
parent
1fdb121260
commit
ce99433afc
@ -14,8 +14,8 @@ var InviteSchema = mongoose.Schema({
|
|||||||
exp: Date
|
exp: Date
|
||||||
});
|
});
|
||||||
|
|
||||||
InviteSchema.methods.use = function(canonicalname, cb) {
|
/*InviteSchema.methods.use = function(canonicalname, cb) {
|
||||||
return this.model('Invite').updateOne({code: this.code}, {recipient: canonicalname, used: Date.now()}, cb);
|
return this.model('Invite').updateOne({code: this.code}, {recipient: canonicalname, used: Date.now()}, cb);
|
||||||
};
|
};*/
|
||||||
|
|
||||||
module.exports = mongoose.model('Invite', InviteSchema);
|
module.exports = mongoose.model('Invite', InviteSchema);
|
@ -1,118 +1,129 @@
|
|||||||
var express = require('express');
|
'use strict';
|
||||||
var router = express.Router();
|
|
||||||
|
|
||||||
var User = require('../models/User.js');
|
const express = require('express');
|
||||||
var Invite = require('../models/Invite.js');
|
const router = express.Router();
|
||||||
|
|
||||||
var passport = require('passport');
|
const User = require('../models/User.js');
|
||||||
|
const Invite = require('../models/Invite.js');
|
||||||
|
|
||||||
var async = require('async');
|
const passport = require('passport');
|
||||||
|
|
||||||
// Normalizes, decomposes, and lowercases a unicode string
|
const async = require('async');
|
||||||
function canonicalize(username) {
|
|
||||||
return username.normalize('NFKD').toLowerCase();
|
function memoize(fn) {
|
||||||
|
let cache = {};
|
||||||
|
|
||||||
|
return async function() {
|
||||||
|
let args = JSON.stringify(arguments);
|
||||||
|
cache[args] = cache[args] || fn.apply(this, arguments);
|
||||||
|
return cache[args];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if an invite code is valid
|
const asyncMiddleware = fn =>
|
||||||
// Returns the invite object if valid
|
(req, res, next) => {
|
||||||
function checkInvite(code, cb) {
|
Promise.resolve(fn(req, res, next))
|
||||||
Invite.findOne({code: code}, function (err, invite) {
|
.catch(next);
|
||||||
if (err)
|
};
|
||||||
cb(err);
|
|
||||||
else if (!invite)
|
// Normalizes, decomposes, and lowercases a utf-8 string
|
||||||
cb('Invalid invite code.');
|
const canonicalizeUsername = username => username.normalize('NFKD').toLowerCase();
|
||||||
else if (invite.used)
|
|
||||||
cb('Invite already used.');
|
// Check if a canonical name is valid
|
||||||
else if (invite.exp < Date.now())
|
async function validateUsername(username, canonicalName, sanitize) {
|
||||||
cb('Invite expired.');
|
if (canonicalName.length > 36)
|
||||||
else
|
return {valid: false, message: 'Username too long.'};
|
||||||
cb(null, invite);
|
|
||||||
});
|
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};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates the username, then registers the user in the database using the given invite.
|
// Query the database for a valid invite code. An error message property is set if invalid.
|
||||||
function registerUser(username, password, invite, sanitize, cb) {
|
async function validateInvite(code) {
|
||||||
async.series([
|
const invite = await Invite.findOne({code: code});
|
||||||
function (cb) {
|
|
||||||
// Canonicalize and sanitize the username, checking for HTML
|
|
||||||
var canonicalName = canonicalize(username);
|
|
||||||
var sanitizedName = sanitize(canonicalName).replace(/\s/g,'');
|
|
||||||
|
|
||||||
if (sanitizedName !== canonicalName)
|
if (!invite)
|
||||||
cb('Username contains invalid characters.');
|
return {valid: false, message: 'Invalid invite code.'};
|
||||||
else if (canonicalName.length > 36)
|
|
||||||
cb('Username too long.');
|
if (invite.used)
|
||||||
else
|
return {valid: false, message: 'Invite already used.'};
|
||||||
cb(null);
|
|
||||||
},
|
if (invite.exp < Date.now())
|
||||||
function(cb) {
|
return {valid: false, message: 'Invite expired.'};
|
||||||
async.waterfall([
|
|
||||||
function(cb) {
|
return {valid: true, invite: invite};
|
||||||
User.count({canonicalname: canonicalize(username)}, cb);
|
|
||||||
},
|
|
||||||
function(count, cb) {
|
|
||||||
if (count !== 0)
|
|
||||||
cb('Username in use.');
|
|
||||||
else
|
|
||||||
cb(null);
|
|
||||||
}
|
|
||||||
], cb);
|
|
||||||
},
|
|
||||||
function (cb) {
|
|
||||||
User.register(new User({
|
|
||||||
username: username,
|
|
||||||
canonicalname: canonicalize(username),
|
|
||||||
scope: invite.scope,
|
|
||||||
date: Date.now()
|
|
||||||
}), password, cb);
|
|
||||||
},
|
|
||||||
function (cb) {
|
|
||||||
invite.use(canonicalize(username), cb);
|
|
||||||
}
|
|
||||||
], function (err) {
|
|
||||||
cb(err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticates and creates the required session variables
|
// Authenticates and creates the required session variables
|
||||||
function setupSession(username, req, res, cb) {
|
function setupSession(username, req, res, cb) {
|
||||||
// Body needs to contain canonical name for proper authentication
|
// Body needs to contain canonical name for proper authentication
|
||||||
req.body.canonicalname = canonicalize(req.body.username);
|
req.body.canonicalname = canonicalizeUsername(req.body.username);
|
||||||
|
|
||||||
passport.authenticate('local')(req, res, function () {
|
passport.authenticate('local')(req, res, function () {
|
||||||
req.session.save(function (err) {
|
req.session.save(function (err) {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
req.session.passport.username = username;
|
req.session.passport.username = username;
|
||||||
req.session.passport.canonicalname = canonicalize(username);
|
req.session.passport.canonicalname = canonicalizeUsername(username);
|
||||||
}
|
}
|
||||||
cb(err);
|
cb(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
router.post('/register', function (req, res) {
|
router.post('/register', asyncMiddleware(async (req, res, next) => {
|
||||||
async.waterfall([
|
const reqUsername = req.body.username;
|
||||||
function (cb) {
|
const reqPassword = req.body.password;
|
||||||
checkInvite(req.body.invite, cb);
|
const reqInviteCode = req.body.invite;
|
||||||
},
|
const canonicalName = canonicalizeUsername(reqUsername);
|
||||||
function (invite, cb) {
|
|
||||||
registerUser(req.body.username, req.body.password, invite, req.sanitize, cb);
|
// memoized verification functions
|
||||||
},
|
const checkInvite = memoize(async () => validateInvite(reqInviteCode));
|
||||||
function (cb) {
|
const checkUsername = memoize(async () => validateUsername(reqUsername, canonicalName, req.sanitize));
|
||||||
setupSession(req.body.username, req, res, cb);
|
|
||||||
}
|
// Validate the invite and username
|
||||||
], function (err) {
|
const [inviteStatus, usernameStatus] = await Promise.all([checkInvite(), checkUsername()]);
|
||||||
if (err) {
|
|
||||||
res.status(401).json({'message': err});
|
// Make sure invite was valid
|
||||||
} else {
|
if (!inviteStatus.valid)
|
||||||
res.status(200).json({'message': 'Registration successful.'});
|
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()
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
// 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.'});
|
||||||
|
}));
|
||||||
|
|
||||||
router.post('/login', function (req, res, next) {
|
router.post('/login', function (req, res, next) {
|
||||||
// Take 'username' from the form and canonicalize it for authentication.
|
// Take 'username' from the form and canonicalize it for authentication.
|
||||||
req.body.canonicalname = canonicalize(req.body.username);
|
req.body.canonicalname = canonicalizeUsername(req.body.username);
|
||||||
|
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
function (cb) {
|
function (cb) {
|
||||||
@ -128,7 +139,7 @@ router.post('/login', function (req, res, next) {
|
|||||||
},
|
},
|
||||||
function (cb) {
|
function (cb) {
|
||||||
req.session.passport.username = req.body.username;
|
req.session.passport.username = req.body.username;
|
||||||
req.session.passport.canonicalname = canonicalize(req.body.username);
|
req.session.passport.canonicalname = canonicalizeUsername(req.body.username);
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
], function (err) {
|
], function (err) {
|
||||||
|
Loading…
Reference in New Issue
Block a user