NateFerrero/amna

View on GitHub
src/lib/authentication.js

Summary

Maintainability
C
1 day
Test Coverage
'use strict';

/**
 * AMNA: Amazing Mongoose Node.js API
 *
 * @author Nate Ferrero
 * @url https://github.com/NateFerrero/amna
 *
 * AMNA - Authentication
 */
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);

module.exports = function (amna, log) {

    var queryJSON = function (data) {
        return '?' + encodeURIComponent(JSON.stringify(data));
    };

    var defaults = function () {
        if (!amna.authentication.urls) {
            amna.authentication.urls = {};
        }

        if (!amna.authentication.urls.auth) {
            amna.authentication.urls.auth = '/';
        }

        if (!amna.authentication.urls.deauth) {
            amna.authentication.urls.deauth = '/';
        }

        if (!amna.authentication.urls.authSuccess) {
            amna.authentication.urls.authSuccess = '/';
        }

        if (!amna.authentication.urls.authFailure) {
            amna.authentication.urls.authFailure = '/';
        }

        if (!amna.authentication.urls.deauthSuccess) {
            amna.authentication.urls.deauthSuccess = '/';
        }
    };

    var setupAuth = function () {
        if (amna.authentication.serializeUser) {
            passport.serializeUser(amna.authentication.serializeUser);
        }

        if (amna.authentication.deserializeUser) {
            passport.deserializeUser(amna.authentication.deserializeUser);
        }

        if (amna.authentication.facebook) {
            require('./authentication-facebook')(amna, passport);
        }

        if (amna.authentication.google) {
            require('./authentication-google')(amna, passport);
        }

        if (amna.authentication.local) {
            if (typeof amna.authentication.local !== 'function') {
                throw new Error('amna.authentication.local must be a function');
            }
            passport.use(new LocalStrategy(amna.authentication.local));
        }
    };

    /**
     * Called during amna.start()
     */
    amna.$auth = function (done) {
        defaults();
        setupAuth();

        amna.$passport = passport;
        amna.$mongoStore = new MongoStore({
            mongoose_connection: amna.$mongooseConnection.connections[0]
        }, function () {
            log('session database connected');
            if (done) {
                done();
            }
        });

        amna.$express.use(session({
            resave: true,
            saveUninitialized: true,
            secret: amna.$express.get('secret'),
            store: amna.$mongoStore
        }));

        amna.$express.use(passport.initialize());
        amna.$express.use(passport.session());

        // Passport Login
        amna.$express.post('/auth', function (req, res, next) {
            passport.authenticate('local', function (err, user, message) {
                if (err) {
                    return next(err);
                }

                if (!user) {
                    if (req.query.redirect) {
                        return res.redirect(302, amna.authentication.urls.fail +
                            queryJSON({message: message || 'Authentication failed'}));
                    }
                    return res.status(400).json(amna.responses.error(message));
                }
                req.logIn(user, function (err) {
                    if (err) {
                        return next(err);
                    }

                    if (req.query.redirect) {
                        return res.redirect(302, amna.authentication.urls.success);
                    }
                    var response = req.user.privateJSON ? req.user.privateJSON() : req.user;
                    return res.status(200).json(amna.responses.ok(response));
                });
            })(req, res, next);
        });

        amna.$express.get('/deauth', function (req, res, next) {
            if (next.noop) { // Argument next is required
                next.noop();
            }
            req.logOut();
            req.session.destroy();
            if (req.query.redirect) {
                return res.redirect(302, amna.authentication.urls.deauthSuccess);
            }
            res.status(200).json(amna.responses.ok());
        });

        if (amna.authentication.facebook) {
            /**
             * Redirect the user to Facebook for authentication.  When complete,
             * Facebook will redirect the user back to the application at
             *   /auth/facebook/callback
             */
            amna.$express.get('/auth/facebook', passport.authenticate('facebook'));

            /**
             * Authenticate based on a Facebook access token
             */
            amna.$express.post('/auth/facebook', passport.authenticate('facebook-token'),
                function (req, res) {
                    if (req.user) {
                        var response = req.user.privateJSON ? req.user.privateJSON() : req.user;
                        return res.status(200).json(amna.responses.ok(response));
                    }
                    return res.status(401).json(amna.responses.error('Invalid access token'));
                }
            );

            /**
             * Facebook will redirect the user to this URL after approval.  Finish the
             * authentication process by attempting to obtain an access token.  If
             * access was granted, the user will be logged in.  Otherwise,
             * authentication has failed.
             */
            amna.$express.get('/auth/facebook/callback',
                passport.authenticate('facebook', { successRedirect: amna.authentication.urls.authSuccess,
                                                    failureRedirect: amna.authentication.urls.authFailure }));
        }

        if (amna.authentication.google) {
            // GET /auth/google
            //   Use passport.authenticate() as route middleware to authenticate the
            //   request.  The first step in Google authentication will involve
            //   redirecting the user to google.com.  After authorization, Google
            //   will redirect the user back to this application at /auth/google/callback
            amna.$express.get('/auth/google',
                passport.authenticate('google', { scope: amna.get('google scope') }),
                function () {
                    // The request will be redirected to Google for authentication, so this
                    // function will not be called.
                });

            // GET /auth/google/callback
            //   Use passport.authenticate() as route middleware to authenticate the
            //   request.  If authentication fails, the user will be redirected back to the
            //   login page.  Otherwise, the primary route function function will be called,
            //   which, in this example, will redirect the user to the home page.
            amna.$express.get('/auth/google/callback',
                passport.authenticate('google', { failureRedirect: amna.authentication.urls.authFailure }),
                function (req, res) {
                    res.redirect(302, amna.authentication.urls.authSuccess);
                });
        }
    };

    return {};
};