diff --git a/.jshintrc b/.jshintrc index 14e5e8db..bbf11574 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,8 +1,7 @@ { "predef": [ "angular", - "Firebase", - "FirebaseSimpleLogin" + "Firebase" ], "bitwise": true, "browser": true, @@ -10,11 +9,10 @@ "forin": true, "indent": 2, "latedef": true, - "maxlen": 115, "noempty": true, "nonbsp": true, "strict": true, "trailing": true, "undef": true, "unused": true -} \ No newline at end of file +} diff --git a/README.md b/README.md index a40ee064..152226d5 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,6 @@ tests, run `grunt test:unit`. To only run the end-to-end [Protractor](https://gi tests, run `grunt test:e2e`. In addition to the automated test suite, there is an additional manual test suite that ensures that -the `$firebaseSimpleLogin` service is working properly with the authentication providers. These tests -can be run with `grunt test:manual`. Note that you must click "Close this window", login to Twitter, +the `$firebaseUser` service is working properly with the authentication providers. These tests can +be run with `grunt test:manual`. Note that you must click "Close this window", login to Twitter, etc. when prompted in order for these tests to complete successfully. diff --git a/bower.json b/bower.json index 620f4c06..f76f2835 100644 --- a/bower.json +++ b/bower.json @@ -31,8 +31,7 @@ ], "dependencies": { "angular": "1.2.x || 1.3.x", - "firebase": "1.0.x", - "firebase-simple-login": "1.6.x" + "firebase": "2.0.x" }, "devDependencies": { "lodash": "~2.4.1", diff --git a/package.json b/package.json index 0c8d532d..07e2ffb9 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "package.json" ], "dependencies": { - "firebase": "1.0.x" + "firebase": "2.0.x" }, "devDependencies": { "coveralls": "^2.11.1", diff --git a/src/FirebaseAuth.js b/src/FirebaseAuth.js new file mode 100644 index 00000000..7f6dbf2e --- /dev/null +++ b/src/FirebaseAuth.js @@ -0,0 +1,271 @@ +/* istanbul ignore next */ +(function() { + 'use strict'; + var FirebaseAuth; + + // Define a service which provides user authentication and management. + angular.module('firebase').factory('$firebaseAuth', [ + '$q', '$timeout', function($q, $t) { + // This factory returns an object containing the current authentication state of the client. + // This service takes one argument: + // + // * `ref`: A Firebase reference. + // + // The returned object contains methods for authenticating clients, retrieving authentication + // state, and managing users. + return function(ref) { + var auth = new FirebaseAuth($q, $t, ref); + return auth.construct(); + }; + } + ]); + + FirebaseAuth = function($q, $t, ref) { + this._q = $q; + this._timeout = $t; + + if (typeof ref === 'string') { + throw new Error('Please provide a Firebase reference instead of a URL when calling `new Firebase()`.'); + } + this._ref = ref; + }; + + FirebaseAuth.prototype = { + construct: function() { + this._object = { + // Authentication methods + $authWithCustomToken: this.authWithCustomToken.bind(this), + $authAnonymously: this.authAnonymously.bind(this), + $authWithPassword: this.authWithPassword.bind(this), + $authWithOAuthPopup: this.authWithOAuthPopup.bind(this), + $authWithOAuthRedirect: this.authWithOAuthRedirect.bind(this), + $authWithOAuthToken: this.authWithOAuthToken.bind(this), + $unauth: this.unauth.bind(this), + + // Authentication state methods + $onAuth: this.onAuth.bind(this), + $getAuth: this.getAuth.bind(this), + $requireAuth: this.requireAuth.bind(this), + $waitForAuth: this.waitForAuth.bind(this), + + // User management methods + $createUser: this.createUser.bind(this), + $changePassword: this.changePassword.bind(this), + $removeUser: this.removeUser.bind(this), + $sendPasswordResetEmail: this.sendPasswordResetEmail.bind(this) + }; + + return this._object; + }, + + + /********************/ + /* Authentication */ + /********************/ + // Common login completion handler for all authentication methods. + _onLoginHandler: function(deferred, error, authData) { + if (error !== null) { + deferred.reject(error); + } else { + deferred.resolve(authData); + } + }, + + // Authenticates the Firebase reference with a custom authentication token. + authWithCustomToken: function(authToken) { + var deferred = this._q.defer(); + + this._ref.authWithCustomToken(authToken, this._onLoginHandler.bind(this, deferred)); + + return deferred.promise; + }, + + // Authenticates the Firebase reference anonymously. + authAnonymously: function(options) { + var deferred = this._q.defer(); + + this._ref.authAnonymously(this._onLoginHandler.bind(this, deferred), options); + + return deferred.promise; + }, + + // Authenticates the Firebase reference with an email/password user. + authWithPassword: function(credentials, options) { + var deferred = this._q.defer(); + + this._ref.authWithPassword(credentials, this._onLoginHandler.bind(this, deferred), options); + + return deferred.promise; + }, + + // Authenticates the Firebase reference with the OAuth popup flow. + authWithOAuthPopup: function(provider, options) { + var deferred = this._q.defer(); + + this._ref.authWithOAuthPopup(provider, this._onLoginHandler.bind(this, deferred), options); + + return deferred.promise; + }, + + // Authenticates the Firebase reference with the OAuth redirect flow. + authWithOAuthRedirect: function(provider, options) { + var deferred = this._q.defer(); + + this._ref.authWithOAuthRedirect(provider, this._onLoginHandler.bind(this, deferred), options); + + return deferred.promise; + }, + + // Authenticates the Firebase reference with an OAuth token. + authWithOAuthToken: function(provider, credentials, options) { + var deferred = this._q.defer(); + + this._ref.authWithOAuthToken(provider, credentials, this._onLoginHandler.bind(this, deferred), options); + + return deferred.promise; + }, + + // Unauthenticates the Firebase reference. + unauth: function() { + if (this.getAuth() !== null) { + this._ref.unauth(); + } + }, + + + /**************************/ + /* Authentication State */ + /**************************/ + // Asynchronously fires the provided callback with the current authentication data every time + // the authentication data changes. It also fires as soon as the authentication data is + // retrieved from the server. + onAuth: function(callback) { + var self = this; + + this._ref.onAuth(callback); + + // Return a method to detach the `onAuth()` callback. + return function() { + self._ref.offAuth(callback); + }; + }, + + // Synchronously retrieves the current authentication data. + getAuth: function() { + return this._ref.getAuth(); + }, + + // Helper onAuth() callback method for the two router-related methods. + _routerMethodOnAuthCallback: function(deferred, rejectIfAuthDataIsNull, authData) { + if (authData !== null) { + deferred.resolve(authData); + } else if (rejectIfAuthDataIsNull) { + deferred.reject("AUTH_REQUIRED"); + } else { + deferred.resolve(null); + } + + // Turn off this onAuth() callback since we just needed to get the authentication data once. + this._ref.offAuth(this._routerMethodOnAuthCallback); + }, + + // Returns a promise which is resolved if the client is authenticated and rejects otherwise. + // This can be used to require that a route has a logged in user. + requireAuth: function() { + var deferred = this._q.defer(); + + this._ref.onAuth(this._routerMethodOnAuthCallback.bind(this, deferred, /* rejectIfAuthDataIsNull */ true)); + + return deferred.promise; + }, + + // Returns a promise which is resolved with the client's current authenticated data. This can + // be used in a route's resolve() method to grab the current authentication data. + waitForAuth: function() { + var deferred = this._q.defer(); + + this._ref.onAuth(this._routerMethodOnAuthCallback.bind(this, deferred, /* rejectIfAuthDataIsNull */ false)); + + return deferred.promise; + }, + + + /*********************/ + /* User Management */ + /*********************/ + // Creates a new email/password user. Note that this function only creates the user, if you + // wish to log in as the newly created user, call $authWithPassword() after the promise for + // this method has been resolved. + createUser: function(email, password) { + var deferred = this._q.defer(); + + this._ref.createUser({ + email: email, + password: password + }, function(error) { + if (error !== null) { + deferred.reject(error); + } else { + deferred.resolve(); + } + }); + + return deferred.promise; + }, + + // Changes the password for an email/password user. + changePassword: function(email, oldPassword, newPassword) { + var deferred = this._q.defer(); + + this._ref.changePassword({ + email: email, + oldPassword: oldPassword, + newPassword: newPassword + }, function(error) { + if (error !== null) { + deferred.reject(error); + } else { + deferred.resolve(); + } + } + ); + + return deferred.promise; + }, + + // Removes an email/password user. + removeUser: function(email, password) { + var deferred = this._q.defer(); + + this._ref.removeUser({ + email: email, + password: password + }, function(error) { + if (error !== null) { + deferred.reject(error); + } else { + deferred.resolve(); + } + }); + + return deferred.promise; + }, + + // Sends a password reset email to an email/password user. + sendPasswordResetEmail: function(email) { + var deferred = this._q.defer(); + + this._ref.resetPassword({ + email: email + }, function(error) { + if (error !== null) { + deferred.reject(error); + } else { + deferred.resolve(); + } + }); + + return deferred.promise; + } + }; +})(); diff --git a/src/firebaseSimpleLogin.js b/src/firebaseSimpleLogin.js deleted file mode 100644 index eab16414..00000000 --- a/src/firebaseSimpleLogin.js +++ /dev/null @@ -1,225 +0,0 @@ -/* istanbul ignore next */ -(function() { - 'use strict'; - var AngularFireAuth; - - // Defines the `$firebaseSimpleLogin` service that provides simple - // user authentication support for AngularFire. - angular.module("firebase").factory("$firebaseSimpleLogin", [ - "$q", "$timeout", "$rootScope", function($q, $t, $rs) { - // The factory returns an object containing the authentication state - // of the current user. This service takes one argument: - // - // * `ref` : A Firebase reference. - // - // The returned object has the following properties: - // - // * `user`: Set to "null" if the user is currently logged out. This - // value will be changed to an object when the user successfully logs - // in. This object will contain details of the logged in user. The - // exact properties will vary based on the method used to login, but - // will at a minimum contain the `id` and `provider` properties. - // - // The returned object will also have the following methods available: - // $login(), $logout(), $createUser(), $changePassword(), $removeUser(), - // and $getCurrentUser(). - return function(ref) { - var auth = new AngularFireAuth($q, $t, $rs, ref); - return auth.construct(); - }; - } - ]); - - AngularFireAuth = function($q, $t, $rs, ref) { - this._q = $q; - this._timeout = $t; - this._rootScope = $rs; - this._loginDeferred = null; - this._getCurrentUserDeferred = []; - this._currentUserData = undefined; - - if (typeof ref == "string") { - throw new Error("Please provide a Firebase reference instead " + - "of a URL, eg: new Firebase(url)"); - } - this._fRef = ref; - }; - - AngularFireAuth.prototype = { - construct: function() { - var object = { - user: null, - $login: this.login.bind(this), - $logout: this.logout.bind(this), - $createUser: this.createUser.bind(this), - $changePassword: this.changePassword.bind(this), - $removeUser: this.removeUser.bind(this), - $getCurrentUser: this.getCurrentUser.bind(this), - $sendPasswordResetEmail: this.sendPasswordResetEmail.bind(this) - }; - this._object = object; - - // Initialize Simple Login. - if (!window.FirebaseSimpleLogin) { - var err = new Error("FirebaseSimpleLogin is undefined. " + - "Did you forget to include firebase-simple-login.js?"); - this._rootScope.$broadcast("$firebaseSimpleLogin:error", err); - throw err; - } - - var client = new FirebaseSimpleLogin(this._fRef, - this._onLoginEvent.bind(this)); - this._authClient = client; - return this._object; - }, - - // The login method takes a provider (for Simple Login) and authenticates - // the Firebase reference with which the service was initialized. This - // method returns a promise, which will be resolved when the login succeeds - // (and rejected when an error occurs). - login: function(provider, options) { - var deferred = this._q.defer(); - var self = this; - - // To avoid the promise from being fulfilled by our initial login state, - // make sure we have it before triggering the login and creating a new - // promise. - this.getCurrentUser().then(function() { - self._loginDeferred = deferred; - self._authClient.login(provider, options); - }); - - return deferred.promise; - }, - - // Unauthenticate the Firebase reference. - logout: function() { - // Simple Login fires _onLoginEvent() even if no user is logged in. We don't care about - // firing this logout event multiple times, so explicitly check if a user is defined. - if (this._currentUserData) { - this._authClient.logout(); - } - }, - - // Creates a user for Firebase Simple Login. Function 'cb' receives an - // error as the first argument and a Simple Login user object as the second - // argument. Note that this function only creates the user, if you wish to - // log in as the newly created user, call $login() after the promise for - // this method has been fulfilled. - createUser: function(email, password) { - var self = this; - var deferred = this._q.defer(); - - self._authClient.createUser(email, password, function(err, user) { - if (err) { - self._rootScope.$broadcast("$firebaseSimpleLogin:error", err); - deferred.reject(err); - } else { - deferred.resolve(user); - } - }); - - return deferred.promise; - }, - - // Changes the password for a Firebase Simple Login user. Take an email, - // old password and new password as three mandatory arguments. Returns a - // promise. - changePassword: function(email, oldPassword, newPassword) { - var self = this; - var deferred = this._q.defer(); - - self._authClient.changePassword(email, oldPassword, newPassword, - function(err) { - if (err) { - self._rootScope.$broadcast("$firebaseSimpleLogin:error", err); - deferred.reject(err); - } else { - deferred.resolve(); - } - } - ); - - return deferred.promise; - }, - - // Gets a promise for the current user info. - getCurrentUser: function() { - var self = this; - var deferred = this._q.defer(); - - if (self._currentUserData !== undefined) { - deferred.resolve(self._currentUserData); - } else { - self._getCurrentUserDeferred.push(deferred); - } - - return deferred.promise; - }, - - // Remove a user for the listed email address. Returns a promise. - removeUser: function(email, password) { - var self = this; - var deferred = this._q.defer(); - - self._authClient.removeUser(email, password, function(err) { - if (err) { - self._rootScope.$broadcast("$firebaseSimpleLogin:error", err); - deferred.reject(err); - } else { - deferred.resolve(); - } - }); - - return deferred.promise; - }, - - // Send a password reset email to the user for an email + password account. - sendPasswordResetEmail: function(email) { - var self = this; - var deferred = this._q.defer(); - - self._authClient.sendPasswordResetEmail(email, function(err) { - if (err) { - self._rootScope.$broadcast("$firebaseSimpleLogin:error", err); - deferred.reject(err); - } else { - deferred.resolve(); - } - }); - - return deferred.promise; - }, - - // Internal callback for any Simple Login event. - _onLoginEvent: function(err, user) { - var self = this; - if (err) { - if (self._loginDeferred) { - self._loginDeferred.reject(err); - self._loginDeferred = null; - } - self._rootScope.$broadcast("$firebaseSimpleLogin:error", err); - } else { - this._currentUserData = user; - - self._timeout(function() { - self._object.user = user; - if (user) { - self._rootScope.$broadcast("$firebaseSimpleLogin:login", user); - } else { - self._rootScope.$broadcast("$firebaseSimpleLogin:logout"); - } - if (self._loginDeferred) { - self._loginDeferred.resolve(user); - self._loginDeferred = null; - } - while (self._getCurrentUserDeferred.length > 0) { - var def = self._getCurrentUserDeferred.pop(); - def.resolve(user); - } - }); - } - } - }; -})(); diff --git a/tests/manual_karma.conf.js b/tests/manual_karma.conf.js index fd8083f7..e60343dc 100644 --- a/tests/manual_karma.conf.js +++ b/tests/manual_karma.conf.js @@ -13,7 +13,6 @@ module.exports = function(config) { '../bower_components/angular/angular.js', '../bower_components/angular-mocks/angular-mocks.js', '../bower_components/firebase/firebase.js', - '../bower_components/firebase-simple-login/firebase-simple-login.js', '../src/module.js', '../src/**/*.js', 'manual/**/*.spec.js'