Google Authentication with Protractor - angularjs

I have only been able to track down this link to solve my problem. I am trying to use protractor to run e2e testing. This is my first go at it, and I like it. However my project requires Google Authentication, then once authenticated, I compare it to my database to make sure the user is in the project. I cannot figure out from the stackoverflow post how to call to the Google Auth page object demee is talking about in the last answer. Also the first person says to find element by.id('Email') and by.id('Passwd') which may be a problem, because my google auth is coming up in another window. So I am not sure how to call to that with Protractor. Here is some of my code after I initialize the $window once gapi is loaded:
.controller('ContainerController', ['$scope', '$rootScope', '$state','$window', '$location','employeeFactory', 'employeeTestFactory', function ($scope, $rootScope, $state, $window,$location, employeeFactory, employeeTestFactory) {
$rootScope.callRequests=function(){};
$rootScope.callInfo=function(){};
if(typeof $rootScope.gapi !== "undefined")gapi.load('client:auth2', initClient);
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
if(typeof $rootScope.gapi === "undefined") return;
gapi.load('client:auth2', initClient);
})
$scope.$state = $state;
$window.initGapi = function() {
gapi.load('client:auth2', initClient);
$rootScope.gapi = gapi;
}
$rootScope.calculateUsed = function(val){
$rootScope.employee.timePending = $rootScope.employee.timePending = 0;
var newTimeUsed = 0;
angular.forEach(val, function(key, value){
var td = key.timeDuration;
if(key.timeState === "pending"){
$rootScope.employee.timePending += Number(td);
}else{
newTimeUsed += Number(td);
}
});
$rootScope.employee.totalTimeUsed = newTimeUsed;
}
$scope.employeeType = $rootScope.email = "";
function initClient() {
gapi.client.init({
apiKey: 'AIzaSyDaMf0eviuFygt1hzwQz03a2k2lrLDnpIc',
discoveryDocs: ["https://people.googleapis.com/$discovery/rest?version=v1"],
clientId: '977491754644-954b83j2evmq65v6kchq4dsd9j0ud4vg.apps.googleusercontent.com',
scope: 'profile'
}).then(function () { gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus); updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
$scope.employee = [];
});
}
function updateSigninStatus(isSignedIn) {
if (isSignedIn) {
getEmailAddress();
}else{
$state.go('app');
}
}
$scope.handleSignInClick = function(event) {
if(!gapi.auth2.getAuthInstance().isSignedIn.get()){
gapi.auth2.getAuthInstance().signIn();
}
}
$scope.handleSignOutClick = function(event) {
if(gapi.auth2.getAuthInstance().isSignedIn.get()){
gapi.auth2.getAuthInstance().signOut();
}
}
function getEmailAddress() {
gapi.client.people.people.get({
resourceName: 'people/me'
}).then(function(response) {
$rootScope.email = response.result.emailAddresses[0].value;
$rootScope.callRequests();
$rootScope.callInfo();
//Here is where I compare google to my db and route users back to main if not in db employeeTestFactory.get($rootScope.email).then(function(message) {
if(typeof message.employeeid === "undefined"){
$state.go('app');
}else if($location.path() === "/"){
$state.go('app.employee');
$rootScope.employee = message;
}else{
$rootScope.employee = message;
}
});
}, function(reason) {
console.log('Error: ' + reason.result.error.message);
});
}
}])
.controller('LoginController', ['$scope', '$state', '$window', '$http','$rootScope', '$timeout', 'GooglePlus', 'gapiService', function ($scope, $state, $window, $http, $rootScope, $timeout, GooglePlus, gapiService) {
$scope.$state = $state;
$scope.callme = function(){
$scope.handleSignInClick();
}
// if it could not be loaded, try the rest of
// the options. if it was, return it.
var url;
var windowThatWasOpened;
$http.get("url").then(function(response) {
url = response.data;
});
$scope.login = function() {
windowThatWasOpened = $window.open(url, "Please sign in with Google", "width=500px,height=700px");
}
window.onmessage = function(e) {
if(windowThatWasOpened) windowThatWasOpened.close();
var urlWithCode = e.data;
var idx = urlWithCode.lastIndexOf("code=");
if(idx === -1) return;
var code = urlWithCode.substring(idx + 5).replace("#","");
$http.get("token?code=" + code).then(function(response) {
var userurl = 'https://www.googleapis.com/plus/v1/people/me?access_token='+response.data.access_token;
$http.get(userurl).then(function(response) {
console.log("user info: "+JSON.stringify(response.data));
})
});
}
}])
And here is the code I am trying to navigate to google with:
describe('Protractor Demo App', function() {
it('should have a title', function() {
browser.get('http://localhost:9000/');
element(by.id('gLogin')).click().then(function(){
Google.loginToGoogle();
});
expect(browser.getTitle()).toEqual('TrinityIT Time Off Tracking');
browser.sleep(5000);
});
});
And here is my conf file:
exports.config = {
framework: 'jasmine',
specs: ['googlePage.js','spec.js'],
onPrepare: function () {
global.isAngularSite = function (flag) {
console.log('Switching to ' + (flag ? 'Asynchronous' : 'Synchronous') + ' mode.')
browser.ignoreSynchronization = !flag;
},
global.BROWSER_WAIT = 5000;
}
}

Assuming that what you're looking to do is test your app, rather than test the OAuth dialogue, then there is an easier approach.
First an OAuth refresher, the whole point of doing all the OAuth stuff is that you end up with an Access Token that you can include as an "Authorization: Bearer xxxxx" HTTP Header with your Google API (eg. Drive, YouTube, Calendar, etc) requests. Sooooooo, if you had an Access Token, you could bypass all the OAuth stuff and your app will be active and able to be tested.
So, what you want is an automated way to get an Access Token. That's pretty simple. Somewhere, either in your app code or in your Protractor preamble scripts, you need to ingest a Refresh Token, and use that to generate an Access Token which is available to your app.
I do this with a file refreshtoken.js which I carefully do NOT check in to git for security reasons. refreshtoken.js is
var refreshtoken="1x97e978a7a0977...";
var client_id="423432423#gfgfd";
If you look at the answer to How do I authorise an app (web or installed) without user intervention? (canonical ?), you will see the steps to get a Refresh Token, and at the bottom, some JavaScript to show how to use the Refresh Token to fetch an Access Token. It might look like a lot of steps, but you only ever do them once, so it's not too onerous.
This approach bypasses OAuth, so isn't the answer if it's specifically OAuth that you want to test. However it does allow you to test your app in a much more robust manner. It also has the benefit that it can be used for Karma unit testing as well as Protractor e2e testing.

Related

Is there a way to find the pendingReuests in new angular apps

I'm trying to automate an angular app using selenium. Before running the selenium script I would want to wait for the app to completely load. I used the following code to do this, but after the app was updated to a new angular version, I'm not able to get the pending requests using this method.
I tried searching for a solution for a few days, but couldn't find any. Thanks in advance :)
angular.element(document).injector().get('$http').pendingRequests.length.toString();
In the latest Angular, you can access Pending Requests from the built-in HTTP$ directly
Or if you want to wrap it in a service look ref from here/below
angular.module('app', [])
// This service keeps track of pending requests
.service('pendingRequests', function() {
var pending = [];
this.get = function() {
return pending;
};
this.add = function(request) {
pending.push(request);
};
this.remove = function(request) {
pending = _.filter(pending, function(p) {
return p.url !== request;
});
};
this.cancelAll = function() {
angular.forEach(pending, function(p) {
p.canceller.resolve();
});
pending.length = 0;
};
})
// This service wraps $http to make sure pending requests are tracked
.service('httpService', ['$http', '$q', 'pendingRequests', function($http, $q, pendingRequests) {
this.get = function(url) {
var canceller = $q.defer();
pendingRequests.add({
url: url,
canceller: canceller
});
//Request gets cancelled if the timeout-promise is resolved
var requestPromise = $http.get(url, { timeout: canceller.promise });
//Once a request has failed or succeeded, remove it from the pending list
requestPromise.finally(function() {
pendingRequests.remove(url);
});
return requestPromise;
}
}])

How to handle $rootscope and take user id of logged in user in AngularJS?

I want to find the ID of the logged in user and display it in a page. I am new to angular and I don't have much clue on how to handle a session..
I have an angular app which is connected to backend API (.net core).
I will show the instances where $rootScope is used in the website (login and authorization is already enabled). I need to get an understanding of this to learn the app.
In App.js :
//Run phase
myApp.run(function($rootScope, $state) {
$rootScope.$state = $state; //Get state info in view
//Should below code be using rootScope or localStorage.. Check which one is better and why.
if (window.sessionStorage["userInfo"]) {
$rootScope.userInfo = JSON.parse(window.sessionStorage["userInfo"]);
}
//Check session and redirect to specific page
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
if(toState && toState.data && toState.data.auth && !window.sessionStorage["userInfo"]){
event.preventDefault();
window.location.href = "#login";
}
if(!toState && !toState.data && !toState.data.auth && window.sessionStorage["userInfo"]){
event.preventDefault();
window.location.href = "#dashboard";
}
});
});
Users.js :
'use strict';
angular.module('users', []);
//Routers
myApp.config(function($stateProvider) {
//Login
$stateProvider.state('login', {
url: "/login",
templateUrl: 'partials/users/login.html',
controller: 'loginController'
});
//Factories
myApp.factory('userServices', ['$http', function($http) {
var factoryDefinitions = {
login: function (loginReq) {
$http.defaults.headers.common['Access-Control-Allow-Origin'] = '*';
return $http.post('http://localhost:1783/api/token?UserName='+loginReq.username+'&password='+loginReq.password).success(function (data) { return data; });
}
}
return factoryDefinitions;
}
]);
//Controllers
myApp.controller('loginController', ['$scope', 'userServices', '$location', '$rootScope', function($scope, userServices, $location, $rootScope) {
$scope.doLogin = function() {
if ($scope.loginForm.$valid) {
userServices.login($scope.login).then(function(result){
$scope.data = result;
if (!result.error) {
window.sessionStorage["userInfo"] = JSON.stringify(result.data);
$rootScope.userInfo = JSON.parse(window.sessionStorage["userInfo"]);
//$localStorage.currentUser = { username: login.username, token: result.data };
//$http.defaults.headers.common.Authorization = 'Token ' + response.token;
$location.path("/dashboard");
}
});
}
};
}]);
I came to know that the information about the user will be available in $rootScope.userInfo. If so, how can I take a value inside it?
Please explain with an example if possible. Thanks in advance.
One:
myApp.controller('loginController', [
'$scope', 'userServices', '$location',
'$rootScope',
function($scope, userServices, $location, $rootScope) {
Inside the controller, $rootScope was injected which makes you have access to the userInfo in that controller.
so if you inject $rootScope into another controller and console.log($rootScope.userInfo) you would see the users data.
myApp.controller('anotherController', ['$scope', '$rootScope', function
($scope, $rootScope){
console.log($rootScope.userInfo) //you'd see the users data from sessionStorage
});
According to this post on quora
$scope is an object that is accessible from current component
e.g Controller, Service only. $rootScope refers to an object
which is accessible from everywhere of the application.
You can think $rootScope as global variable and $scope as local variables.
$rootScope Defn.
In your case, once the user is logged in a key "userInfo" in sessionStorage is created and the same data is copied to $rootScope.userInfo. To check the fields in the userInfo after login try
console.log($rootScope.userInfo);
and print it in the console or open your session storage in your browser debugger tools [for chrome open developer tools>applications>sessionstorage>domainname] to view the values in the "userInfo" key.
Suppose you have
{
uid: "10",
fullname: "John Doe"
}
you can access uid in the script using $rootScope.userInfo.uid or $rootScope.userInfo['uid'].
Just in case you are unable to read the code, here is an explanation
if (window.sessionStorage["userInfo"]) {
$rootScope.userInfo = JSON.parse(window.sessionStorage["userInfo"]);
}
is checking the user is logged in or not.
the factory
myApp.factory('userServices', ['$http', function($http) {
var factoryDefinitions = {
login: function (loginReq) {
$http.defaults.headers.common['Access-Control-Allow-Origin'] = '*';
return $http.post('http://localhost:1783/api/token?UserName='+loginReq.username+'&password='+loginReq.password).success(function (data) { return data; });
}
}
is calling the server to get the userInfo object.
$scope.doLogin = function() {
if ($scope.loginForm.$valid) {
userServices.login($scope.login).then(function(result){
$scope.data = result;
if (!result.error) {
window.sessionStorage["userInfo"] = JSON.stringify(result.data);
$rootScope.userInfo = JSON.parse(window.sessionStorage["userInfo"]);
//$localStorage.currentUser = { username: login.username, token: result.data };
//$http.defaults.headers.common.Authorization = 'Token ' + response.token;
$location.path("/dashboard");
}
});
}
};
$scope.doLogin is calling the above factory and storing the userInfo object.

AngularFire: how to set $location.path in onAuth()?

I am building an angular+firebase app with user authentication (with angularfire 0.8).
I need to use onAuth() auth event handler, since I will provide multiple authentication paths, included social, and want to avoid code duplication. Inside onAuth callback, I need to reset location.path to '/'.
Usually everything works nicely, but, if app is loaded on an already authenticated session (<F5>, for example), on $scope.$apply() I get "Error: [$rootScope:inprog] $apply already in progress"
(if I don't use $scope.$apply(), location path is not applyed to scope, and no page change happens).
I suspect I make some stupid mistake, but can't identify it...
This is my workflow:
app.controller('AuthCtrl', function ($scope, $rootScope, User) {
var ref = new Firebase(MY_FIREBASE_URL);
$scope.init = function () {
$scope.users = [];
User.all.$bindTo($scope, 'users').then(function () {
console.info('$scope.users bound:', $scope.users);
});
};
$scope.login = function () {
ref.authWithPassword({
email: $scope.user.email,
password: $scope.user.password,
}, function(err) {
if (err) {
console.error('Error during authentication:', err);
}
});
};
ref.onAuth(function(authData) {
if (authData) {
console.info('Login success');
var $rootScope.currentUser = $scope.users[authData.uid];
$location.path('/');
$scope.$apply();
} else {
console.info('Logout success');
}
});
};
app.factory('User', function ($firebase) {
var ref = $firebase(new Firebase(MY_FIREBASE_URL + 'users'));
return {
all: ref.$asObject()
};
});
Just as a reference, I want to post the solution I found, and I'm currently adopting:
$scope.init = function () {
$scope.params = $routeParams;
$scope.debug = CFG.DEBUG;
$scope.lastBuildDate = lastBuildDate;
$scope.error = null;
$scope.info = null;
$scope.users = [];
User.all.$bindTo($scope, 'users').then(function () {
// watch authentication events
refAuth.onAuth(function(authData) {
$scope.auth(authData);
});
});
...
};
...
I.e., it was enough to move the watch on authentication events inside the callback to bindTo on users object from Firebase.

How to get the authenticated user info and use in all controllers and services?

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;
});
});

angularjs with firebase auth share service

Halo all,
I want to use angularjs with firebase simple login (facebook). But I have no idea how to
create the auth share service.
What I want to do is
create a authentication service
use this auth service to check if user loggedin in every controllers
controllers will do the $location if user loggedin/not-login
I also new to angularjs but I don't know which services should I use in this situation.
service or factory?
How can I put the below code in angular service then tell each controller if user logged in or not?
var firebaseRef = new Firebase("https://test.firebaseio.com");
var auth = new FirebaseAuthClient(firebaseRef, function(error, user) {
if (user) {
console.log(user);
} else if (error) {
console.log(error);
} else {
console.log('user not login');
}
});
Here is what i'm guessing, return user value from authService so in controllers if authService.user exists then redirect to loggedin page otherwise show login dialog with a
login button to call the following code
authService.login('facebook');
Let me know if I can do like this, or there is a better way?
Here is what I'm using so far...
I haven't implemented the redirecting yet but the rest works.
p4pApp.factory('firebaseAuth', function($rootScope) {
var auth = {},
FBref = new Firebase(p4pApp.FIREBASEPATH);
auth.broadcastAuthEvent = function() {
$rootScope.$broadcast('authEvent');
};
auth.client = new FirebaseAuthClient(FBref, function(error, user) {
if (error) {
} else if (user) {
auth.user = user;
auth.broadcastAuthEvent();
} else {
auth.user = null;
auth.broadcastAuthEvent();
}
});
auth.login = function() {
this.client.login('facebook');
};
auth.logout = function() {
this.client.logout();
};
return auth;
});
The AuthCtrl is common to all/most of my pages.
var AuthCtrl = function($scope, firebaseAuth) {
$scope.login = function() {
firebaseAuth.login();
};
$scope.logout = function() {
firebaseAuth.logout();
};
$scope.isLoggedIn = function() {
return !!$scope.user;
};
// src: Alex Vanston (https://coderwall.com/p/ngisma)
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
$scope.$on('authEvent', function() {
$scope.safeApply(function() {
$scope.user = firebaseAuth.user;
});
});
};
Regarding factory vs. service, there was a good blog post explaining how to approach the choice that I recommend reading: http://iffycan.blogspot.com/2013/05/angular-service-or-factory.html
In terms of doing authentication, your general approach of assigning signed in state to a variable in a service seems alright. We're thinking about doing a deeper integration with Angular for authentication, but for now this seems like a reasonable approach.

Resources