How to specify events priority in AngularJS Controller? - angularjs

I have a Spring+AngularJS RestFul application with user identification capabilites.
As soon as the user is logged in I store this data in the following manner:
$window.sessionStorage.user
Because I want to have this sessionStorage in other tabs I have implemented in my main controller the solution proposed here: https://stackoverflow.com/a/32766809/1281500
Also in this controller I listen to $stateChangeStart events to check wether the user is logged in or not. If the user is not logged in, I redirect him to the corresponding login page.
My problem comes when I open a new tab in the explorer. As you'll see in the code below, I get the $window.sessionStorage.user variable from the old tab to the new tab like this:
else if (event.key == 'sessionStorage' && !$window.sessionStorage.length) {
// another tab sent data <- get it
var data = JSON.parse(event.newValue);
for (var key in data) {
$window.sessionStorage.setItem(key, data[key]);
}
But the code of the $stateChangeStart is executed BEFORE the snippet above so the $window.sessionStorage.user is not available yet and the user is always redirected to the login page even when he's already logged in (at least in the original tab)
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
// if the user tries to access a restricted page an is not logged in, he is redirected to the login view
var restrictedPage = $.inArray(toState.name, ['login', 'sign-up']) === -1;
// "$window.sessionStorage.user" IS ALWAYS "UNDEFINED" just after a new tab is opened
if (restrictedPage && $window.sessionStorage.user === undefined) {
// Go to login page
}
});
The full controller code is below. How can I have user credentials available in the $stateChangeStart block?
(function() {
'use strict';
angular
.module('app.core')
.controller('CoreController', CoreController);
CoreController.$inject = ['$q', '$scope', '$rootScope', '$state', '$location', '$cookies', 'LoginService', '$window'];
function CoreController($q, $scope, $rootScope, $state, $location, $cookies, LoginService, $window) {
var vm = this;
var sessionStorage_transfer = function(event) {
if(!event) { event = $window.event; } // ie suq
if(!event.newValue) return; // do nothing if no value to work with
if (event.key == 'getSessionStorage') {
// another tab asked for the sessionStorage -> send it
$window.localStorage.setItem('sessionStorage', JSON.stringify(sessionStorage));
// the other tab should now have it, so we're done with it.
$window.localStorage.removeItem('sessionStorage'); // <- could do short timeout as well.
} else if (event.key == 'sessionStorage' && !$window.sessionStorage.length) {
// another tab sent data <- get it
var data = JSON.parse(event.newValue);
for (var key in data) {
$window.sessionStorage.setItem(key, data[key]);
}
}
};
// listen for changes to localStorage
if($window.addEventListener) {
$window.addEventListener("storage", sessionStorage_transfer);
} else {
$window.attachEvent("onstorage", sessionStorage_transfer);
};
// ask other tabs for session storage (this is ONLY to trigger event)
if (!$window.sessionStorage.length) {
$window.localStorage.setItem('getSessionStorage', 'user');
$window.localStorage.removeItem('getSessionStorage', 'user');
};
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
// if the user tries to access a restricted page an is not logged in, he is redirected to the login view
var restrictedPage = $.inArray(toState.name, ['login', 'sign-up']) === -1;
if (restrictedPage && $window.sessionStorage.user === undefined) {
// Go to login page
}
});
}
})();
UPDATE
My application is stateless so it works in the following way:
The first time the user access to the application I check the username and password are correct (call to Spring Rest service) and at that point I generate a token for the user. This token and user data are both stored in the sessionStorage like $window.sessionStorage.authToken and $window.sessionStorage.authToken.
I manage this from another AngularJS Controller (LoginController):
function login(valid) {
if(!valid) return;
LoginService.authenticate(vm.user)
.then(
function(user) {
var authToken = user.token;
$window.sessionStorage.authToken = authToken;
vm.authError = false;
if (vm.rememberMe) {
var now = new Date();
var expireDate = new Date(now.getFullYear(), now.getMonth() + 1, now.getDate());
$cookies.put(AUTH_TOKEN_COOKIE, authToken, {'expires': expireDate});
}
LoginService.getUser()
.then(
function(user) {
$window.sessionStorage.user = user
$state.go('installations');
}
);
},
function(errAuthentication) {
vm.authError = true;
$window.sessionStorage.user = null;
}
);
}
From now on I send this token with every request to the RESTful application and I have a filter in Spring that checks the token with the user credentials is correct. In case this token doesn't exist or is not valid anymore, Spring Security throws the corresponding Exception and I catch this Exception to redirect to the login page (I don't think this code is important for the current question but I can post it if necessary).
So basically my user only lives in the current sessionStorage object, I don't have a service to check if the user is logged in or not, I just check the variables I stored during the first log in process.
I hope this helps to clarify the process I little bit more.
Thank you very much.

It's really difficult to guarantee that your controller will run before other modules.
If you want to execute this check before everything else you need to do that on a run module.
angular
.module('myApp')
.run(runBlock)
.run(['mySessionChecker', '$rootScope', function (mySessionChecker, $rootScope ) {
$rootScope.$on("$locationChangeStart", function () {
//Service that checks if the user is logged or not
mySessionChecker.checkSession().then( function (auth) {
//Auth tells you if the user is logged in or not
console.log('User is '+auth);
doSomething();
})
});
}]);
function runBlock($log) {
$log.log('Angular's running');
}
Run Module will bootstrap immediately after App Module.
Then, for instance, you can emit an event with $rootScope or using Postal.js and do something with that information.
I would create a Parent Controller which intercepts that event and I would extend the controllers where you need to do the check to understand if the user is logged or not.
Take a look at this Q/A.
Hope I've been helpful.

Related

AngularJS: Is it possible to access service value in app.run?

My problem: I don't want users with a particular role typing in a valid URL for another page in the site and navigating there. I created a service to track the role. The role is set in the UsersController on login and console logs confirm it. Console logging shows the role is set properly everywhere, but I can never get access to it in app.run or app.config. I tried rootScope variables, event emitters, broadcasts, etc. But every time I type in a url the logging statements in app.run show the role is undefined. Help?
PS - Sorry the code's a little messy. I've been experimenting.
var app = angular.module('MainApp', ['ngRoute', 'ngMaterial']);
app.run(function ($rootScope, $location, $templateCache, roleAuthorization) {
$rootScope.$on('$viewContentLoaded', function () {
$templateCache.removeAll();
});
$rootScope.$on('handleEmit', function (event, args) {
console.log("handling emit");
$rootScope.role = args.role;
$rootScope.$broadcast('handleBroadcast', {role: args.role});
roleAuthorization.setAuthRole(args.role);
});
$rootScope.$watch(function() {
return $location.path();
},
function(a){
console.log("Here we go: " + $rootScope.userRoleValue);
if(a !== '/pharmacy/' && a !== '/users/login/' && a !== '/' && roleAuthorization.getAuthRole() === 'pharmacy'){
window.location.href = '/pharmacy/';
}
});
});
app.service('roleAuthorization', function ($rootScope) {
$rootScope.userRoleValue = '';
this.getAuthRole = function () {
return $rootScope.userRoleValue;
};
this.setAuthRole = function (x) {
console.log("auth role set to " + x);
$rootScope.userRoleValue = x;
console.log('rootscope var is ' + $rootScope.userRoleValue);
};
});
Thanks to #JB Nizet for the inspiration. On page reload we always get the current user, so I just needed to set it there as well for it to take and be available. Simple fix, in hindsight!
His comment: When the user types a URL in the location bar and hits Enter, the page reloads and the whole application restarts from scratch. So anything you've saved in memory at login time is gone, forever.

AngularJS Login Module

I am new to angularjs and started implementing login/logout for my assignments.
What i am doing now is-
$rootScope.$on('$stateChangeStart', function (event, toState, toParams,fromState) {
var token = SessionService.get('token')
LoginService.isLoggedIn({token:token}).$promise.then(function(response){
if(!response.error){
var isLoggedInOnServer = response.object
var requireLogin = toState.data.requireLogin
if(!isLoggedInOnServer && requireLogin){
console.log("1....")
event.preventDefault()
$state.go('user.signin');
}
console.log(toState.data.title,['signin','signup','forget'].indexOf(toState.data.title),isLoggedInOnServer,requireLogin)
if(isLoggedInOnServer && !requireLogin && ['signin','signup','forget'].indexOf(toState.data.title) > -1){
console.log("2....")
$state.go('app.dashboard')
}
}else{
console.log("3....")
event.preventDefault();
$state.go('user.signin')
}
})
The problem with this is when i hit a secure page, it first got there and show that page in flicker then return to login page if i am not login.
If i am not login this should immediately redirect me to login page instead of that secure page.
Like Spring-security in spring, can any body tell me robust login mechanism or some logic that i can implement?
I'm making a login with redirection too.. this is working fine to me.
when i login, the token is stored on a cookie and a variable to render my menubar.
if my cookie is empty and my render too this redirect to the index.html (my login)
var miApp = angular.module('Natura', ['ngRoute', 'ngTable', 'ngCookies'])
.run(function ($rootScope, $location, $cookies, $window, loginService) {
$rootScope.render = $cookies.get('render');
$rootScope.$on('$routeChangeStart', function () {
if ($window.location.pathname !== "/NaturaWEB/index.html") {
if ($rootScope.render !== "true") {
$window.location.href = 'index.html#/';
}
}
});
});
with $window.location.pathname i can control what url is being trying to access.
sorry if this is a lot crappy.

Angularjs authentication redirect issue

I am having an app that every page except login requires authentication. I do this by checking the $stateChangeStart and then redirecting to /login when token is not set. on initial app load this works fine, but at login screen if i type another "restricted" url. It does the check, changes the url, but still loads the restricted page. What am I missing here?
//app.run:
app.lazy = $couchPotato;
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
$rootScope.$on('$stateChangeStart', function(event, toState, toStateParams) {
console.log("state changed to "+toState.name);
console.log(toState);
Authentication.validateLogin(toState, toStateParams);
});
Authentication.validateLogin:
validateLogin:function(toState, toStateParams){
$rootScope.toState = toState;
$rootScope.toStateParams = toStateParams;
if ($localStorage.token == null) {
console.error("Not Authenticated!");
$location.url('/login');
}
}
I see that your using angular-ui, so I'm not exactly sure what advantages that has over using 'basic' angular, but I wrote this to handle validating a token when the route changes.
app.run(['$rootScope', 'authService',
function ($rootScope, authService) {
$rootScope.$on("$routeChangeSuccess", function (event, next, current) {
if (next && next.$$route && authService.isAuthenticated()) {
authService.validate();
$rootScope.appTitle = next.$$route.title;
}
});
}]);
The $routeChangeSuccess handles navigation to routes after the controller has loaded (very important for objects that load when the page loads when validation is confirmed), and then validates them.
This also performs 2 checks in that it checks if the token in local storage exists and is formed correctly, and if it is, send it back to the server to confirm that.
I also found that I had to use the $locationChangeStart to handle page refresh, to re-validate when someone tries to refresh the page.
The validateLogin solution:
validateLogin:function(toState, toStateParams){
if(toState.access){
if(toState.access.loginRequired){
if ($localStorage.token == null) {
$state.go('login');
}
}
}
Try this with the validateLogin:
validateLogin:function(toState, toStateParams){
if(toState.access){
if(toState.access.loginRequired){
if ($localStorage.token == null) {
$state.go('login');
}
}
}
}

AngularJS: Check user role to decide if user have permission to view pages or not

I have a problem with my authentication mechanism. I have to call an API to get current user roles, then check it to decide that user can view which pages of my app. My idea: call API in myApp.run block and then check:
angular.module('MyApp')
.run(['$rootScope', 'authenService', '$location', function($rootScope, authenService, $location) {
var currentUser;
function checkRole() {
if(angular.isDefined(currentUser) && angular.isObject(currentUser) && ... && currentUser.isAdmin && $location.url() === '/dashboard') {
return true;
}
return false;
}
/*
** #name: getCurrentUser
** #description: get current user info, use promise $q
*/
authenService.getCurrentUser()
.then(function getSuccess(currentUserInfo){
currentUser = currentUserInfo;
//if user is not admin and current page is not dashboard page, redirect to dashboard page
if(!checkRole()) {
$location.path('/dashboard');
}
}, function getError(){});
//when user go to new path, check role if user have permission to access new page or not
$rootScope.$on('$routeChangeStart', function onRouteChange() {
if(!checkRole()) {
$location.path('/dashboard');
}
})
}];
My problem: while getCurrentUser API is in progress (still not receive response from server), the code $rootScope.$on('$routeChangeStart') is executed and always redirects user to dashboard page.
What should I do? Do you have any idea/solution to resolve this problem? or How can I wait for response from server and then run the UserCtrl controller(when user go to /user, the UserCtrl will execute without checkRole because response hasn't been received).
EDIT
The things I really want to do are when user goes to mySite.com, I will call an API to get user roles. The request is still in progress, but somehow user goes to mySite.com/#/user, the User page shows in web immediately(The requirement is user can't see User page till the request finishes). After the request finished, user will be redirected to mySite.com/#/dashboard if user doesn't have permission.
SOLUTION
I show loading screen to block my app till the request finished. Thank you for your help.
try using $q service:
angular.module('MyApp')
.run(['$rootScope', 'authenService', '$location', '$q', function($rootScope, authenService, $location, $q) {
var currentUser;
function checkRole() {
var defered = $q.defer();
if(angular.isDefined(currentUser) && angular.isObject(currentUser)){
defered.resolve(currentUser.isAdmin && ($location.url() === '/dashboard'))
}else{
authenService.getCurrentUser().then(function getSuccess(currentUserInfo){
currentUser = currentUserInfo;
defered.resolve(currentUser.isAdmin && ($location.url() === '/dashboard'))
});
}
return defered.promise;
}
$rootScope.$on('$routeChangeStart', function onRouteChange() {
checkRole().then(function(isAdmin){
if(isAdmin) $location.path('/dashboard');
})
})
}];

return to origin target after routing to loginpage using $routeChangeStart

i'm implementing an login-form to my single page angularjs application.
An $routeChangeStart listener watches for route changes. If the user start the application or moves from one route to an other, the application will check, if the user is logged in and -if not- the user will be routed to the login-page.
app.run(['$rootScope', '$location', 'Auth', function ($rootScope, $location, Auth) {
$rootScope.$on('$routeChangeStart', function (event, next) {
if (!Auth.isLoggedIn()) {
console.log('DENY, go to login-page');
event.preventDefault();
$location.path('/login');
}
else {
console.log('ALLOW');
$location.path('/main'); //main is not correct in every case
}
});
}]);
The Auth factory handles the information, if the user is logged in.
If the user is logged in, the new path is main -> $location.path('/main');.
But: I don't need an fixed target after login. What I need is an dynamic target depending on the origin target of the user.
Example: new user is on /main and clicks to /details -> $routeChangeStart is fired and the user is routed to /login. After an valid login, the user should be back to /details, because this was the target he likes to go.
The problem is: insight the login page/controller, I have no information about the real target of the user.
I know, bevore routing to /login, the next parameter has the Information about the real target of the user and all parameters, which needed to render the target-page (like /details, what is in real: /details/:ID/....).
Should I pass those informations (next.originalPath, next.params) as routing parameter to /login in order to redirect from the loginpage after login is successful? Or should I store those informations insight the Auth service (don't think, that this is the right place), so there are available everywhere?
What is the best way, to return the user to his origin route target after login?
use otherwise method. Put there $injector, and put ther $state
$urlRouterProvider.otherwise ($injector) ->
$state = $injector.get('$state')
write an interceptor, witch recognize what reqest has become, and redirect user. injector should hold data that are need
this.loginAction= (redirect) ->
$location = $injector.get('$location')
$location.path('login')
this.loggedAction= (redirect) ->
$state = $injector.get('$state')
$state.go();
in my current project, we save the real target to an angular service. and after successful login we check if that service contains a redirect url. if it exists redirect to URL otherwise to home page.
here is sample code, note: we are using ui-router
$rootScope.$on('$stateChangeStart', function (event, toState , toParams/*, fromState, fromParams*/) {
// if route requires auth and user is not logged in
//toState['requiresAuth'] -> contains authorization level
if (typeof toState['requiresAuth'] === 'string' && !authService.isUserAuthenticated(toState['requiresAuth'])) {
navService.saveRedirectPage();
navService.goToLoginPage();
return;
}
currentPage = toState;
});
Navigation Service
navService.saveRedirectPage = function () {
var nextRouteURL = $location.path();
storService.setParameter('redirectURL', nextRouteURL);
};
//goes to redirect page if possible
//otherwise goes to home page
navService.goToRedirectPage = function () {
if (storService.hasParameter('redirectURL')) {
var redirectURL = storService.getParameterOnce('redirectURL');
$location.path(redirectURL);
return;
}
navService.goToHomePage();
};
storService is a simple service that keeps named parameters given to it
here it is
(function () {
angular.module('doktorin.services').factory('StorageService', ['$q', function ($q) {
var storageAPI = {};
var storageData = {};
var storageWaiters = {};
var notifyWaiters = function (name, data) {
if (storageWaiters[name] instanceof Array) {
for (var i = 0; i < storageWaiters[name].length; i++) {
storageWaiters[name][i].resolve(data);
}
//reset list
storageWaiters[name] = null;
}
};
///returns and deletes parameter for single usage
storageAPI.getParameterOnce = function (name) {
var data = storageAPI.getParameter(name);
storageAPI.deleteParameter(name);
return data;
};
storageAPI.deleteParameter = function (name) {
delete storageData[name];
};
storageAPI.setParameter = function (name, data) {
storageData[name] = data;
notifyWaiters(name, data);
};
storageAPI.getParameter = function (name) {
if (!storageAPI.hasParameter(name)) {
throw 'Invalid parameter get at StorageService with Name: ' + name;
}
return storageData[name];
};
storageAPI.hasParameter = function (name) {
return typeof storageData[name] !== 'undefined';
};
storageAPI.waitParameter = function (name) {
var deferred = $q.defer();
if (storageAPI.hasParameter(name)) {
deferred.resolve(storageAPI.getParameter(name));
} else {
if ( !(storageWaiters[name] instanceof Array)) {
storageWaiters[name] = [];
}
storageWaiters[name].push(deferred);
}
return deferred.promise;
};
return storageAPI;
}]);
})();
and here is successful login event
authService.login(me.logCredentials).then(function () {
navService.goToRedirectPage();
},
function (err) {
//handle error here
});

Resources