firebase simple login tutorial missing user - javascript

Tutorial: http://www.thinkster.io/angularjs/wBhtRLWHIR/6-authenticating-users-with-a-service
I'm following this tutorial and it seems like I'm losing my user as soon as they register.
Here is my auth.js factory:
'use strict';
app.factory('Auth', function($firebaseSimpleLogin, FIREBASE_URL, $rootScope){
var ref = new Firebase(FIREBASE_URL);
var auth = $firebaseSimpleLogin(ref);
var Auth = {
register : function(user) {
return auth.$createUser(user.email, user.password);
},
signedIn : function() {
// PROBLEM: authUser is always null
console.log(auth.user);
return auth.user !== null;
},
logout : function () {
auth.$logout();
}
};
$rootScope.signedIn = function () {
return Auth.signedIn();
};
return Auth;
});
Here is my auth.js controller:
'use strict';
app.controller('AuthCtrl', function($scope, $location, Auth){
if (Auth.signedIn()) {
$location.path('/');
}
$scope.register = function () {
Auth.register($scope.user).then(function (authUser) {
console.log(authUser);
$location.path('/');
});
};
});
The console.log under signedIn in the factory is always null. Any idea where the disconnect is? The registration itself is working fine, and authUser is populated in the console.log in the controller when registering.

The latest documentation for Angularfire says that the $createUser method of $firebaseSimpleLogin returns a promise but it doesn't mention any parameters being passed to the then callback.
You can use the $getCurrentUser method to get the current user after the user registers.
The tutorial needs to be updated and you should always be checking the documentation for whatever libraries you're using yourself.
Your code for signedIn should look like this:
Auth.signedIn = function() {
auth.$getCurrentUser().then(function(currentUser) {
console.log(currentUser);
}, function() {
console.log('error');
});
};

I found a very similar question that is further along in the tutorial :can't show logout button after $createUser
In the answer I learned that angularfire used to automatically log the user in after it was created. Apparently now it no longer does that which is why the auth.user in signedIn was null.

I am now doing the same tutorial. This code (in auth controller) worked for me:
$scope.register = function () {
Auth.register($scope.user).then(function (authUser) {
console.log(authUser);
Auth.login($scope.user);
$location.path('/');
});
};
I'm a total n00b, but what (I think) this is doing is authenticating the user, then running a function that logs the user in right after. Logout button is now functioning as expected.

Related

angularfire cannot read property facebook - how do i keep using authData throughout app

I´m working on an android game using ionic framework and firebase.
My plan is to let users login using facebook login with firebase, after this i want to save the game data to the users database key.
The first part is working. the script makes an database array based on the users facebook details. but the problem is after this is made, i cant seem to let angular change any database data. It seems like the authData is stuck in the login function...
Is there a way to keep the authdata for use in different controllers and functions?
app.factory("Auth", function($firebaseAuth) {
var FIREB = new Firebase("https://name.firebaseio.com");
return $firebaseAuth(FIREB);
});
app.controller('HomeScreen', function($scope, Auth, $firebaseArray) {
Auth.$onAuth(function(authData){
$scope.authData = authData;
});
var users = new Firebase("https://name.firebaseio.com/users/");
// create a synchronized array
$scope.users = $firebaseArray(users);
$scope.facebooklogin = function() {
Auth.$authWithOAuthPopup("facebook").then(function(authData){
users.child(authData.facebook.cachedUserProfile.id).set({
Username: authData.facebook.displayName,
Id: authData.facebook.cachedUserProfile.id,
Gender: authData.facebook.cachedUserProfile.gender,
Email: authData.facebook.email,
level: "1"
});
}).catch(function(error){
});
}
$scope.facebooklogout = function() {
Auth.$unauth();
}
$scope.changeLVL = function(authData) {
users.child(authData.facebook.cachedUserProfile.id).set({
level: "2"
});
}
});
And this is the datastructure it creates in firebase
users
998995300163718
Email: "Name#email.com"
Gender: "male"
Id: "998995300163718"
Username: "name lastname"
level: "1"
and after trying to edit i get this error... (using the changelevel function)
TypeError: Cannot read property 'facebook' of undefined
at Scope.$scope.changeLVL (http://localhost:8100/js/controllers.js:35:23)
at fn (eval at <anonymous> (http://localhost:8100/lib/ionic/js/ionic.bundle.js:21977:15), <anonymous>:4:218)
at http://localhost:8100/lib/ionic/js/ionic.bundle.js:57606:9
at Scope.$eval (http://localhost:8100/lib/ionic/js/ionic.bundle.js:24678:28)
at Scope.$apply (http://localhost:8100/lib/ionic/js/ionic.bundle.js:24777:23)
at HTMLButtonElement.<anonymous> (http://localhost:8100/lib/ionic/js/ionic.bundle.js:57605:13)
at HTMLButtonElement.eventHandler (http://localhost:8100/lib/ionic/js/ionic.bundle.js:12103:21)
at triggerMouseEvent (http://localhost:8100/lib/ionic/js/ionic.bundle.js:2870:7)
at tapClick (http://localhost:8100/lib/ionic/js/ionic.bundle.js:2859:3)
at HTMLDocument.tapMouseUp (http://localhost:8100/lib/ionic/js/ionic.bundle.js:2932:5)
The main issue is you're relying on the cachedUserProfile.gender property to exist. This isn't guaranteed to be there for every user. You'll need to find a fallback to avoid an error.
Let's simplify by injecting the user via the resolve() method in the router. Don't mind the structure of the code, it's from the Angular Styleguide (my preferred way of writing Angular apps).
angular.module("app", ["firebase"])
.config(ApplicationConfig)
.factory("Auth", Auth)
.controller("HomeScreen", HomeController);
function Auth() {
var FIREB = new Firebase("https://name.firebaseio.com");
return $firebaseAuth(FIREB);
}
function ApplicationConfig($stateProvider) {
$stateProvider
.state("home", {
controller: "HomeScreen",
templateUrl: "views/home.html"
})
.state("profile", {
controller: "ProfileScreen",
templateUrl: "views/profile.html",
resolve: {
currentUser: function(Auth) {
// This will inject the authenticated user into the controller
return Auth.$waitForAuth();
}
}
});
}
function HomeController($scope, Auth, $state) {
$scope.googlelogin = function() {
Auth.$authWithOAuthPopup("google").then(function(authData) {
users.child($scope.authData.google.cachedUserProfile.id).set({
Username: $scope.authData.google.cachedUserProfile.id,
Gender: $scope.authData.google.cachedUserProfile.gender || ""
});
$state.go("app.next");
});
}
}
function ProfileController(currentUser) {
console.log(currentUser.facebook); // injected from router
}
The benefit of this approach is that you don't have to check for authenticated users in the controller. If the user is injected, you know you have an authenticated user.
Check out the AngularFire docs for more information.

Angular.JS API using a factory

I've written a backend service which is used by a Angular.JS frontend using a factory, like so:
angular.module('app.social', ['ngResource'])
.factory('Social', function($http) {
return {
me: function() {
return $http.get('http://localhost:3000/me');
},
likeVideo: function(link) {
return $http.post('http://localhost:3000/like/video', { link : link });
},
post: function(link) {
return $http.post('http://localhost:3000/post', { link : link });
},
postVideo: function(link) {
return $http.post('http://localhost:3000/post/video', { link : link });
},
friends: function() {
return $http.get('http://localhost:3000/friends');
},
taggableFriends: function() {
return $http.get('http://localhost:3000/friends/taggable');
},
videos: function() {
return $http.get('http://localhost:3000/videos');
}
};
});
The Social.me endpoint receives profile information from the REST backend. This function is used in various Angular controllers, however (profile page, item detail page, header account button etc.). This means that for every view, profile information is requested from http://localhost:3000/me. Is this good practice, or is it a better idea to cache the information within the factory?
EDIT: Updated code (based on answer from #Rebornix):
angular.module('app.social', ['ngResource'])
.service('SocialService', function() {
var serviceData = {
me: null
};
return serviceData;
})
.factory('Social', function($http, SocialService) {
return {
me: function() {
if (SocialService.me === null) {
return $http.get('http://localhost:3000/me').then(function(response) {
SocialService.me = response.data;
return SocialService.me;
});
} else {
return SocialService.me;
}
}
}
};
});
In the controller, I use:
angular.module('app.profile', [])
.controller('ProfileCtrl', ['$window', '$scope', 'Social', function($window, $scope, Social) {
$scope.me = Social.me();
}])
And the view:
<div ng-controller="ProfileCtrl">
<h1 class="profile-name">{{ me.name }}</h1>
</div>
But the view is not updated as the Facebook.me value get initialized on the first request. I guess I have to manually trigger $scope.$apply() somehow?
You can create a service as storage across controllers like
angular.module('app.social', ['ngResource'])
.service("SocialService", function() {
var info = {
me: null,
friends: []
};
return info;
})
.factory('Social', function($http, SocialService) {
return {
me: function() {
$http.get('http://localhost:3000/me').then(function(response){
SocialService.me = response.data;
});
},
Then in all your controllers, reference infoService instead of calling API again. What you need to is fetching latest data and refresh infoService, all controllers scope will be notified with this change.
In your controller
angular.module('app.profile', [])
.controller('ProfileCtrl', ['$window', '$scope', 'SocialService', 'Social', function($window, $scope, SocialService, Social) {
$scope.SocialService = SocialService;
// Kick off social factory to update user info, you can move it into
// any other functions like `ng-click`.
Social.me();
}])
Then in your view
{{SocialService.me}}
(function (app) {
'use strict';
app.factory('myService', MyService);
MyService.$inject = ['$q', 'serviceResource'];
function MyService($q, serviceResource) {
var jobs = [];
var service = {
getJobs: getJobs
};
return service;
//////////////////////////////////////
function getJobs(refresh) {
if (refresh) {
return serviceResource.autosysJobs().$promise.then(function (data) {
jobs = data;
return jobs;
}, function (err) {
throw err;
});
}
else {
var deferrer = $q.defer();
deferrer.resolve(jobs);
return deferrer.promise;
}
}
}
}(angular.module('app')));
you can pass a bool argument to tell weather to get local copy or fresh copy
It all depends upon the frequency of data change in back end data change and degree of tolerance of data inconsistency in your application. if the source data is changing too frequently and you can't afford inconsistent data then you have no choice other than to get fresh copy every time, but if that's not the case then you can cash data locally

firebaseAuth with Angular: User Login

I am using Angular (1.3.5) and Firebase to write a toy blog program, and am currently struggling with the user login part.
I first created an Angular module:
var blogApp = angular.module('blogApp', ['ngRoute', 'firebase', 'RegistrationController']);
Then on top of blogApp, I created a controller called ** RegistrationController **:
blogApp.controller('RegistrationController', function ($scope, $firebaseAuth, $location) {
var ref = new Firebase('https://myAppName.firebaseio.com');
// var authLogin = $firebaseAuth(ref);
$scope.login = function(){
ref.authWithPassword({
email: $scope.user.email,
password: $scope.user.password
}, function(err, authData) {
if (err) {
$scope.message = 'login error';
} else {
$scope.message = 'login sucessful!';
}
});
}; // login
}); //RegistrationController
I attached the login() method to ng-submit in my user-login form in the scope of RegistratinController.
When I click to submit the login form, the form does not make any response, without any errors showing.
The login form works only when I click the 'Submit' button twice - why is this? confusing
You are using the Firebase JavaScript library and not AngularFire for the login methods.
You need to pass the Firebase reference into the $firebaseAuth binding.
var auth = $firebaseAuth(ref);
From there you can call auth.$authWithPassword.
$scope.login = function(){
auth.$authWithPassword({
email: $scope.user.email,
password: $scope.user.password
}, function(err, authData) {
if (err) {
$scope.message = 'login error';
} else {
$scope.message = 'login successful!';
}
});
}; // login
AngularFire is an Angular binding for the Firebase Library that handles auto syncing over objects and arrays. AngularFire also handles when to call $scope.apply to properly update the view.
In your case, the login code is working the first click, but it doesn't get applied to the page. You can wrap this code in a $timeout, but it would be better to use the $firebaseAuth service.
Thanks to user jacobawenger, for the solution he posted here:
Can't get new password authentication methods to work in AngularFire 0.9.0?
This works as expected:
myApp.controller('RegistrationController',
function($scope, $firebaseAuth, $location) {
var ref = new Firebase('https://myApp.firebaseio.com');
var simpleLogin = $firebaseAuth(ref);
$scope.login = function() {
simpleLogin.$authWithPassword({
email: $scope.user.email,
password: $scope.user.password
}).then(function() {
$location.path('/mypage');
}, function(err) {
$scope.message = err.toString();
});
} // login
}); // RegistrationController

Route resolve promise, not fully resolved - angularjs

So, what I'm trying to do here is something simple:
check the role of the loggedUser on each route (with a resolve that sets the user if a token or login credentials are valid on the backend)
redirect to the intended route
if not allowed for a route, redirect to a different route
In my route provider I have something like
$routeProvider
...
.when('/admin', {
templateUrl: 'views/admin/dashboard.html',
controller: 'AdminDashboardCtrl',
resolve: {
checkLoggedUser: check
}
})
...
where ckeck is this function
var check = function($rootScope, $q, AuthService) {
var deferred = $q.defer();
if($rootScope.loggedUser) {
return;
}
console.log('inside resolve check')
AuthService.check().success(function(data) {
$rootScope.loggedUser = data.user;
deferred.resolve(data.user);
});
console.log('finished check')
return deferred.promise;
};
And my AuthService.check() is this function
check: function()
{
var authtoken = StorageService.get('authtoken');
if(!authtoken) {
$location.path('login');
}
console.log('before returning');
return $http.post($rootScope.base + 'auth/authenticate', { 'authtoken': authtoken });
},
In my .run(function I have
$rootScope.$on('$routeChangeSuccess', function() {
setIntendedUrl();
console.log($rootScope.loggedUser);
console.log($location.path());
});
and setIntendedUrl() check for the loggedUser and redirects to the correct page (or, in what I'm trying to accomplish, redirect to a different page if not allowed, for example the loggedUser has role = 1, can visit only the routes /admin, if a user has role = 2, and the requested path is /admin, he has to be redirected to /user)
So after all this code, when the app run this is my log in the console (see in the code where are they called)
inside resolve check app.js:29
before returning authservice.js:24
finished check app.js:36
intended: /admin/agents/create app.js:149 <--- here is where I redirect the user
Object {id: "21", name: "Kyle", surname: "Butler", roleId: "2"...} app.js:167
/admin/agents/create <--- requested path
This is not what I was expecting, so the first three logs are good, the third doesn't wait the promise to be returned (so I don't have a loggedUser) then the AuthService:check() returns the user and it's everything done at this point, the user with role = 2 is in a route that is not allowed to see.
Just to complete the code, this is the setIntendedUrl function
var setIntendedUrl = function() {
intended = $location.path();
console.log('intended: ' + intended)
if(intended !== '/login') {
if($rootScope.loggedUser && $rootScope.loggedUser.roleId === '1' && !/^\/admin*/.test(intended)) {
intended = '/admin';
} else if($rootScope.loggedUser && $rootScope.loggedUser.roleId === '2' && !/^\/manager*/.test(intended)) {
intended = '/manager';
}
StorageService.set('intended', intended);
//$location.path(intended);
}
};
What I am doing wrong? Why the user in the check function is not resolved before the other code is executed?
Can you make use of session/locals storage or $rootScope where you can store the users authorization object with given routes, permission info once user logged in.
Now is route resolve or run() block you can retrieve the user auth object perform authorization action.
e.g.
.run(['sessionService', '$rootScope', '$location', function(sessionService, $rootScope, $location) {
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
var currentUser = sessionService.get('user_details');
if(next.taskAccess && next.taskAccess != ""){
var hasPerm = $rootScope.getPermission(next.taskAccess);
if(!hasPerm){
$location.path('/unauthorized');
}
}
});
}]);

How to turn off eventlistener in factory method which is returning promise to controllers?

I have a loginService factory used to perform login, logout and provide user data to controllers. Because I need to update userdata in controllers every time loginstate changes, my factory method is returning an update promise:
app.controller('TestCtrl', function ($scope, loginService) {
loginService.currentUserData().then(null, null, function(CurrUserData){
$scope.CurrUserData = CurrUserData;
});
});
In loginService I'm listening to $firebaseSimpleLogin:login/logout events and after they're fired, I pass the userdata object (returned by function based on UID) or null ($fbSimpleLogin:logout event) to $emit.
And finally, in my loginService.currentUserData() method I'm listening to this emitted events and returning deferred.notify(userdata/null).
First issue is that when I change the view (template+ctrl+location), I need to invoke $firebaseSimpleLogin:login/logout event to deliver my userData to new controller. Now, I'm doing it by $locationChangeStart event, but there should be better way...
And last issue: when I'm changing the view, there are more data calls, than I expectet.
Probably every controller add event listeners on $rootScope by calling loginService.currentUserData()? Described code below:
$rootScope.$on('$firebaseSimpleLogin:login', function (e, authUser) {
findUserByUid(authUser.uid);
});
$rootScope.$on('$firebaseSimpleLogin:logout', function() {
$rootScope.$emit('userLogout', null);
});
$rootScope.$on('$locationChangeStart', function(event, next, current) {
currentUser().then(function(u){
$timeout(function() { // without this same event on viewchange is fired
// by simplelogin, collision (I need to replace this whole block with invoking simpleloginevent)
if (u) {$rootScope.$emit('$firebaseSimpleLogin:login', u);
} else {$rootScope.$emit('$firebaseSimpleLogin:logout', null);};
}, 150);
});
});
function findUserByUid (uid) {
var query = $firebase(usersRef.startAt(uid).endAt(uid));
query.$on('loaded', function () {
var username = query.$getIndex()[0];
setCurrentUser(username);
});
}
function setCurrentUser (username) {
if (username) {$rootScope.$emit('userData', $firebase(usersRef).$child(username));};
}
var currentUserData = function () { // this method is used in CTRLs
var deferred = $q.defer();
var uDl = $rootScope.$on('userData', function(e, FbUserData){deferred.notify(FbUserData); });
var uLl = $rootScope.$on('userLogout', function(){deferred.notify(null); });
return deferred.promise;
};
I recently wrote a demo AngularFire app that has similar functionality. The way I found to handle this is only worry about three points.
When the user logs in $rootScope.$on('$firebaseSimpleLogin:$login')
When the user logs out $rootScope.$on('$firebaseSimpleLogin:$logout')
Calling $getCurrentUser()
This will be able to capture the login life cycle. Since you need to know who the current user is, you can rely on the $firebaseSimpleLogin method rather than trying to $emit your own events.
You also could resolve the current user in the $routeProvider for each view. This way each view won't be rendered until the user has been loaded.
Here's the plunker project and the example Factory:
http://plnkr.co/edit/M0UJmm?p=preview
// Auth factory that encapsulates $firebaseSimpleLogin methods
// provides easy use of capturing events that were emitted
// on the $rootScope when users login and out
.factory('Auth', function($firebaseSimpleLogin, Fb, $rootScope) {
var simpleLogin = $firebaseSimpleLogin(Fb);
return {
getCurrentUser: function() {
return simpleLogin.$getCurrentUser();
},
login: function(provider, user) {
simpleLogin.$login(provider, {
email: user.email,
password: user.password
});
},
logout: function() {
simpleLogin.$logout();
},
onLogin: function(cb) {
$rootScope.$on('$firebaseSimpleLogin:login',
function(e, user) {
cb(e, user);
});
},
onLogout: function(cb) {
$rootScope.$on('$firebaseSimpleLogin:logout',
function(e, user) {
cb(e, user);
});
}
}
})

Categories