Been playing around with firebase and angularjs and just trying to put some little things together. I have the auth working now in a controller with this function called on the sign in button click:
$scope.signin = function(){
var user1 = $scope.cred.user;
var pass1 = $scope.cred.password;
var ref = new Firebase("https://kingpinapp.firebaseio.com");
var auth = new FirebaseAuthClient(ref, function(error, user) {
if (user) {
// user authenticated with Firebase
console.log(user);
} else if (error) {
// an error occurred authenticating the user
console.log(error);
} else {
// user is logged out
}
});
auth.login('password', {
email: user1,
password: pass1,
rememberMe: false
});
console.log("tracer");
}
Now this is great and works fine. But it seems to work in a async manner for example my console.log("tracer") returns before the user object of the auth.login. I know I probably need to work with promises to get this done and tried doing the following:
var defer = $q.defer();
defer.auth
.then(function() {
auth.login('password', {
email: user1,
password: pass1,
rememberMe: false
});
})
.then(function() {
console.log("tracer");
})
But i'm receiving a $q is not defined after declaring it in the controller module. So what I'm trying to do is
check if the user is logged in.
wait till I receive a yes/no
if not logged in. use auth.login
else user user logged in do some other things
I thought of putting the auth.login function in the else of the variable auth but that doesn't seem like it would work. Just trying to figure out the proper logic in understanding how to get this to work.
You don't want to have a FirebaseAuthClient per controller, but you do want to alert all of your controllers when a user's auth state changes.
FirebaseAuthClient will take care of session storage for you. You just need to hide your sign in screen/button once a user is successfully signed in.
Try something like this:
.service('myAuthService', ["$rootScope", function($rootScope) {
var ref = new Firebase("https://kingpinapp.firebaseio.com");
this.auth = new FirebaseAuthClient(ref, function(error, user) {
if (user) {
$rootScope.$emit("login", user);
}
else if (error) {
$rootScope.$emit("loginError", error);
}
else {
$rootScope.$emit("logout");
}
});
}])
.controller('myCtrl', ["$scope", "$rootScope", "myAuthService", function($scope, $rootScope, myAuthService) {
$scope.signin = function() {
var user1 = $scope.cred.user;
var pass1 = $scope.cred.password;
myAuthService.auth.login('password', {
email: user1,
password: pass1,
rememberMe: false
});
}
// listen for user auth events
$rootScope.$on("login", function(event, user) {
// do login things
$scope.user = user;
})
$rootScope.$on("loginError", function(event, error) {
// tell the user about the error
})
$rootScope.$on("logout", function(event) {
// do logout things
})
}])
<button ng-show="user" ng-click="signin()">Sign in</button>
Make sure you are including $q as a dependency in your controller. In the simple case:
function MyController($scope, $q, angularFire) {
$scope.signin = ...
}
Or if you're using the "proper" module syntax:
angular.module("MyModule", ["firebase"]).
controller("MyController", ["$scope", "$q", "angularFire", function($scope, $q, aF) {
$scope.signin = ...
}]);
Learn more about angular dependency injection here: http://docs.angularjs.org/guide/di, and take a look at https://gist.github.com/sbrekken/5151751 for an example of Firebase auth using deferred.
I've posted the answer to this question already here FACTORY: get current user.id for Firebase Simple Login (Email / Password)
It's a pretty solid solution to this problem and may be just what you're looking for.
Related
I'm using Satellizer for authentication in my Angular app and have pretty much everything working... except that I can't seem to figure out how to display the username (or email) after successful login in the navbar.
My login controller looks like this
$scope.login = function() {
$auth.login($scope.user).then(function(response) {
$scope.user = JSON.stringify(response.data.user);
localStorage.setItem('user', user);
$scope.user = response.data;
$rootScope.authenticated = true;
$rootScope.currentUser = response.data.user;
// redirect user here after successful login
$state.go('home');
}
}
I have this in my $states (using UI Router) for global access
.run(function ($rootScope, $auth, $state) {
$rootScope.$on('$stateChangeStart', function (event, toState, fromState) {
if (toState.loginRequired && !$auth.isAuthenticated()) {
//if ($auth.isAuthenticated()) {
$rootScope.currentUser = JSON.parse($state, localStorage.currentUser);
//$rootScope.currentUser = JSON.stringify($state, localStorage.currentUser);
$state.go('/login');
event.preventDefault();
};
});
});
And then this in my navbar controller
.controller('NavbarCtrl', function($scope, $rootScope, $window, $auth) {
$scope.isAuthenticated = function() {
return $auth.isAuthenticated();
$scope.user.email = $localStorage.currentUser.email;
}
}
I'm not getting any errors in the console, so I'm not sure exactly where I'm going wrong...?
currentUser is undefined in the localStorage, I thought I was setting that in my login controller code above...?
First, you store the user object after login in localStorage.user, then you read currentUser in NavbarCtrl with
$localStorage.currentUser.email;
You should use the same property user, i.e
$scope.user.email = localStorage.user.email;
But then, why do you need this in local storage?
since you put the currentUser in the $rootScope, you should be able to directly use it in your navbar, e.g.
<span>{{currentUser.email}}</span>
I'm trying to work out how to unit test my login controller with Karma/Jasmine/Mocha.
I basically want to test if a 200 comes back from the $auth.login() then the message saved should be equal to "successfully logged in",
otherwise if I receive a 401 then the message that comes back should be "error logging in".
UPDATE
This is where I'm at, at the moment.
login.controller.js
function loginCtrl($auth, $scope, $rootScope, $location) {
var vm = this;
vm.login = function() {
var credentials = { email: vm.email, password: vm.password };
// Use Satellizer's $auth service to login
$auth.login(credentials).then(function() {
vm.message = "Successfully logged in!";
}, function(error) {
vm.message = "Error logging in!";
}).then(function(responses) {
$location.path('home');
});
};
}
login.controller.spec.js
describe('Login Controller', function() {
var q, scope, ctrl, auth;
beforeEach(module('app.login'));
beforeEach(inject(function($q, $rootScope, $controller, $auth) {
q = $q;
scope = $rootScope.$new();
ctrl = $controller('loginCtrl', { $scope: scope, SessionService: sessionService, $auth: auth, $q: q });
auth = $auth;
}));
it('should present a successfull message when logged in', function () {
var defer = q.defer();
sinon.stub(auth, 'login')
.withArgs({ email: 'test#test.com', password: 'test_password' })
.returns(defer.promise);
ctrl.login();
defer.resolve();
scope.$apply();
expect(ctrl.message).to.equal('Successfully logged in!');
});
});
Since this is your controller test, you'd probably need to spyOn your service ($auth) like so (in Jasmine) -
var defer = $q.defer();
spyOn('$auth', login).andReturn(defer.promise);
controller.email = 'test#test.com';
controller.password = 'test_password';
controller.login();
defer.resolve();
scope.$apply();
expect($auth.login).toHaveBeenCalledWith({ email: 'test#test.com', password: 'test_password' });
expect(scope.message).toEqual("successfully logged in");
and for the failure case using defer.reject() and pretty much the same format for assertion.
In my opinion, you'd end up worrying about http related status-codes or responses only at the service level and not at the controller level. There you'd use $httpBackend to mock the responses with their status-codes and the corresponding responses.
EDIT
In mocha, as per my research, you'd end up doing something like -
sinon.stub($auth, 'login')
.withArgs({ email: 'test#test.com', password: 'test_password' })
.returns(defer.promise);
to stub the method. And the verification of the call as -
sinon.assert.calledOnce($auth.login);
Rest of it remains the same. The assertion of the message will also change to assert.equal for mocha.
EDIT
Checkout this fiddle - http://jsfiddle.net/9bLqh5zc/. It uses 'sinon' for the spy and 'chai' for assertion
As of this post, I'm trying to figure out if the user is logged in (using a token based authentication).
The scheme is following :
1/ The page loads, app run is called, and authenticated is set to false as default
app.run(function($http, UserService) {
UserService.requestCurrentUser();
$http.defaults.xsrfHeaderName = 'X-CSRFToken';
$http.defaults.xsrfCookieName = 'csrftoken';
});
app.constant('AUTHENTICATED', false);
2/ UserService call for its method requestCurrentUser() in which a http get is sent to the correct url with the token in its header.
If token is correct, this sends back the user (success case, we're authenticated).
If not, I get a permission error (error case, we're not authenticated).
This updates currentUserproperty and AUTHENTICATED constant.
app.factory('UserService', function ($http, $q, $window, AUTHENTICATED) {
var _currentUser = {};
return {
getCurrentUser: function() {
return _currentUser;
},
setCurrentUser: function(user) {
_currentUser = user;
},
requestCurrentUser: function() {
return $http.get('/accounts/api/').then(
function (response) {
_currentUser = response.data;
AUTHENTICATED = true;
},
function (error) {
AUTHENTICATED = false;
}
);
},
};
});
3/ Controller is called and authenticated and currentUser scope values are updated.
app.controller('AuthCtrl', function ($scope, AuthService, UserService, AUTHENTICATED) {
$scope.authenticated = AUTHENTICATED;
$scope.currentUser = UserService.getCurrentUser();
});
Problem is that controller tries to reach the values before requestCurrentUser method (launched in app run) has received a response. So where should I launch requestCurrentUser to get the expected behavior ?
Thanks
What you could do it wrap your user state object in a parent object. For example:
var state = {
_currentUser: {}
};
return {
getUserState: function(){ return state; }
};
Then inside your controller:
$scope.state = UserService.getUserState();
This way, when your user updates (no matter when or how in your service), anything bound to the state will receive the update. So your controller will have access to state._currentUser when it is available.
I am trying to anonymously authenticate users using AngularFire. I want to authenticate a user only once (so, if the user has already been authenticated, a new uid won't be generated). When I use the code below, I get a previous_websocket_failure notification. I also get an error in the console that says TypeError: Cannot read property 'uid' of null. When the page is refreshed, everything works fine.
Any thoughts on what I'm doing wrong here?
app.factory('Ref', ['$window', 'fbURL', function($window, fbURL) {
'use strict';
return new Firebase(fbURL);
}]);
app.service('Auth', ['$q', '$firebaseAuth', 'Ref', function ($q, $firebaseAuth, Ref) {
var auth = $firebaseAuth(Ref);
var authData = Ref.getAuth();
console.log(authData);
if (authData) {
console.log('already logged in with ' + authData.uid);
} else {
auth.$authAnonymously({rememberMe: true}).then(function() {
console.log('authenticated');
}).catch(function(error) {
console.log('error');
});
}
}]);
app.factory('Projects', ['$firebaseArray', '$q', 'fbURL', 'Auth', 'Ref', function($firebaseArray, $q, fbURL, Auth, Ref) {
var authData = Ref.getAuth();
var ref = new Firebase(fbURL + '/projects/' + authData.uid);
console.log('authData.uid: ' + authData.uid);
return $firebaseArray(ref);
}]);
In your Projects factory, you have assumed authData will not be null. There are no guarantees here, since your Projects factory is initialized as soon as you inject it into another provider. I also noticed that your Auth service doesn't actually return anything. This probably means that the caller has to know the internal workings and leads to quite a bit of coupling. A more SOLID structure would probably be as follows:
app.factory('Projects', function(Ref, $firebaseArray) {
// return a function which can be invoked once
// auth is resolved
return function(uid) {
return $firebaseArray(Ref.child('projects').child(uid));
}
});
app.factory('Auth', function(Ref, $firebaseAuth) {
return $firebaseAuth(Ref);
});
app.controller('Example', function($scope, Auth, Projects) {
if( Auth.$getAuth() === null ) {
auth.$authAnonymously({rememberMe: true}).then(init)
.catch(function(error) {
console.log('error');
});
}
else {
init(Auth.$getAuth());
}
function init(authData) {
// when auth resolves, add projects to the scope
$scope.projects = Projects(authData.uid);
}
});
Note that dealing with auth in your controllers and services should generally be discouraged and dealing with this at the router level is a more elegant solution. I'd highly recommend investing in this approach. Check out angularFire-seed for some example code.
I'm using angularFireAuth and I want to retrieve the logged in user's info and use in
all the controllers or services when the app is initial.
Currently, I used this in every controller but i having some problem.
$scope.$on("angularFireAuth:login", function(evt, user){
console.log(user);
});
The callback will not call if it is not a full page load or return null when app initial.
I need some tips for how can I return the authenticated user's info so I can use when app is initial and in all the controllers and services.
Example
When in controller or services
$scope.auth.user.id will return user's ID
$scope.auth.user.name will return user's name
etc
I would start with a userService for this:
angular.module('EventBaseApp').service('userService', function userService() {
return {
isLogged: false,
username: null
}
});
And write a LoginCtrl controller:
angular.module('EventBaseApp')
.controller('LoginCtrl', function ($scope, userService, angularFireAuth) {
var url = "https://example.firebaseio.com";
angularFireAuth.initialize(url, {scope: $scope, name: "user"});
$scope.login = function() {
angularFireAuth.login("github");
};
$scope.logout = function() {
angularFireAuth.logout();
};
$scope.$on("angularFireAuth:login", function(evt, user) {
userService.username = $scope.user;
userService.isLogged = true;
});
$scope.$on("angularFireAuth:logout", function(evt) {
userService.isLogged = false;
userService.username = null;
});
});
Inject the userService anywhere you want the user.
My app that am currently working on that uses this - https://github.com/manojlds/EventBase/blob/master/app/scripts/controllers/login.js
Based on ideas presented here - http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app
i'm not sure quite what your question is. but if you are looking to authorise once rather than in each controller, you can put the code into the module instead and put it into the $rootScope.
var myapp = angular.module('myapp').run(
function ($rootScope) {
$rootScope.user = null;
$rootScope.$on("angularFireAuth:login", function (evt, user) {
$rootScope.user = user;
});
});