This repository has been archived on 2024-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
tracman-server/config/auth.js

252 lines
7.1 KiB
JavaScript
Raw Normal View History

'use strict';
2017-04-09 21:40:08 -06:00
const
mw = require('./middleware.js'),
2017-04-01 11:03:05 -06:00
mail = require('./mail.js'),
User = require('./models.js').user,
slug = require('slug'),
crypto = require('crypto'),
2017-04-01 11:03:05 -06:00
env = require('./env.js');
2016-03-31 17:06:21 -06:00
module.exports = (app, passport) => {
2017-04-01 11:03:05 -06:00
// Methods for success and failure
2017-04-09 21:40:08 -06:00
const
loginOutcome = {
2017-04-12 17:39:39 -06:00
failureRedirect: '/login',
failureFlash: true
2017-04-13 22:45:25 -06:00
},
2017-04-09 21:40:08 -06:00
connectOutcome = {
2017-04-12 17:39:39 -06:00
failureRedirect: '/settings',
failureFlash: true
},
loginCallback = (req,res)=>{
2017-04-13 22:45:25 -06:00
res.redirect( req.session.next || '/map' );
2017-04-09 21:40:08 -06:00
};
2017-04-13 22:45:25 -06:00
2017-04-01 11:03:05 -06:00
// Login/-out
app.route('/login')
.get( (req,res)=>{
2017-04-13 22:45:25 -06:00
if (req.isAuthenticated()){ loginCallback(); }
2017-04-01 11:03:05 -06:00
else { res.render('login'); }
})
.post( passport.authenticate('local',loginOutcome), loginCallback );
app.get('/logout', (req,res)=>{
2017-04-01 11:03:05 -06:00
req.logout();
2017-04-12 17:39:39 -06:00
req.flash('success',`You have been logged out.`);
2017-04-13 22:45:25 -06:00
res.redirect(req.session.next || '/');
2017-04-01 11:03:05 -06:00
});
2017-04-13 22:45:25 -06:00
2017-04-01 11:03:05 -06:00
// Signup
app.get('/signup', (req,res)=>{
res.redirect('/login#signup');
2017-04-13 23:00:18 -06:00
})
.post('/signup', (req,res,next)=>{
2017-04-13 22:45:25 -06:00
// Send token and alert user
function sendToken(user){
2017-04-13 22:45:25 -06:00
// Create a password token
2017-04-13 22:45:25 -06:00
user.createToken((err,token)=>{
if (err){ mw.throwErr(err,req); }
2017-04-13 22:45:25 -06:00
// Email the instructions to continue
mail.send({
from: mail.from,
to: `<${user.email}>`,
subject: 'Complete your Tracman registration',
text: mail.text(`Welcome to Tracman! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/settings/password/${token}`),
html: mail.html(`<p>Welcome to Tracman! </p><p>To complete your registration, follow this link and set your password:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a></p>`)
2017-04-13 22:45:25 -06:00
}).catch((err)=>{
mw.throwErr(err,req);
res.redirect('/login#signup');
2017-04-13 22:45:25 -06:00
}).then(()=>{
req.flash('success', `An email has been sent to <u>${user.email}</u>. Check your inbox to complete your registration. `);
2017-04-13 22:45:25 -06:00
res.redirect('/login');
});
});
2017-04-13 22:45:25 -06:00
}
2017-04-13 22:45:25 -06:00
// Check if somebody already has that email
User.findOne({'email':req.body.email}, (err,user)=>{
if (err){ mw.throwErr(err,req); }
2017-04-13 22:45:25 -06:00
2017-04-01 11:03:05 -06:00
// User already exists
if (user && user.auth.password) {
req.flash('warning','A user with that email already exists! If you forgot your password, you can <a href="/login/forgot">reset it here</a>.');
res.redirect('/login#login');
next();
}
2017-04-13 22:45:25 -06:00
// User exists but hasn't created a password yet
else if (user) {
// Send another token (or the same one if it hasn't expired)
sendToken(user);
}
2017-04-13 22:45:25 -06:00
// Create user
else {
2017-04-13 22:45:25 -06:00
user = new User();
user.created = Date.now();
user.email = req.body.email;
user.slug = slug(user.email.substring(0, user.email.indexOf('@')));
2017-04-13 22:45:25 -06:00
// Generate unique slug
var generateSlug = new Promise((resolve,reject) => {
(function checkSlug(s,cb){
2017-04-13 22:45:25 -06:00
User.findOne({slug:s})
.catch((err)=>{
mw.throwErr(err,req);
})
.then((existingUser)=>{
// Slug in use: generate a random one and retry
if (existingUser){
s = '';
while (s.length<6) {
s+='abcdefghijkmnpqrtuvwxy346789'.charAt(Math.floor(Math.random()*28));
}
checkSlug(s,cb);
2017-04-13 22:45:25 -06:00
}
// Unique slug: proceed
2017-04-13 22:45:25 -06:00
else { cb(s); }
});
2017-04-13 22:45:25 -06:00
})(user.slug, (newSlug)=>{
user.slug = newSlug;
resolve();
});
});
2017-04-13 22:45:25 -06:00
// Generate sk32
var generateSk32 = new Promise((resolve,reject) => {
crypto.randomBytes(32, (err,buf)=>{
if (err) { mw.throwErr(err,req); }
user.sk32 = buf.toString('hex');
resolve();
});
});
2017-04-13 22:45:25 -06:00
// Save user and send the token by email
Promise.all([generateSlug, generateSk32])
2017-04-13 22:45:25 -06:00
.catch(err => {
mw.throwErr(err,req);
}).then(() => {
user.save( (err)=>{
if (err){ mw.throwErr(err,req); }
sendToken(user);
});
2017-04-01 11:03:05 -06:00
});
2017-04-13 22:45:25 -06:00
2017-04-01 11:03:05 -06:00
}
2017-04-13 22:45:25 -06:00
2017-04-01 11:03:05 -06:00
});
});
2017-04-01 11:03:05 -06:00
// Forgot password
2017-04-13 23:00:18 -06:00
app.route('/login/forgot')
.all( (req,res,next)=>{
if (req.isAuthenticated()){ loginCallback(); }
else { next(); }
})
.get( (req,res,next)=>{
res.render('forgot');
})
.post( (req,res,next)=>{
//TODO: Validate and sanitize email
// req.assert('email', 'Please enter a valid email address.').isEmail();
// req.sanitize('email').normalizeEmail({ remove_dots: false });
User.findOne( {'email':req.body.email}, (err,user)=>{
if (err){ mw.throwErr(err); }
// No user with that email
if (!user) {
// Don't let on that no such user exists, to prevent dictionary attacks
req.flash('success', `If an account exists with the email <u>${req.body.email}</u>, an email has been sent there with a password reset link. `);
res.redirect('/login');
}
// User with that email exists
else {
// Create reset token
user.createToken( (err,token)=>{
if (err){ next(err); }
// Email reset link
mail.send({
from: mail.from,
to: mail.to(user),
subject: 'Reset your Tracman password',
text: mail.text(`Hi, \n\nDid you request to reset your Tracman password? If so, follow this link to do so:\n${env.url}/settings/password/${token}\n\nIf you didn't initiate this request, just ignore this email. `),
html: mail.html(`<p>Hi, </p><p>Did you request to reset your Tracman password? If so, follow this link to do so:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a></p><p>If you didn't initiate this request, just ignore this email. </p>`)
}).then(()=>{
req.flash('success', `If an account exists with the email <u>${req.body.email}</u>, an email has been sent there with a password reset link. `);
res.redirect('/login');
}).catch((err)=>{
mw.throwErr(err);
});
2017-04-13 23:00:18 -06:00
});
}
});
2017-04-13 23:00:18 -06:00
});
2016-03-31 15:01:27 -06:00
2017-04-01 11:03:05 -06:00
// Social
app.get('/login/:service', (req,res,next)=>{
2017-04-13 22:45:25 -06:00
let service = req.params.service,
sendParams = (service==='google')? {scope:['profile']} : null;
// Social login
if (!req.user) {
2017-04-01 11:03:05 -06:00
passport.authenticate(service, sendParams)(req,res,next);
}
2017-04-13 22:45:25 -06:00
// Connect social account
else if (!req.user.auth[service]) {
2017-04-01 11:03:05 -06:00
passport.authorize(service, sendParams)(req,res,next);
}
2017-04-13 22:45:25 -06:00
// Disconnect social account
else {
2017-04-01 11:03:05 -06:00
req.user.auth[service] = undefined;
2017-04-13 22:45:25 -06:00
req.user.save()
.catch((err)=>{
mw.throwErr(err,req);
res.redirect('/settings');
2017-04-13 22:45:25 -06:00
}).then(()=>{
2017-04-01 11:03:05 -06:00
req.flash('success', `${mw.capitalize(service)} account disconnected. `);
res.redirect('/settings');
2017-04-13 22:45:25 -06:00
});
2016-03-31 17:06:21 -06:00
}
});
app.get('/login/:service/cb', (req,res,next)=>{
2017-04-01 11:03:05 -06:00
var service = req.params.service;
if (!req.user) {
passport.authenticate(service, loginOutcome)(req,res,next);
} else {
req.flash('success', `${mw.capitalize(service)} account connected. `);
2017-04-12 11:41:27 -06:00
req.session.next = '/settings';
2017-04-01 11:03:05 -06:00
passport.authenticate(service, connectOutcome)(req,res,next);
}
}, loginCallback);
2017-04-01 11:03:05 -06:00
// Android auth
//TODO: See if there's a better method
app.get('/auth/google/idtoken', passport.authenticate('google-id-token'), (req,res)=>{
2017-04-13 22:45:25 -06:00
if (!req.user){ res.sendStatus(401); }
2017-04-01 11:03:05 -06:00
else { res.send(req.user); }
} );
};