/** * @module flitter-auth/deploy/controllers/Auth */ const validator = require('validator') const bcrypt = require('bcrypt') const uuid = require('uuid/v4') /** * Controller for the auth and user functionality for Flitter-auth. * @class */ class Auth { /** * Create an authenticated session. * @param {Express/Request} req - the incoming Express request * @param {module:flitter-auth/deploy/models/User~User} user - the user for which a session should be created */ create_auth_session(req, user){ req.session.auth = { authenticated: true, uuid: user.uuid, user } req.session.auth.user.password = "" } /** * Destroy the authenticated session. * @param {Express/Request} req - the incoming Express request */ destroy_auth_session(req){ req.session.auth = {} } /** * Serve the registration page. * @param {Express/Request} req - the incoming Express request * @param {Express/Response} res - the corresponding Express response */ register_get(req, res){ /* * Check for auth_form errors in the session. * If they exist, pass them along to the view. */ let submission_data = { error: false, data: {} } if ( req.session && 'auth_form' in req.session && 'errors' in req.session.auth_form ){ submission_data = req.session.auth_form delete req.session.auth_form } /* * Return the registration view with the errors. */ _flitter.view(res, 'auth/register', { submission_data }) } /** * Handle the registration and create a new user. * @param {Express/Request} req - the incoming Express request * @param {Express/Response} res - the corresponding Express response */ register_post(req, res){ let username, password /* * Creates a session variable with the auth_form errors. */ const create_error_session = (field, message) => { let auth_form = { error: true, errors: { }, data: { 'username': req.body.username } } auth_form.errors[field] = message req.session.auth_form = auth_form } /* * Fail if username isn't provided. */ if ( !( 'username' in req.body ) ){ req.session.auth_form = { error: true, errors: { 'username': 'Username is required.' } } return res.redirect('/auth/register') } /* * Fail if the password or password verification aren't provided. */ if ( !( 'password' in req.body ) || !( 'password_verify' in req.body ) ){ req.session.auth_form = { error: true, errors: { 'password': 'Password and verification are required.' } } return res.redirect('/auth/register') } /* * Validate that the username is alphanumeric. */ if ( !validator.isAlphanumeric( req.body.username ) ) { /* * Create an auth_form error in the session and * redirect back to the registration form. */ create_error_session('username', 'Username must be alpha-numeric.') return res.redirect('/auth/register') } /* * Get the User model. */ const User = _flitter.model('User') /* * Check if the username is already in the * database. If so, redirect with an error. */ User.find({ username: req.body.username }, (err, users) => { if ( users.length ){ /* * Create an auth_form error in the session and * redirect back to the registration form. */ create_error_session('username', 'Username is already taken.') return res.redirect('/auth/register') } username = req.body.username /* * Check if the password meets the minimum length requirement. */ if ( !validator.isLength(req.body.password, {min:8}) ){ /* * Create an auth_form error in the session and * redirect back to the registration form. */ create_error_session('password', 'Password must be at least 8 characters.') return res.redirect('/auth/register') } /* * Check that the password matches the password_verify field. */ if ( !validator.equals(req.body.password, req.body.password_verify) ){ /* * Create an auth_form error in the session and * redirect back to the registration form. */ create_error_session('password', 'Passwords don\'t match.') return res.redirect('/auth/register') } password = req.body.password /* * Create the password hash. */ bcrypt.hash(password, 10, (err, hash) => { /* * Instantiate a new user object. */ const unique_id = uuid() const user = new User({ username, password: hash, data: JSON.stringify({}), uuid: unique_id }) /* * Store the new user in the database. */ user.save().then(()=>{ _flitter.controller('Auth').create_auth_session(req, user) return _flitter.view(res, 'auth/register_success') }) }) }) } /** * Log the user out and destroy the authenticated session. * @param {Express/Request} req - the incoming Express request * @param {Express/Response} res - the corresponding Express response */ logout( req, res ){ if ( req.session ){ _flitter.controller('Auth').destroy_auth_session(req) } return _flitter.view(res, 'auth/logged_out') } /** * Serve the login view. * @param {Express/Request} req - the incoming Express request * @param {Express/Response} res - the corresponding Express response */ login_get( req, res ){ /* * Check for auth_form errors in the session. * If they exist, pass them along to the view. */ let submission_data = { error: false, data: {} } if ( req.session && 'auth_form' in req.session && 'errors' in req.session.auth_form ){ submission_data = req.session.auth_form delete req.session.auth_form } /* * Return the login view with the errors. */ return _flitter.view(res, 'auth/login', { submission_data }) } /** * Process a login request. * If it is successful, create a user session and carry on. * @param {Express/Request} req - the incoming Express request * @param {Express/Response} res - the corresponding Express response */ login_post( req, res ){ const create_error_session = (field, message) => { let auth_form = { error: true, errors: { }, data: { 'username': req.body.username } } auth_form.errors[field] = message req.session.auth_form = auth_form } /* * Fail if username isn't provided. */ if ( !( 'username' in req.body ) ){ req.session.auth_form = { error: true, errors: { 'username': 'Username is required.' } } return res.redirect('/auth/login') } /* * Fail if the password isn't provided. */ if ( !( 'password' in req.body ) ){ req.session.auth_form = { error: true, errors: { 'password': 'Password is required.' } } return res.redirect('/auth/login') } /* * Make sure the username is still alphanum. * Just a sanity check to prevent code injection. */ if ( !validator.isAlphanumeric( req.body.username ) ){ create_error_session('username', "Invalid username.") return res.redirect('/auth/login') } const User = _flitter.model('User') User.findOne({username: req.body.username}, (err, user) => { /* * If no user with the username is found, return * an error back. */ if ( !user ){ create_error_session('username', 'Invalid username.') return res.redirect('/auth/login') } /* * Check the provided password against the stored hash. */ bcrypt.compare( req.body.password, user.password, (err, match) => { /* * Return an error if the password fails the hash. */ if ( !match ){ create_error_session('password', 'Invalid password.') return res.redirect('/auth/login') } else { /* * Create an authenticated session. */ _flitter.controller('Auth').create_auth_session(req, user) /* * If a destination was provided, redirect there. */ if ( req.session.destination ){ const redirect_to = req.session.destination delete req.session.destination return res.redirect(redirect_to) } /* * Otherwise, redirect to the provided sample dash. */ else { return res.redirect('/auth/dash') } } }) }) } /** * Return the dash view. * By default, this is pretty simple. It's designed to be replaced by your app. * @param {Express/Request} req - the incoming Express request * @param {Express/Response} res - the corresponding Express response */ dash_get(req, res){ return _flitter.view(res, 'auth/dash', { user: req.session.auth.user }) } } module.exports = Auth