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.
Related
I have an Umbraco project with an Area section configured with Angular.
I use the Plugins to integrate the Area with the use of package.manifest like this:
Into edit.controller.js, I have this script:
'use strict';
angular.module("umbraco")
.controller('Administration.AdministrationTree.EditController', function administrationEditController($scope, $routeParams, $http) {
//set a property on the scope equal to the current route id
$scope.id = $routeParams.id;
$scope.url = "";
$scope.canShow = false;
$scope.showIframe = function () {
if ($scope.url === "") {
return false;
}
return true;
};
$scope.canShow = false;
if (!$scope.id) {
return;
}
$http.get('/umbraco/backoffice/administration/CustomSection/GetUrl/?node=' + $scope.id)
.success(function (data) {
$scope.url = JSON.parse(data);
$scope.canShow = $scope.url;
});
});
When I run the project and click on any node in this area, I receive most of the time a 404 error like if the page was not exist. I say "most of the time" because 1 out of 10, it works and the page is displayed.
However, if I put a breakpoint in the javascript function below and I click on any node and resume the javascript after the breakpoint was hitting, the node related html page is displayed correctly.
Anybody know why when I put a breakpoint, Umbraco or Angular are able to resolve 100% of the time the page but not when I don't have any breakpoint in this function?
Thanks
I really hate to answer my own questions but after 2 weeks without answers and a lot of reflections on my side, I finally found a solution to this problem.
What was causing the problem of out synching between Umbraco and Angular was due to the $http.get query which is asynchronous with Angular (no other choice I think) and after a response from the server to get a valid URL, the $scope object was not able to talk to Umbraco to redirect to the valid URL.
On my asp.net MVC controller, the GetUrl function was trying to get a valid URL doing a query to the database where I keep a structure of nodes which correspond to a tree displayed to the user. This is a slow process and the time required to respond to the HTTP get request was too long the vast majority of the time.
Here is my solution to this problem:
'use strict';
var myCompany = myCompany || {};
myCompany.myProject = myCompany.myProject || {};
myCompany.myProject.controller = (function (parent){
parent.urls = {};
function loadUrls () {
$.get('/umbraco/backoffice/administration/CustomSection/GetUrls')
.success(function (data) {
parent.urls = data;
});
};
loadUrls();
return parent;
})({});
angular.module("umbraco")
.controller('Administration.AdministrationTree.EditController', function administrationEditController($scope, $routeParams, $http) {
//set a property on the scope equal to the current route id
$scope.id = $routeParams.id;
$scope.url = "";
$scope.canShow = false;
$scope.showIframe = function () {
if ($scope.url === "") {
return false;
}
return true;
};
$scope.canShow = false;
if (!$scope.id) {
return;
}
var url = myCompany.myProject.controller.urls.find(function (element) {
return element.Key == $scope.id;
});
if (url) $scope.url = url.Value;
$scope.canShow = $scope.url;
});
Note in this case that I have an iffe function which query the server to build an array of all my URLs from the backoffice and then when Angular need a redirection, I search directly from the array.
The iffe function is calling only once when the user enters in the backoffice section which I think is nice because the structure behind rarely changes.
I'm not sure if it's a hack or the valid way to do the thing due to my lack of experience with Angular but it works like a charm.
I am developing an app that pushes user's geolocation to the server (with regards to permissions, of course).Currently, when the user navigates to the account page, a method is invoked to push their geolocation to the server.Instead, I would like a service (if that's the best method) to run while the app is running that will push the geolocation. This means that the location held for the user will always be up to date and not just updated when the account page is visited.
There is a checkbox on the account page where they select if they want to share their geolocation or not.I have a $watcher on the checkbox...
$scope.$watch("account.shareLocation", function(newValue, oldValue) {
if (newValue) {
locationService.pushLocation(userPosition);
} else {
locationService.pushLocation(null);
}
});
So, if the user selects to share their location (newValue === true) then, pushLocation() should be constantly invoked with the userPosition until the value of account.shareLocation (the checkbox) is altered otherwise.
I made a small example here: https://jsfiddle.net/ojzdxpt1/2/
You have your main controller that inits the service if the user has allowed it (I also added a mock $timeout of them unchecking it).
var app = angular.module('TestApp', []);
app.controller('TestController', function($scope, $timeout, LocationService) {
console.log('app init');
//-- on checkbox change and/or app init;
var trackLocation = true;
if (trackLocation) {
LocationService.start();
//-- imitate them turning the service off
$timeout(function() {
console.log('stop tracking location');
LocationService.stop();
},10000);
}
});
Now for your service you could do something like this:
app.service('LocationService', function($interval) {
var int;
return {
start: function() {
int = $interval(this.saveLocation,3000);
},
stop: function() {
$interval.cancel(int);
},
saveLocation: function() {
console.log('save location');
}
}
});
I am trying to fetch user info from user's database and access it from anywhere using rootscope. I get the user's email and uid as the user signs in. However,to get the user's info from database, I need to call my database and read the info one by one and store it as a variable in rootscope to access it from everywhere.
So, my question is below :
How can I use rootscope in this example?
Is there any better way to do this?
in the below example, the console log is showing the first name, but I don't know how to rootscope it?
Thanks for help.
app.run(['$rootScope', '$state', '$stateParams', '$cookies', "$location",
function ($rootScope, $state, $stateParams, $cookies, $location) {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
$rootScope.user.uid = user.uid;
$rootScope.user.email = user.email;
return firebase.database().ref('/users/' + user.uid).once('value').then(function(snapshot) {
var firstname = snapshot.val().firstname;
console.log("first name", firstname);
});
} else {
// No user is signed in.
}
});
}]);
If you use AngularFire Always use its wrapper methods vs native firebase methods.
Do not use
firebase.auth().onAuthStateChanged
firebase.database().ref('/users/' + user.uid).once('value')
Use
$firebaseAuth().$onAuthStateChanged
var userData = $firebaseObject(firebase.database().ref('/users/' + user.uid));
Checkout the following link for full list of available methods in angularFire.
https://github.com/firebase/angularfire/blob/master/docs/reference.md
I recomend modifying your code like this.
$firebaseAuth().$onAuthStateChanged(function(user) {
if (user) {
$rootScope.user = $firebaseObject(firebase.database().ref('/users/' + user.uid));
$rootScope.user.uid = user.uid;
$rootScope.user.email = user.email;
console.log("Here you should have complete user object");
// still if you want to do something when user is loaded
/* $rootScope.user.$loaded(function(loadedUser) {
var firstname = loadedUser.firstname;
console.log("first name", firstname);
});*/
} else {
// No user is signed in.
}
});
Of course you need to include $firebaseObject and $firebaseAuth using dependency injection.
There are two approaches to fulfil your needs
Approach 1:
you can do this $rootScope.authData=user; then access it from anywhere by injecting $rootScope. but problem in this is that when you will refresh page $rootScope will be empty Check this SO Question
Approach 2:
I will prefer this approach,you can use use $getAuth() function of angularfire, $getAuth is syncronous function which will gives you current user data.
var myUser=$scope.AuthObj.$getAuth();
for more detail check this
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.
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');
})
})
}];