Auth.js 10.7 KB
define(function(require) {
    'use strict';
    var module = require('module');
    var $ = require('jquery');
    var _ = require('underscore');
    var Backbone = require('backbone');

    function bindMethods() {
        var args = _.toArray(arguments);
        var self = args.shift();
        _.each(args, function(methodName) {
            self[methodName] = _.bind(self[methodName], self);
        });
    }
    function attachStart(self, callback) {
        var start = $.Deferred();
        self.start = function() {
            start.resolve();
            return this;
        };
        return start.done(_.bind(callback, self));
    }
    function attachReady(self) {
        var deferred = $.Deferred().done(function() {
            self.set('isReady', true);
        });

        var promise = deferred.promise();
        self.ready = function() {
            return promise;
        };
        return deferred;
    }

    var authSync = function authSync(origSync, method, model, options) {
        var args = _.toArray(arguments);
        args.shift();
        var url = options.url || _.result(model, 'url');
        if (url) {
            var sessionId = app.get('auth').get('sessionId');
            if (sessionId) {
                options.url = url + (url.indexOf('?') == -1 ? '?' : '&') + 'sessionId=' + sessionId;
            }
        }
        return origSync.apply(this, args);
    };

    var FacebookProvider = Backbone.Model.extend({
        initialize: function(data, globalOptions) {
            // early facebook javascript load
            require(['facebook']);
            FacebookProvider.__super__.initialize.apply(this, arguments);
            var self = this;
            var fbLoaded = $.Deferred();
            var ready = attachReady(this);
            attachStart(this, function() {
                require(['facebook'], function(FB) {
                    FB.init({
                        appId: globalOptions.appId,
                        status: true,
                    });
                    FB.getLoginStatus(function(response) {
                        self.set({
                            'status': response.status,
                            'authResponse': response.authResponse,
                        });
                        fbLoaded.resolve(FB);
                    });
                }, function() {
                    /* global console:false */
                    console.log('error loading facebook javascript');
                    fbLoaded.reject();
                });
            });
            fbLoaded.always(function() {
                ready.resolve();
            });
            // log helper
            this.on('change:status', function() {
                /* global console:false */
                switch (this.get('status')) {
                    case 'connected':
                        console.log('FB connected', this.get('authResponse'));
                        break;
                    case 'not_authorized':
                        console.log('FB not authorized');
                        break;
                    default:
                        console.log('FB not logged in');
                        break;
                }
            }, this);
            this.login = function() {
                fbLoaded.done(function(FB) {
                    FB.login(function(response) {
                        self.set({
                            'status': response.status,
                            'authResponse': response.authResponse,
                        });
                        self.trigger('provider:logged-in');
                    });
                });
                return self;
            };
            this.logout = function() {
                fbLoaded.done(function(FB) {
                    if (self.get('authResponse')) {
                        FB.logout(function(response) {
                            self.set({
                                'status': response.status,
                                'authResponse': null,
                            });
                            self.trigger('provider:logged-out');
                        });
                    } else {
                        self.trigger('provider:logged-out');
                    }
                });
                return self;
            };
            this.attachProviderData = function(data) {
                var authResponse = this.get('authResponse') || {};
                _.extend(data, {
                    facebookAccessToken: authResponse.accessToken,
                    facebookUserId: authResponse.userID,
                });
            };
            bindMethods(this, 'attachProviderData');
        },
    });
    var FormProvider = Backbone.Model.extend({
        initialize: function(data, globalOptions) {
            FormProvider.__super__.initialize.apply(this, arguments);
            var ready = attachReady(this);
            attachStart(this, function() {
                ready.resolve();
            });
            this.login = function() {
                // TODO: possibly fetch userName/password from browser.localStorage
                this.trigger('provider:logged-in');
                return this;
            };
            this.logout = function() {
                this.trigger('provider:logged-out');
                return this;
            };
            this.attachProviderData = function(data) {
                _.extend(data, {
                    formUserName: this.get('userName'),
                    formPassword: this.get('password'),
                });
            };
            bindMethods(this, 'login', 'logout', 'attachProviderData');
        },
    });
    var Auth = Backbone.Model.extend({
        defaults: function() {
            return {
                isLoggedIn: false,
                permissions: new Backbone.Model(),
                user: new Backbone.Model(),
                providers: new Backbone.Model(),
            };
        },
        initialize: function(data, globalOptions) {
            Auth.__super__.initialize.apply(this, arguments);
            (function(self, cookieName) {
                if (!cookieName) {
                    return;
                }
                var sessionId = self.get('sessionId');
                if (sessionId) {
                    $.cookie('cookieName', sessionId);
                } else {
                    self.set('sessionId', $.cookie(cookieName));
                }
                self.on('change:sessionId', function() {
                    var sessionId = this.get('sessionId');
                    if (sessionId) {
                        $.cookie(cookieName, sessionId);
                    } else {
                        $.removeCookie(cookieName);
                    }
                }, self);
            })(this, globalOptions.cookieName);
            var ready = attachReady(this);
            attachStart(this, function() {
                _.invoke(this.get('providers').values(), 'start');
            });

            var self = this;
            function applyLoginResults(resultData) {
                var userData = resultData.user;
                var user = self.get('user');
                self.set('isLoggedIn', !!userData);
                if (userData) {
                    user.set(userData);
                } else {
                    user.clear();
                }
                self.set('sessionId', resultData.sessionId);
                var permissions = self.get('permissions');
                var toErase = {}, toSet = {};
                _.each(permissions.keys(), function(key) {
                    toErase[key] = false;
                });
                _.each(resultData.permissions, function(key) {
                    delete toErase[key];
                    toSet[key] = true;
                });
                permissions.set(toErase, {unset: true}).set(toSet);
            }
            function api(apiName, data) {
                var apiHandler = globalOptions.api || function(apiName, data) {};
                return apiHandler(apiName, data, {sessionId: self.get('sessionId')}).always(function() {
                    self.unset('errorMessage');
                }).done(applyLoginResults).error(function(xhr) {
                    if (xhr.responseJSON && xhr.responseJSON.errorMessage) {
                        self.set('errorMessage', xhr.responseJSON.errorMessage);
                    }
                });
            }
            var addProviderData = _.bind(function addProviderData() {
                var args = _.toArray(arguments);
                var providerToApply, data;
                if (typeof args[0] === 'object') {
                    data = args.shift();
                }
                if (typeof args[0] === 'string') {
                    providerToApply = args[0];
                }
                data = _.extend({}, data);
                var providers = this.get('providers');
                _.each(providerToApply ? [providerToApply] : providers.keys(), function(providerKey) {
                    providers.get(providerKey).attachProviderData(data);
                });
                return data;
            }, this);
            var providerReadies = _.map(this.get('providers').values(), function(provider) {
                return provider.ready();
            });

            $.when.apply($, providerReadies).done(_.bind(function() {
                api('auth:startVisit', addProviderData({
                    userAgent: navigator.userAgent,
                })).always(function() {
                    ready.resolve();
                });
                var providers = this.get('providers');
                _.each(providers.keys(), function(providerKey) {
                    providers.get(providerKey).on('provider:logged-in', function() {
                        api('auth:login:' + providerKey, addProviderData(providerKey));
                    });
                });
            }, this));
            this.login = function(how) {
                var provider = how ? this.get('providers').get(how) : null;
                if (provider) {
                    provider.login();
                }
            };
            this.logout = function() {
                _.invoke(this.get('providers').values(), 'logout');
                api('auth:logout');
            };
            bindMethods(this, 'login', 'logout');
        },
    }, {
        FacebookProvider: FacebookProvider,
        FormProvider: FormProvider,
        wrapSync: function(origSync) {
            return function() {
                return authSync.apply(this, [origSync].concat(_.toArray(arguments)));
            };
        },
        syncMixin: function(Class) {
            Class.prototype.sync = Auth.wrapSync(Class.prototype.sync);
            return Class;
        },
    });
    return Auth;
});