Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.

Commit 9f55685

Browse files
committed
Fixes #590 - $requireAuth/$waitForAuth now detects expired tokens or change in auth status.
1 parent 4562e24 commit 9f55685

File tree

2 files changed

+56
-26
lines changed

2 files changed

+56
-26
lines changed

src/FirebaseAuth.js

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
// Define a service which provides user authentication and management.
66
angular.module('firebase').factory('$firebaseAuth', [
7-
'$q', '$firebaseUtils', '$log', function($q, $firebaseUtils, $log) {
7+
'$q', '$firebaseUtils', function($q, $firebaseUtils) {
88
/**
99
* This factory returns an object allowing you to manage the client's authentication state.
1010
*
@@ -13,21 +13,20 @@
1313
* authentication state, and managing users.
1414
*/
1515
return function(ref) {
16-
var auth = new FirebaseAuth($q, $firebaseUtils, $log, ref);
16+
var auth = new FirebaseAuth($q, $firebaseUtils, ref);
1717
return auth.construct();
1818
};
1919
}
2020
]);
2121

22-
FirebaseAuth = function($q, $firebaseUtils, $log, ref) {
22+
FirebaseAuth = function($q, $firebaseUtils, ref) {
2323
this._q = $q;
2424
this._utils = $firebaseUtils;
25-
this._log = $log;
26-
2725
if (typeof ref === 'string') {
2826
throw new Error('Please provide a Firebase reference instead of a URL when creating a `$firebaseAuth` object.');
2927
}
3028
this._ref = ref;
29+
this._initialAuthResolver = this._initAuthResolver();
3130
};
3231

3332
FirebaseAuth.prototype = {
@@ -246,27 +245,41 @@
246245
* rejected if the client is unauthenticated and rejectIfAuthDataIsNull is true.
247246
*/
248247
_routerMethodOnAuthPromise: function(rejectIfAuthDataIsNull) {
249-
var ref = this._ref;
248+
var ref = this._ref, utils = this._utils;
249+
// wait for the initial auth state to resolve; on page load we have to request auth state
250+
// asynchronously so we don't want to resolve router methods or flash the wrong state
251+
return this._initialAuthResolver.then(function() {
252+
// auth state may change in the future so rather than depend on the initially resolved state
253+
// we also check the auth data (synchronously) if a new promise is requested, ensuring we resolve
254+
// to the current auth state and not a stale/initial state (see https://github.com/firebase/angularfire/issues/590)
255+
var authData = ref.getAuth(), res = null;
256+
if (authData !== null) {
257+
res = utils.resolve(authData);
258+
}
259+
else if (rejectIfAuthDataIsNull) {
260+
res = utils.reject("AUTH_REQUIRED");
261+
}
262+
else {
263+
res = utils.resolve(null);
264+
}
265+
return res;
266+
});
267+
},
250268

251-
return this._utils.promise(function(resolve,reject){
252-
function callback(authData) {
269+
/**
270+
* Helper that returns a promise which resolves when the initial auth state has been
271+
* fetch from the Firebase server. This never rejects and resolves to undefined.
272+
*
273+
* @return {Promise<Object>} A promise fulfilled when the server returns initial auth state.
274+
*/
275+
_initAuthResolver: function() {
276+
var ref = this._ref;
277+
return this._utils.promise(function(resolve) {
278+
function callback() {
253279
// Turn off this onAuth() callback since we just needed to get the authentication data once.
254280
ref.offAuth(callback);
255-
256-
if (authData !== null) {
257-
resolve(authData);
258-
return;
259-
}
260-
else if (rejectIfAuthDataIsNull) {
261-
reject("AUTH_REQUIRED");
262-
return;
263-
}
264-
else {
265-
resolve(null);
266-
return;
267-
}
281+
resolve();
268282
}
269-
270283
ref.onAuth(callback);
271284
});
272285
},

tests/unit/FirebaseAuth.spec.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,31 +289,48 @@ describe('FirebaseAuth',function(){
289289

290290
describe('$requireAuth()',function(){
291291
it('will be resolved if user is logged in', function(){
292+
ref.getAuth.and.returnValue({provider:'facebook'});
292293
wrapPromise(auth.$requireAuth());
293-
callback('onAuth')({provider:'facebook'});
294+
callback('onAuth')();
294295
$timeout.flush();
295296
expect(result).toEqual({provider:'facebook'});
296297
});
297298

298299
it('will be rejected if user is not logged in', function(){
300+
ref.getAuth.and.returnValue(null);
299301
wrapPromise(auth.$requireAuth());
300-
callback('onAuth')(null);
302+
callback('onAuth')();
301303
$timeout.flush();
302304
expect(failure).toBe('AUTH_REQUIRED');
303305
});
304306
});
305307

306308
describe('$waitForAuth()',function(){
307309
it('will be resolved with authData if user is logged in', function(){
310+
ref.getAuth.and.returnValue({provider:'facebook'});
308311
wrapPromise(auth.$waitForAuth());
309-
callback('onAuth')({provider:'facebook'});
312+
callback('onAuth')();
310313
$timeout.flush();
311314
expect(result).toEqual({provider:'facebook'});
312315
});
313316

314317
it('will be resolved with null if user is not logged in', function(){
318+
ref.getAuth.and.returnValue(null);
319+
wrapPromise(auth.$waitForAuth());
320+
callback('onAuth')();
321+
$timeout.flush();
322+
expect(result).toBe(null);
323+
});
324+
325+
it('promise resolves with current value if auth state changes after onAuth() completes', function() {
326+
ref.getAuth.and.returnValue({provider:'facebook'});
327+
wrapPromise(auth.$waitForAuth());
328+
callback('onAuth')();
329+
$timeout.flush();
330+
expect(result).toEqual({provider:'facebook'});
331+
332+
ref.getAuth.and.returnValue(null);
315333
wrapPromise(auth.$waitForAuth());
316-
callback('onAuth')(null);
317334
$timeout.flush();
318335
expect(result).toBe(null);
319336
});

0 commit comments

Comments
 (0)