1
0
mirror of https://github.com/Foltik/Shimapan synced 2025-01-06 00:08:25 -05:00

Refactor all base auth routes, allowing for case insensitivity and unicode usernames.

This commit is contained in:
Jack Foltz 2018-01-15 15:29:32 -05:00
parent 4b4d32ec9c
commit 26cc9bf6ef
Signed by: foltik
GPG Key ID: 303F88F996E95541
4 changed files with 126 additions and 50 deletions

View File

@ -14,4 +14,8 @@ var InviteSchema = mongoose.Schema({
exp: Date exp: Date
}); });
InviteSchema.methods.use = function(canonicalname, 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);

View File

@ -7,6 +7,11 @@ var UserSchema = mongoose.Schema({
unique: true, unique: true,
required: true required: true
}, },
canonicalname: {
type: String,
unique: true,
required: true
},
scope: [String], scope: [String],
uploadCount: { uploadCount: {
type: Number, type: Number,
@ -19,6 +24,6 @@ var UserSchema = mongoose.Schema({
date: Date date: Date
}); });
UserSchema.plugin(passportLocalMongoose); UserSchema.plugin(passportLocalMongoose, {usernameField: 'canonicalname'});
module.exports = mongoose.model('User', UserSchema); module.exports = mongoose.model('User', UserSchema);

View File

@ -6,57 +6,124 @@ var Invite = require('../models/Invite.js');
var passport = require('passport'); var passport = require('passport');
function checkInvite(code, callback) { var async = require('async');
// Normalizes, decomposes, and lowercases a unicode string
function canonicalize(username) {
return username.normalize('NFKD').toLowerCase();
}
// Checks if an invite code is valid
// Returns the invite object if valid
function checkInvite(code, cb) {
Invite.findOne({code: code}, function (err, invite) { Invite.findOne({code: code}, function (err, invite) {
if (err) return callback(err); if (err)
if (!invite || invite.used || invite.exp < new Date()) cb(err);
callback(null, false); else if (!invite)
cb('Invalid invite code.');
else if (invite.used)
cb('Invite already used.');
else if (invite.exp < Date.now())
cb('Invite expired.');
else else
callback(null, true, invite); cb(null, invite);
}); });
} }
function useInvite(code, username) { // Validates the username, then registers the user in the database using the given invite.
Invite.updateOne({code: code}, {recipient: username, used: new Date()}, function (err) { function registerUser(username, password, invite, sanitizeFn, cb) {
if (err) throw err; async.series([
function (cb) {
// Canonicalize and sanitize the username, checking for HTML
var canonicalName = canonicalize(username);
var sanitizedName = sanitizeFn(canonicalName);
if (sanitizedName !== canonicalName)
cb('Username failed sanitization check.');
else if (canonicalName.length > 36)
cb('Username too long.');
else
cb(null);
},
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);
}); });
} }
router.post('/register', function (req, res, next) { // Authenticates and creates the required session variables
// Validate the invite code, then hand off to passport function setupSession(username, req, res, cb) {
checkInvite(req.body.invite, function (err, valid, invite) { // Body needs to contain canonical name for proper authentication
if (valid) { req.body.canonicalname = canonicalize(req.body.username);
User.register(
new User({username: req.body.username, scope: invite.scope, date: Date.now()}), passport.authenticate('local')(req, res, function () {
req.body.password, req.session.save(function (err) {
function (err) { if (!err) {
if (err) return res.status(403).json({'message': err.message}); req.session.username = username;
passport.authenticate('local')(req, res, function () { req.session.canonicalname = canonicalize(username);
req.session.save(function(err) { }
if (err) return next(err); cb(err);
useInvite(req.body.invite, req.body.username); });
req.session.username = req.body.username; });
res.status(200).json({'message': 'Registered.'}); }
});
}); router.post('/register', function (req, res) {
} async.waterfall([
); function (cb) {
checkInvite(req.body.invite, cb);
},
function (invite, cb) {
registerUser(req.body.username, req.body.password, invite, req.sanitize, cb);
},
function (cb) {
setupSession(req.body.username, req, res, cb);
}
], function (err) {
if (err) {
res.status(401).json({'message': err});
} else { } else {
res.status(401).json({'message': 'Invalid invite code.'}); res.status(200).json({'message': 'Registration successful.'});
} }
}); });
}); });
router.post('/login', function (req, res, next) { router.post('/login', function (req, res, next) {
passport.authenticate('local', function(err, user, info) { // Take 'username' from the form and canonicalize it for authentication.
if (err) return next(err); req.body.canonicalname = canonicalize(req.body.username);
if (!user) return res.status(401).json({'message': info});
req.logIn(user, function(err) { async.waterfall([
if (err) return next(err); function (cb) {
req.session.username = user; passport.authenticate('local', function(err, user, info) {
res.status(200).json({'message': 'Logged in.'}); cb(err, user, info);
}); })(req, res, next);
})(req, res, next); },
function (user, info, cb) {
if (!user)
cb(info);
else
req.logIn(user, cb);
},
function (cb) {
req.session.username = req.body.username;
req.session.canonicalname = canonicalize(req.body.username);
cb();
}
], function (err) {
if (err)
res.status(401).json({'message': err});
else
res.status(200).json({'message': 'Login successful.'});
});
}); });
router.get('/logout', function (req, res) { router.get('/logout', function (req, res) {
@ -64,17 +131,18 @@ router.get('/logout', function (req, res) {
res.status(200).json({'message': 'Logged out.'}); res.status(200).json({'message': 'Logged out.'});
}); });
router.get('/session', function(req, res) { router.get('/session', function (req, res) {
if (req.session.passport.user) { if (req.session.passport.canonicalname) {
User.findOne({username: req.session.passport.user}, function(err, user) { User.findOne({canonicalname: req.session.passport.canonicalname}, function (err, user) {
res.status(200).json({ res.status(200).json({
user: user.username, username: user.username,
scope: user.scope canonicalname: user.canonicalname,
}); scope: user.scope
}); });
} else { });
res.status(401).json({'message': 'Unauthorized.'}); } else {
} res.status(401).json({'message': 'Unauthorized.'});
}
}); });
module.exports = router; module.exports = router;

View File

@ -50,7 +50,6 @@ app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.text()); app.use(bodyParser.text());
app.use(sanitizer()); app.use(sanitizer());
app.use(methodOverride('X-HTTP-Method-Override')); app.use(methodOverride('X-HTTP-Method-Override'));
app.use(passport.initialize());
//app.use(favicon(__dirname + '/public/img/favicon.ico')); //app.use(favicon(__dirname + '/public/img/favicon.ico'));