gadael/gadael

View on GitHub
modules/passport.js

Summary

Maintainability
D
2 days
Test Coverage
'use strict';

const http = require('http');
const util = require('util');
const LocalStrategy = require('passport-local').Strategy;
const GoogleStrategy = require('passport-google-oauth2' ).Strategy;
const HeaderStrategy = require('passport-trusted-header').Strategy;
const CasStrategy = require('passport-cas').Strategy;
const usercreated = require('./emails/usercreated');


exports = module.exports = function(app, passport) {

    let loginservices = app.config.company.loginservices;
    let User = app.db.models.User;
    let gt = app.utility.gettext;

    passport.db = app.db;



    /**
     * Local strategy callback, login with form
     * @param {String} username     User input
     * @param {String} password     User input
     * @param {Function} done
     */
    function useLocalStrategy(username, password, done) {

        let conditions = {
            isActive: true,
            email: username
        };

        User
        .findOne(conditions)
        .select('+password')
        .exec(function(err, user) {
            if (err) {
                return done(err);
            }

            if (!user) {
                return done(null, false, { message: gt.gettext('Unknown user') });
            }

            if (!user.password) {
                return done(null, false, { message: gt.gettext('No password set on user') });
            }

            User.validatePassword(password, user.password, function(err, isValid) {
                if (err) {
                    return done(err);
                }

                if (!isValid) {
                    return done(null, false, { message: gt.gettext('Invalid password') });
                }

                return done(null, user);
            });
        });
    }




    /**
     * Check if the account can be created from google profile
     * @param {Object} profile
     * @param {Function} callback
     */
    function canCreateProfile(profile, callback) {

        let email = profile.emails[0].value;
        let domain = loginservices.google.domain;

        if (!domain) {
            return callback(new Error(gt.gettext('Creation of a new account is not allowed on this application')));
        }

        if (-1 === '@'+domain.indexOf(email)) {
            return callback(new Error(util.format(gt.gettext('Only email from %s are allowed'), domain)));
        }

        // if the email already exists

        User.findOne({ email: email }).exec()
        .then(user => {
            if (null === user) {
                return callback(null);
            }
        })
        .catch(callback);
    }



    /**
    * Google strategy callback
    * @param {ClientRequest} request
    * @param {String} accessToken
    * @param {String} refreshToken
    * @param {Object} profile          Contain google user profile
    * @param {Function} done           Callback, wait for error and user object
    */
    function useGoogleStrategy(request, accessToken, refreshToken, profile, done) {

        if (request.user) {
            // already authenticated, but not linked to google account
            if (undefined === request.user.google) {
                request.user.google = {};
            }
            request.user.google.profile = profile.id;
            request.user.save(done);
            return;
        }


        if (!profile.emails[0].value) {
            return done(new Error(gt.gettext('Google API problem: Email is mandatory')));
        }


        User.findOne({ $or: [
            { 'google.profile': profile.id },
            { 'email': profile.emails[0].value }
        ] }).exec()
        .then(user => {
            if (!user) {
                // user not found by profile ID or by email
                // create from profile
                return canCreateProfile(profile, err => {

                    if (err) {
                        return done(err);
                    }

                    user = new User();
                    user.initFromGoogle(profile);
                    user.save()
                    .then(savedUser => {
                        done(savedUser);
                        // send an email to admins because of the new user without absence account
                        return usercreated(request.app, savedUser);
                    })
                    .then(mail => {
                        return mail.send();
                    });
                });
            }

            // user found

            return done(null, user);
        })
        .catch(done);

    }

    /**
     * Header strategy callback to authenticate by email
     * @param {ClientRequest} request
     * @param {Object} requestHeaders
     * @param {Function} done Callback, wait for error and user object
     */
    function useHeaderStrategy(requestHeaders, done) {
        if (!loginservices.header.emailHeader) {
            return done(new Error('missing emailHeader configuration'));
        }

        if (undefined === requestHeaders[loginservices.header.emailHeader]) {
            return done(new Error('wrong header configuration, header does not exists'));
        }

        const mail = requestHeaders[loginservices.header.emailHeader];
        if (!mail) {
            done(new Error('No email address in header'), null);
        }

        User.findOne({ 'email': mail }).exec()
        .then(user => {
            return done(null, user);
        })
        .catch(done);
    }

    /**
     * CAS authentication strategy callback
     * @param {String} login
     * @param {Function} done Callback, wait for error and user object
     */
    function useCasStrategy(profile, done) {
        if (!profile.attributes.mail) {
            return done(new Error('No email found in CAS profile'));
        }

        User.findOne({ 'email': profile.attributes.mail }).exec()
        .then(user => {
            if (!user) {
                return done(new Error('No account found for email '+profile.attributes.mail));
            }
            return done(null, user);
        })
        .catch(done);
    }

    if (loginservices.cas.enable) {
        passport.use(new CasStrategy({
            version: 'CAS3.0',
            ssoBaseURL: loginservices.cas.ssoBaseURL,
            serverBaseURL: app.config.url+'login/cas'
        }, useCasStrategy));
    }

    if (loginservices.header.enable) {
        passport.use(new HeaderStrategy({
            headers: [loginservices.header.emailHeader]
        }, useHeaderStrategy));
    }

    if (loginservices.form.enable) {
        passport.use(new LocalStrategy(useLocalStrategy));
    }




    if (loginservices.google.enable) {

        const googleOptions = {
            clientID: loginservices.google.clientID,
            clientSecret: loginservices.google.clientSecret,
            callbackURL: app.config.url+'login/google-callback',
            passReqToCallback: true
        };

        passport.use(new GoogleStrategy(googleOptions, useGoogleStrategy));
    }




    /**
     * Serialize user once connected
     */
    passport.serializeUser((user, done) => {
        let company = app.config.company;
        if (company) {
            company.lastLogin = new Date();
            company.save();
        }

        done(null, user._id);
    });

    passport.deserializeUser((id, done) => {

        // here, if i use app.db instead of passport.db, tests fail with a socket hangup
        passport.db.models.User.findOne({ _id: id })
        .populate('department')
        .populate('roles.admin')
        .populate('roles.manager')
        .populate('roles.account')
        .exec(function(err, user) {

            if (null === user) {
                console.error('User not found in deserializeUser '+id+' -> NULL');
            }

            done(err, user);
        });
    });


   let req = http.IncomingMessage.prototype;

    /**
     * Test if logged in
     * output a 401 unauthorized header on failure
     */
    req.ensureAuthenticated = function(req, res, next) {
        if (req.isAuthenticated()) {
            return next();
        }

        var workflow = req.app.utility.workflow(req, res);


        workflow.httpstatus = 401;
        workflow.emit('exception', gt.gettext('Access denied for anonymous users'));
    };




    /**
     * Test if logged in as administrator
     * output a 401 unauthorized header on failure
     */
    req.ensureAdmin = function(req, res, next) {



        if (req.isAuthenticated() && req.user.canPlayRoleOf('admin')) {
            return next(req, res);
        }

        let workflow = req.app.utility.workflow(req, res);
        let gt = req.app.utility.gettext;

        workflow.httpstatus = 401;
        workflow.emit('exception', gt.gettext('Access denied for non administrators'));
    };


    /**
     * Test if logged in as user a verified user account
     * output a 401 unauthorized header on failure
     */
    req.ensureAccount = function(req, res, next) {

        var denyAccess = function() {

            var workflow = req.app.utility.workflow(req, res);
            var gt = req.app.utility.gettext;

            workflow.httpstatus = 401;
            workflow.emit('exception', gt.gettext('Access denied'));
        };

        if (req.user.canPlayRoleOf('account')) {
            if (req.app.config.requireAccountVerification) {
                if (req.user.roles.account.isVerified !== 'yes') {
                    return denyAccess();
                }
            }
            return next(req, res);
        }

        return denyAccess();
    };


};