Can't retrieve anonymous authenticated user's info on first login - angularjs

Logic: users select a few items on the homepage, then click a 'confirm' button which starts a Firebase $signInAnonymously() auth flow. Their selection is stored under their users/{uid} branch in the database tree and they are redirected to a checkout page that retrieves their selection and asks for more information to proceed.
Issue: when the user lands on the checkout page for the first time their auth state cannot be retrieved (so their selection doesn't appear). However when they refresh the page, everything works as expected for all subsequent attempts (their user info is stored and now retrievable)
Code:
User auth and selection setter/getter factory userService
var auth = $firebaseAuth();
var usersRef = $firebaseRef.users; // custom ref in app config
// Authenticate anonymously to create user session
function startSession() {
return auth.$signInAnonymously()
.then(function(user) {
return user;
})
.catch(function(error) {
console.log(error);
});
}
// Check authentication state before everything loads
function checkAuthState() {
return $q(function(resolve) {
var unsubscribe = auth.$onAuthStateChanged(function(user) {
if (user) {
unsubscribe();
resolve(user);
}
else {
console.log('User unidentified');
}
});
});
}
// Save user's menu selection into selection node of firebase
function saveSelection(items, user) {
var selectionRef = usersRef.child(user.uid).child('selection');
for (var i = 0, item; !!(item = items[i]); i++) {
var id = item.id;
if (item.selected) {
selectionRef.child(id).update(item);
}
else if (typeof(selectionRef.child(id)) !== 'undefined') {
selectionRef.child(id).remove();
}
}
}
// Get user's selection from firebase
function getSelection(user) {
var selectionRef = usersRef.child(user.uid).child('selection');
return $q(function(resolve) {
var selection = $firebaseArray(selectionRef);
resolve(selection);
});
}
Menu controller:
var menu = this;
menu.saveMenu = saveMenu;
// Save menu selection and create anonymous user on firebase
function saveMenu() {
var items = menu.items;
return userService.startSession()
.then(function(user) {
return userService.saveSelection(items, user);
});
}
Checkout controller:
// Get selected menu items from user data
function getCheckoutItems() {
return userService.checkAuthState()
.then(function(user) {
return userService.getSelection(user);
})
.then(function(selection) {
checkout.items = selection;
return checkout.items;
})
.catch(function(error) {
console.log(error);
});
}
I've looked through dozens of posts on SO before asking this. Here are a couple of the similar ones I've found:
Apparent race condition getting firebase.User from controller in Firebase 3.x
Handle asynchronous authentication in Firebase on page reload to get list that needs user's uid
I've also looked through the reference on GitHub to set it up:
https://github.com/firebase/angularfire/blob/master/docs/reference.md#firebaseauth
NB: I'm using the new version of Firebase with Angular 1.5.8:
"firebase": "^3.4.1",
"angularfire": "^2.0.2"

UPDATE
Got it. I had to add a resolve to my /checkout route to wait for authentication before loading elements on the page... Figured it out thanks to this answer by David East from the Firebase team.
resolve: {
// controller will not be loaded until $waitForSignIn resolves
"firebaseUser": function($firebaseAuthService) {
return $firebaseAuthService.$waitForSignIn();
}
}

Related

location on change view issue angularjs

Stuck in an issue where i have to check authentication when location changes:
Issue:
while on location change i request the back-end to check auth if response is not 401 then show the page otherwise redirect to login page, when response is 401 the page shows for a bit and then the redirection happens to login page , for me its a headache because the page shows for a bit and then location changes , someone help me please?
$rootScope.$on('$locationChangeStart', function (event, next, current) {
var publicPages = [''];
$rootScope.showPreloader = true;
AuthenticationService.Login(function (_data) {
if (_data.result) {
count =1;
localStorage.setItem('userInfo', angular.toJson(_data.result));
awtcDataService.employeeInfo = _data.result;
var roles = awtcDataService.employeeInfo
var route = localStorage.getItem('setRoute')
if (route == '/check') {
$state.go('help');
}
else {
$state.go('home');
}
}
if (_data.status === 401) {
$state.go('login', {reload: true})//shows the page e.g home and then redirects to this page
count =1;
event.preventDefault();
return;
$timeout(function(){
console.log("show after directive partial loaded")
});
}
else {
}
}, function (error) {
if (error.status === 401) {
$state.go('login', {reload: true})/shows the page sometimes e.g home and then redirects to this page
count =1;
event.preventDefault();
return;
$timeout(function(){
console.log("show after directive partial loaded")
});
}
});
});
All state events, (i.e. $stateChange* and friends) are now deprecated and disabled by default. Instead of listening for events, use the Transition Hook API.
https://ui-router.github.io/guide/ng1/migrate-to-1_0#state-change-events

Using $scope in AngularJS to change value at index.html in routing

I'm making a single page app in AngularJS using Firebase and using the side nav bar on index.html.
And after login, I want the username should be on the side nav bar but the $scope is working for the current page on the controller is working.
scotchApp.controller('mainController', function($scope) {
$scope.validateLogin = function()
{
var email = $scope.login.userName + "#xyz.co";
var password = $scope.login.password;
firebase.auth().signInWithEmailAndPassword(email, password).catch(function(error) {
$scope.message = "please enter the correct details";
});
firebase.auth().onAuthStateChanged(function(user)
{
if(user)
{
$scope.usermobile = $scope.login.userName;
window.location.assign("/#/equipment");
}
});
}
$scope.message = '';
});
The current user is available in firebase.auth().currentUser as described in the docs so in your mainController you don't need to listen for auth changes.
In your code you are attaching a new listener each time the user tries to login, but what you actually want is to respond to one successful login, so just add a then to your sign in call:
firebase.auth().signInWithEmailAndPassword(email, password)
.then(function (user) {
console.log('the logged in user: ', user);
// Go to the other route here.
// It is recommended to use the router instead of "manually" changing `window.location`
})
.catch(function(error) {
$scope.message = "please enter the correct details";
});

How can I get protractor to wait for a login before proceeding?

I have a non-angular entry page to my app and I'm trying to first login:
describe('Authentication', function() {
it('should authenticate a user', function() {
browser.driver.get('https://example.com')
browser.driver.findElement(by.id('username')).sendKeys("user");
browser.driver.findElement(by.id('password')).sendKeys("mypass");
browser.driver.findElement(by.tagName('input')).click()
var url = browser.getLocationAbsUrl()
browser.driver.sleep(1)
browser.waitForAngular()
return
})
})
However, this gives an error:
Failed: Error while waiting for Protractor to sync with the page: "window.angular is undefined. This could be either because this is a non-angular page or bec
ause your test involves client-side navigation, which can interfere with Protractor's bootstrapping. See http://git.io/v4gXM for details"
What can I do to resolve this?
I wrote some helpers in the past to get this work in my e2e-tests:
waitForUrlToChangeTo: function (urlToMatch) {
var currentUrl;
return browser.getCurrentUrl().then(function storeCurrentUrl(url) {
currentUrl = url;
})
.then(function waitForUrlToChangeTo() {
browser.ignoreSynchronization = true;
return browser.wait(function waitForUrlToChangeTo() {
return browser.getCurrentUrl().then(function compareCurrentUrl(url) {
browser.ignoreSynchronization = false;
return url.indexOf(urlToMatch) !== -1;
});
});
}
);
},
login : function (username, password, url) {
browser.get('#/login');
element(by.model('username')).sendKeys(username);
element(by.model('password')).sendKeys(password);
element(by.buttonText('LOGIN')).click();
return this.waitForUrlToChangeTo(url);
}
And then in tests:
describe('when I login with valid credentials', function() {
it('should redirect to dashboard', function() {
helper.login('user', 'pass', '#/dashboard').then(function() {
expect(browser.getTitle()).toMatch('Dashboard');
});
});
});
I would say wait for logged in page until it displays properly and than do action. For e.g,
Target some element in logged-in page and wait for it.
Wait for url change, etc.
login -> browser.sleep(500)/wait for logged in page's element/URL change ->
other action
browser.driver.wait(function(){
expectedElement.isDisplayed().then(function (isVisible){
return isVisible === true;
},50000, 'Element not present ');
},50000);
if that element is not present within specified time, timeout error
would display & you would know unitl that time it's not logged in.

Handling secure login page in protractor

My team is working to use AngularJs and Polymer components for a new web app. I am looking into how to create a UI automation suite for this. After lots of research looks like Protractor may help me out here with some tweaks to handle Polymer. But, the current challenge is as follows -
I navigate to the app
As part of our company policy, the every web visit is validated (unless within same session). Here is how the validation works -
A login page (non-Anugular) page appears after one types the required url. Sign in with the credentials
Another intermediate page appears where it asks to wait for page to load or click a link to go to next page. Click the link
Url changes back to the original used in #1
Note: These validation pages take hell lot of time to load (changes to different internal urls). Also, the validation is skipped sometimes (within same session or through some other logic)
I have been struggling to design a prototype to handle all these. I am also trying to use Page Object while designing the prototype. Here is what I have so far.
login.js
________________________________________________________
var currentUrl;
var lastChangedUrl;
var secureUrl = 'corplogin.ssogen2.corporate.company.com';
var getwayUrl = 'gateway.zscalertwo.net';
var loginSuite = function(driver) {
var defer = protractor.promise.defer();
describe('Handle login', function() {
/*afterEach(function() {
//driver.manage().deleteAllCookies();
})*/
//it('Login to security test', function(){
//********** Wait for page to load/URL to change to secure login page ************
driver.getCurrentUrl().then(function(url) {
currentUrl = url;
}).then(function() {
driver.wait(function() {
return driver.getCurrentUrl().then(function (url) {
lastChangedUrl = url;
return url !== currentUrl;
});
});
}).then(function() {
//********** login to secure page ************
if (lastChangedUrl.indexOf(secureUrl) > -1 || lastChangedUrl.indexOf(getwayUrl) > -1) {
var element = driver.findElement(By.name("username"));
element.sendKeys("Username");
element = driver.findElement(By.name("password"));
element.sendKeys("password"); //Give password
element = driver.findElement(By.name("submitFrm"));
element.click();
}
}).then (function() {
//********** page is slow. wait for page to load/URL to change ************
driver.getCurrentUrl().then(function(url) {
currentUrl = url;
}).then(function() {
driver.wait(function() {
return driver.getCurrentUrl().then(function (url) {
lastChangedUrl = url;
return url !== currentUrl;
});
});
}).then (function() {
//********** Click on the link to to go to test page ***********
if (lastChangedUrl.indexOf(getwayUrl) > -1) {
var element = driver.findElement(By.tagName("a"));
console.log("before click............");
element.click();
}
//********** page is slow. wait for page to load/URL to change ************
driver.getCurrentUrl().then(function(url) {
currentUrl = url;
}).then(function() {
driver.wait(function() {
return driver.getCurrentUrl().then(function (url) {
lastChangedUrl = url;
return url !== currentUrl;
});
});
})
.then (function() {
//return defer.promise;
//browser.pause();
});
}, 60000);
});
//});
}, 60000);
return defer.promise;
};
module.exports = loginSuite;
spec.js
___________________________________________________________________________
describe('Protractor Demo App', function() {
var myUrl = 'http://<my test app url>/';
var driver = browser.driver;
beforeEach(function() {
driver.get(myUrl);
});
it('should login', function() {
loginSuite(driver)
.then(
function(){
console.log("End of tests:");
expect(driver.getCurrentUrl()).toBe(myUrl);
});
});
The issue here -
My expectation here is to have the promise returns to spec.js after the secure login page is handled so that I can continue with other testing using the driver object. For the sake testing I am logging 'End of tests' message and doing a dummy validation. But, looks like those two lines don't get executed.
Login to the secure site works and I see page changes to original test page. I tested that with Browser.pause(). But, the logging 'End of test' never happens, nor the validation.
I need to handle the scenario where the secure login page doesn't appear. Not sure what adjustment I need to do in login.js page
Is my approach for page object and handling the promises wrong here? I am able to go to one step further on the test app page when all the code are placed under one js file instead of splitting them for page object. Please help here.
I wanted to share with you the "polymer way" of solving your problem.
The code below use two elements to monitor the URL, the auth flow, the previous page visited and log the user in/out of the app
The first will bind to the origin route, so you can send the user back there
<app-route
route="{{route}}"
pattern="/origin/:page"
data="{{data}}"
tail="{{subroute}}">
</app-route>
The second will bind to the authPage, allowing you to show/hide the auth page.
<app-route
route="{{subroute}}"
pattern=":authPage"
data="{{data}}
active="{{authPageActive}}">
</app-route>
User auth, monitoring and page redirecting
Use the element: <firebase-auth>
Is the user singned in?: signedIn="{{isSignedIn}}"
<firebase-auth id="auth" user="{{user}}" provider="google" on-
error="handleError" signedIn="{{isSignedIn}}"></firebase-auth>
Add an observer
observers: [
'_userSignedInStatus(isSignedIn)' // observe the user in/out
],
Add a Function
_userSignedInStatus: function (isSignedIn) {
if (isSignedIn === false) {
this.page = 'view404'; // redirect the user to another page
// import an element that cover the view
} else {
//send a log message to your database
}
}

How to prevent new anonymous authentication UID after browser refresh in Firebase

I am using anonymous authentication in Firebase with Angular. The goal is to have one UID associated with a user until the browser is closed. I would like to use the same UID even if the page is refreshed. However, when I use the code below, a new UID and token is created every time a user refreshes the page. How do I prevent this from happening?
myApp.factory('fbAuth', function($firebaseAuth) {
var ref = new Firebase('https://xxxxxxxx.firebaseio.com');
ref.authAnonymously(function (error, authData) {
if (error) {
console.log('Login Failed!', error);
} else {
console.log('Authenticated successfully with payload:', authData);
}
},
{remember: 'sessionOnly'
});
});
myApp.controller('ProjectListCtrl', function(Projects, fbAuth) {
var projectList = this;
projectList.projects = Projects;
});

Resources