tracman-server/config/routes/auth.js

346 lines
11 KiB
JavaScript
Raw Normal View History

2017-04-16 19:00:39 -06:00
'use strict';
const
mw = require('../middleware.js'),
mail = require('../mail.js'),
User = require('../models.js').user,
crypto = require('crypto'),
moment = require('moment'),
2017-05-02 07:24:47 -06:00
slugify = require('slug'),
2017-05-08 11:47:53 -06:00
debug = require('debug')('tracman-routes-auth'),
2017-04-26 21:13:14 -06:00
env = require('../env/env.js');
2017-04-16 19:00:39 -06:00
module.exports = (app, passport) => {
// Methods for success and failure
const
loginOutcome = {
failureRedirect: '/login',
failureFlash: true
2017-04-19 19:37:00 -06:00
},
2017-04-16 19:00:39 -06:00
loginCallback = (req,res)=>{
2017-05-08 11:47:53 -06:00
debug(`Login callback called... redirecting to ${req.session.next}`);
2017-04-19 20:03:45 -06:00
req.flash(req.session.flashType,req.session.flashMessage);
req.session.flashType = undefined;
req.session.flashMessage = undefined;
2017-04-18 22:13:57 -06:00
res.redirect( req.session.next || '/map' );
},
2017-04-20 20:31:10 -06:00
appLoginCallback = (req,res,next)=>{
2017-05-08 11:47:53 -06:00
debug('appLoginCallback called.');
if (req.user){ res.send(req.user); }
2017-04-20 20:31:10 -06:00
else {
let err = new Error("Unauthorized");
err.status = 401;
next(err);
}
2017-04-16 19:00:39 -06:00
};
// Login/-out
app.route('/login')
.get( (req,res)=>{
// Already logged in
if (req.isAuthenticated()) { loginCallback(req,res); }
// Show login page
else { res.render('login'); }
})
.post( passport.authenticate('local',loginOutcome), loginCallback );
app.get('/logout', (req,res)=>{
req.logout();
req.flash('success',`You have been logged out.`);
2017-04-18 22:13:57 -06:00
res.redirect( req.session.next || '/' );
2017-04-16 19:00:39 -06:00
});
// Signup
app.route('/signup')
.get( (req,res)=>{
res.redirect('/login#signup');
})
.post( (req,res,next)=>{
// Send token and alert user
function sendToken(user){
debug(`sendToken() called for user ${user.id}`);
2017-04-16 19:00:39 -06:00
// Create a password token
2017-05-02 07:21:53 -06:00
user.createPassToken( (err,token,expires)=>{
if (err){
2017-07-14 01:40:19 -06:00
debug(`Error creating password token for user ${user.id}!`);
2017-04-16 19:00:39 -06:00
mw.throwErr(err,req);
res.redirect('/login#signup');
}
else {
2017-07-14 01:40:19 -06:00
debug(`Created password token for user ${user.id} successfully`);
// Figure out expiration time
let expirationTimeString = (req.query.tz)?
moment(expires).utcOffset(req.query.tz).toDate().toLocaleTimeString(req.acceptsLanguages[0]):
moment(expires).toDate().toLocaleTimeString(req.acceptsLanguages[0])+" UTC";
// Email the instructions to continue
2017-07-14 01:40:19 -06:00
debug(`Emailing new user ${user.id} at ${user.email} instructions to create a password...`);
mail.send({
2017-05-08 15:45:06 -06:00
from: mail.noReply,
2017-05-02 07:21:53 -06:00
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}\n\nThis link will expire at ${expirationTimeString}. `),
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><p>This link will expire at ${expirationTimeString}. </p>`)
})
.then(()=>{
2017-07-14 01:45:43 -06:00
debug(`Successfully emailed new user ${user.id} instructions to continue`);
req.flash('success', `An email has been sent to <u>${user.email}</u>. Check your inbox and follow the link to complete your registration. (Your registration link will expire in one hour). `);
res.redirect('/login');
})
.catch((err)=>{
2017-07-14 01:40:19 -06:00
debug(`Failed to email new user ${user.id} instructions to continue!`);
mw.throwErr(err,req);
res.redirect('/login#signup');
});
2017-04-18 01:08:57 -06:00
}
2017-04-16 19:00:39 -06:00
});
}
// Validate email
req.checkBody('email', 'Please enter a valid email address.').isEmail();
// Check if somebody already has that email
2017-07-14 01:40:19 -06:00
debug(`Searching for user with email ${req.body.email}...`);
2017-04-16 19:00:39 -06:00
User.findOne({'email':req.body.email})
2017-04-19 20:03:45 -06:00
.then( (user)=>{
// User already exists
if (user && user.auth.password) {
2017-07-14 01:40:19 -06:00
debug(`User ${user.id} has email ${req.body.email} and has a password`);
req.flash('warning',`A user with that email already exists! If you forgot your password, you can <a href="/login/forgot?email=${req.body.email}">reset it here</a>.`);
2017-04-19 20:03:45 -06:00
res.redirect('/login#login');
next();
}
// User exists but hasn't created a password yet
else if (user) {
2017-07-14 01:40:19 -06:00
debug(`User ${user.id} has email ${req.body.email} but doesn't have a password`);
2017-04-19 20:03:45 -06:00
// Send another token (or the same one if it hasn't expired)
sendToken(user);
}
// Create user
else {
2017-07-14 01:40:19 -06:00
debug(`User with email ${req.body.email} doesn't exist; creating one`);
2017-04-16 19:00:39 -06:00
2017-04-19 20:03:45 -06:00
user = new User();
user.created = Date.now();
user.email = req.body.email;
2017-05-02 07:24:47 -06:00
user.slug = slugify(user.email.substring(0, user.email.indexOf('@')));
2017-04-16 19:00:39 -06:00
2017-04-19 20:03:45 -06:00
// Generate unique slug
2017-04-27 15:25:16 -06:00
const slug = new Promise((resolve,reject) => {
2017-07-14 01:40:19 -06:00
debug(`Creating new slug for user...`);
2017-04-19 20:03:45 -06:00
(function checkSlug(s,cb){
2017-07-14 01:40:19 -06:00
debug(`Checking to see if slug ${s} is taken...`);
2017-04-19 20:03:45 -06:00
User.findOne({slug:s})
.then((existingUser)=>{
2017-04-16 19:00:39 -06:00
2017-04-19 20:03:45 -06:00
// Slug in use: generate a random one and retry
if (existingUser){
2017-07-14 01:40:19 -06:00
debug(`Slug ${s} is taken; generating another...`);
2017-05-02 07:30:48 -06:00
crypto.randomBytes(6, (err,buf)=>{
if (err) {
2017-05-19 01:16:52 -06:00
debug('Failed to create random bytes for slug!');
2017-05-02 07:30:48 -06:00
mw.throwErr(err,req);
reject();
}
if (buf) {
checkSlug(buf.toString('hex'),cb);
}
2017-04-19 20:03:45 -06:00
});
}
2017-04-16 19:00:39 -06:00
2017-04-19 20:03:45 -06:00
// Unique slug: proceed
2017-07-14 01:40:19 -06:00
else {
debug(`Slug ${s} is unique`);
cb(s);
}
2017-04-19 20:03:45 -06:00
})
.catch((err)=>{
2017-05-19 01:16:52 -06:00
debug('Failed to create slug!');
2017-04-19 20:03:45 -06:00
mw.throwErr(err,req);
2017-04-20 21:07:35 -06:00
reject();
2017-04-16 19:00:39 -06:00
});
2017-04-19 20:03:45 -06:00
})(user.slug, (newSlug)=>{
debug(`Successfully created slug: ${newSlug}`);
2017-04-19 20:03:45 -06:00
user.slug = newSlug;
resolve();
2017-04-16 19:00:39 -06:00
});
2017-04-19 20:03:45 -06:00
});
// Generate sk32
2017-04-27 15:25:16 -06:00
const sk32 = new Promise((resolve,reject) => {
2017-07-14 01:40:19 -06:00
debug('Creating sk32 for user...');
2017-05-02 07:30:48 -06:00
crypto.randomBytes(32, (err,buf)=>{
if (err) {
2017-05-19 01:16:52 -06:00
debug('Failed to create sk32!');
2017-05-02 07:30:48 -06:00
mw.throwErr(err,req);
reject();
}
if (buf) {
user.sk32 = buf.toString('hex');
debug(`Successfully created sk32: ${user.sk32}`);
2017-05-02 07:30:48 -06:00
resolve();
}
2017-04-16 19:00:39 -06:00
});
2017-04-19 20:03:45 -06:00
});
2017-04-16 19:00:39 -06:00
2017-04-19 20:03:45 -06:00
// Save user and send the token by email
Promise.all([slug, sk32])
.then( ()=>{ sendToken(user); })
.catch( (err)=>{
2017-05-19 01:16:52 -06:00
debug('Failed to save user after creating slug and sk32!');
2017-04-20 21:07:35 -06:00
mw.throwErr(err,req);
res.redirect('/login#signup');
});
2017-04-19 20:03:45 -06:00
}
})
.catch( (err)=>{
2017-05-19 01:16:52 -06:00
debug(`Failed to check if somebody already has the email ${req.body.email}`);
2017-04-19 20:03:45 -06:00
mw.throwErr(err,req);
res.redirect('/signup');
});
2017-04-16 19:00:39 -06:00
});
// Forgot password
app.route('/login/forgot')
// Check if user is already logged in
2017-04-16 19:00:39 -06:00
.all( (req,res,next)=>{
if (req.isAuthenticated()){ loginCallback(req,res); }
else { next(); }
} )
// Show forgot password page
2017-04-16 19:00:39 -06:00
.get( (req,res,next)=>{
res.render('forgot', {email:req.query.email});
2017-04-16 19:00:39 -06:00
} )
// Submitted forgot password form
2017-04-16 19:00:39 -06:00
.post( (req,res,next)=>{
// Validate email
req.checkBody('email', 'Please enter a valid email address.').isEmail();
2017-05-19 01:16:52 -06:00
// Check if somebody has that email
2017-04-16 19:00:39 -06:00
User.findOne({'email':req.body.email})
.then( (user)=>{
// 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 does exist
else {
// Create reset token
2017-04-18 01:08:57 -06:00
user.createPassToken( (err,token)=>{
2017-04-16 19:00:39 -06:00
if (err){ next(err); }
// Email reset link
mail.send({
2017-05-08 15:45:06 -06:00
from: mail.noReply,
2017-04-16 19:00:39 -06:00
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)=>{
2017-05-19 01:16:52 -06:00
debug(`Failed to send reset link to ${user.email}`);
2017-04-19 19:37:00 -06:00
mw.throwErr(err,req);
res.redirect('/login');
2017-04-16 19:00:39 -06:00
});
});
}
}).catch( (err)=>{
2017-05-19 01:16:52 -06:00
debug(`Failed to check for if somebody has that email (in reset request)!`);
2017-04-16 19:00:39 -06:00
mw.throwErr(err,req);
res.redirect('/login/forgot');
});
} );
2017-04-19 19:37:00 -06:00
// Android
app.post('/login/app', passport.authenticate('local'), appLoginCallback);
2017-04-19 19:37:00 -06:00
2017-04-19 20:03:45 -06:00
// Token-based (android social)
app.get(['/login/app/google','/auth/google/idtoken'], passport.authenticate('google-token'), appLoginCallback);
// app.get('/login/app/facebook', passport.authenticate('facebook-token'), appLoginCallback);
// app.get('/login/app/twitter', passport.authenticate('twitter-token'), appLoginCallback);
2017-04-19 19:37:00 -06:00
2017-04-16 19:00:39 -06:00
// Social
app.get('/login/:service', (req,res,next)=>{
let service = req.params.service,
2017-04-19 19:37:00 -06:00
sendParams = (service==='google')?{scope:['https://www.googleapis.com/auth/userinfo.profile']}:null;
2017-04-16 19:00:39 -06:00
// Social login
if (!req.user) {
2017-05-08 11:47:53 -06:00
debug(`Attempting to login with ${service} with params: ${JSON.stringify(sendParams)}...`);
2017-04-16 19:00:39 -06:00
passport.authenticate(service, sendParams)(req,res,next);
}
// Connect social account
else if (!req.user.auth[service]) {
2017-05-08 11:47:53 -06:00
debug(`Attempting to connect ${service} account...`);
2017-04-16 19:00:39 -06:00
passport.authorize(service, sendParams)(req,res,next);
}
// Disconnect social account
else {
2017-05-08 11:47:53 -06:00
debug(`Attempting to disconnect ${service} account...`);
2017-05-02 07:21:53 -06:00
// Make sure the user has a password before they disconnect their google login account
// This is because login used to only be through google, and some people might not have
// set passwords yet...
if (!req.user.auth.password && service==='google') {
req.flash('warning',`Hey, you need to <a href="/settings/password">set a password</a> before you can disconnect your google account. Otherwise, you won't be able to log in! `);
2017-04-19 19:37:00 -06:00
res.redirect('/settings');
2017-05-02 07:21:53 -06:00
}
else {
req.user.auth[service] = undefined;
req.user.save()
.then(()=>{
req.flash('success', `${mw.capitalize(service)} account disconnected. `);
res.redirect('/settings');
})
.catch((err)=>{
2017-05-19 01:16:52 -06:00
debug(`Failed to save user after disconnecting ${service} account!`);
2017-05-02 07:21:53 -06:00
mw.throwErr(err,req);
res.redirect('/settings');
});
}
2017-04-16 19:00:39 -06:00
}
2017-04-19 19:37:00 -06:00
2017-04-16 19:00:39 -06:00
});
2017-04-19 20:03:45 -06:00
app.get('/login/google/cb', passport.authenticate('google',loginOutcome), loginCallback );
app.get('/login/facebook/cb', passport.authenticate('facebook',loginOutcome), loginCallback );
app.get('/login/twitter/cb', passport.authenticate('twitter',loginOutcome), loginCallback );
2017-04-16 19:00:39 -06:00
};