.state('newProduct', {
url: '/products/new',
templateUrl: 'app/products/templates/product-new.html',
controller: 'ProductNewCtrl',
authenticate: 'cook,admin'
})
I'm trying to add different client routes based on role authentifications but if I try to add another role such as cook for example it won't trigger the page defined by the url for both of them. It will work separately tho if that makes more sense
authenticate: 'cook',
authenticate: 'admin'
is this a syntax error?
In your .state block, 'authenticate' is simply a data holder
You will need to do a manual check on the value of authenticate to handle permissions.
For example:
myApp.config(function($stateProvider, $urlRouterProvider){
$stateProvider
.state("newProduct", {
url: '/products/new',
templateUrl: 'app/products/templates/product-new.html',
controller: 'ProductNewCtrl',
authenticate: true
})
});
Where you require to check permissions, you you will need access the $state u want using the $state object.
for example in your controller inject the $state object and use:
if($state.get('newProduct').authenticate){ //if(true) in this case)
//do what u want
}
If you want to check permissions everytime u change state/screen heres an example for that too:
angular.module("myApp").run(function ($rootScope, $state, AuthService) {
$rootScope.$on("$stateChangeStart", function(event, toState,
toParams,fromState, fromParams){
if (toState.authenticate){
// User is authenticated
// do what u want
}
});
});
found the fix thanks to Alon indexOf it got me thinking :)
.run(function($rootScope, $state, Auth) {
// Redirect to login if route requires auth and the user is not logged in
// also if the user role doesn't match with the one in `next.authenticate`
$rootScope.$on('$stateChangeStart', function(event, next) {
if (next.authenticate) {
var loggedIn = Auth.isLoggedIn(function(role) {
if (role && next.authenticate.indexOf(role[0]) !== -1) {
console.log('works')
return; // logged in and roles matches
}
event.preventDefault();
if(role) {
// logged in but not have the privileges (roles mismatch)
console.log(next.authenticate.indexOf(role[0]));
$state.go('onlyAdmin');
} else {
// not logged in
$state.go('login');
}
});
}
});
});
Related
I have angular ui-router and I with to restrict access to a view unless a cookie is active.
I set my cookie in a post where i set cookie to users email address.
I know wish to only allow user to a view if they have a cookie username.
How do I do this ?
Setting cookie
FirstModule.controller('LoginController', function ($scope, $http, $cookies, $location) {
$scope.sometext = {};
$scope.LoginForm = function () {
var data = {
LoginEmail: $scope.sometext.LoginEmail
};
$http({
url: 'http://localhost:8080/back-end/controller',
method: "POST",
data: data,
headers: {'Content-Type': 'application/json'}
}).then(function (response) {
if (response.status === 200)
// $scope.sometext = "You are a valid member, please proceed to Mock Up Maker";
$cookies.put('UserName', $scope.sometext.LoginEmail);
$location.path('/download');
// console.log($cookies);
}).catch(function (response) {
if (response.status === 400)
$scope.sometext = "Hmm, it seems you are not registered, try again or register";
else if (response.status === 404)
$scope.sometext = "this is non a valid email address, please check email";
else if (response.status === 500)
$scope.sometext = "No API connection. Server side fail ";
else $scope.sometext = "Server connection error, give it a second to establish connection then try again";
});
}
});
router
FirstModule.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
// route to show our basic form (/form)
.state('form', {
url: '/form',
templateUrl: 'views/form.html',
controller: 'formController'
})
// nested states
// each of these sections will have their own view
// url will be nested (/form/signup)
.state('form.signup', {
url: '/signup',
templateUrl: 'views/form-signup.html'
})
// url will be /form/select
.state('form.select', {
url: '/select',
templateUrl: 'views/form-select.html'
})
// url will be /form/type
.state('form.type', {
url: '/type',
templateUrl: 'views/form-type.html'
})
// catch all route
// send users to the form page
$urlRouterProvider.otherwise('/form/signup');
});
You can use the resolve property from ui-router, just add the field resolve in your state definition and if the condition is not met redirect the user to a different state (eg. the login state)
state('user', {
url: '/',
templateUrl: 'UserView.html',
controller: 'UserViewController',
resolve: {
check: function($q, $cookies) {
if ($cookies.get('UserName')){ //cookie to check
return $q.resolve({});
} else{
return $q.reject({redirectState: 'loginState'});
}
}
}
});
then add an error handler to detect any error
$rootScope.$on('$stateChangeError', function(evt, to, toParams, from, fromParams, error) {
if (error.redirectState) {
$state.go(error.redirectState);
}
})
The answer given by Karim is good and should work well. However, this is like taking action at second step in flow.
The best way to handle this is to provide user a way to navigate to this URL only if he has that cookie set. You can either disable the link or hide it completely. IMO, that is a better approach.
I's rather implement it both ways to make my application robust. That implies, disabling/removing link/button will prevent users from accessing the link at all. And the Router-Resolve will safeguard from smart users who can copy paste the link or hit enter/F5 on browser. :)
I'm trying to create a service to check if a certain route needs a user to be logged in to access the page. I have a working code but I want to place the $scope.$on('routeChangeStart) function inside the service. I want to place it in a service because I want to use it in multiple controllers. How do I go about this?
Current code:
profileInfoCtrl.js
angular.module('lmsApp', ['ngRoute'])
.controller('profileInfoCtrl', ['$scope', '$location', ' 'pageAuth', function($scope, $location, pageAuth){
//I want to include this in canAccess function
$scope.$on('$routeChangeStart', function(event, next) {
pageAuth.canAccess(event, next);
});
}]);
pageAuth.js
angular.module('lmsApp')
.service('pageAuth', ['$location', function ($location) {
this.canAccess = function(event, next) {
var user = firebase.auth().currentUser;
//requireAuth is a custom route property
if (next.$$route.requireAuth && user == null ) {
event.preventDefault(); //prevents route change
alert("You must be logged in to access page!");
}
else {
console.log("allowed");
}
}
}]);
routes.js
angular.module('lmsApp')
.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider){
$routeProvider
.when('/admin', {
templateUrl: 'view/admin.html',
css: 'style/admin.css',
controller: 'adminCtrl',
requireAuth: true //custom property to prevent unauthenticated users
})
.otherwise({
redirectTo: '/'
});
}]);
By using $routeChangeStart, you are listening to a broadcast sent by $routeProvider on every change of the route. I don't think you need to call it in multiple places ( controllers ), just to check this.
In your service:
angular.module('lmsApp')
.service('pageAuth', ['$location', function ($location) {
var canAccess = function(event,next,current){
var user = firebase.auth().currentUser;
//requireAuth is a custom route property
if (next.$$route.requireAuth && user == null ) {
event.preventDefault(); //prevents route change
alert("You must be logged in to access page!");
}
else {
console.log("allowed");
}
}
$rootScope.$on('$routeChangeStart',canAccess);
}]);
And then inject your service in the .run() part of your application. This will ensure the check will be done automatically ( by the broadcast as mentioned earlier ).
In you config part :
angular.module('lmsApp')
.run(function runApp(pageAuth){
//rest of your stuff
});
You would add an event handler in the Service to $rootScope instead of $scope.
Also it would be much better if you would add the $routeChangeSuccess in the ".run" section so all the pages can be monitored from one point rather than adding it in every controller
You need to listen $rootScope instead of $scope.
And I think it's better to call that listener on the init of wrapped service, for instance (Services are singletons, so it will be run once you will inject it to any controller).
angular.module('lmsApp')
.service('stateService', ['$rootScope', function ($rootScope) {
$rootScope.$on('$locationChangeStart', (event, next, current) => {
// do your magic
});
}]);
I'm not sure if this is a duplicate or not, but I didn't manage to find anything that worked for me, so I'm posting this question.
I have a situation where I need to get values from database before directing user to certain routes, so I could decide what content to show.
If I move e.preventDefault() right before $state.go(..) then it works, but not properly. Problem is that it starts to load default state and when it gets a response from http, only then it redirects to main.home. So let's say, if the db request takes like 2 seconds, then it takes 2 seconds before it redirects to main.home, which means that user sees the content it is not supposed to for approximately 2 seconds.
Is there a way to prevent default at the beginning of state change and redirect user at the end of state change?
Also, if we could prevent default at the beginning of state change, then how could we continue to default state?
(function(){
"use strict";
angular.module('app.routes').run(['$rootScope', '$state', '$http', function($rootScope, $state, $http){
/* State change start */
$rootScope.$on('$stateChangeStart', function(e, to, toParams, from, fromParams){
e.preventDefault();
$http
.get('/url')
.error(function(err){
console.log(err);
})
.then(function(response){
if( response.data === 2 ){
// e.preventDefault()
$state.go('main.home');
}
// direct to default state
})
}
}]);
});
You could add a resolve section to your $stateProviderConfig.
Inside the resolve you can make a request to the databse and check required conditions. If case you don't want user to acces this page you can use $state.go() to redirect him elsewhere.
Sample config:
.state({
name: 'main.home',
template: 'index.html',
resolve: {
accessGranted: ['$http', '$state', '$q',
function($http, $state, $q) {
let deffered = $q.defer();
$http({
method: 'GET',
url: '/url'
}).then(function(data) {
if (data === 2) {
// ok to pass the user
deffered.resolve(true);
} else {
//no access, redirect
$state.go('main.unauthorized');
}
}, function(data) {
console.log(data);
//connection error, redirect
$state.go('main.unauthorized');
});
return deffered.promise;
}
]
}
});
Documentation of the resolve is available here
Note that you could use Promise object instead of $q service in case you don't need to support IE
One way to handle this situation is adding an interceptor as follows.
.config(function ($httpProvider) {
$httpProvider.interceptors.push('stateChangeInterceptor');
}).factory('stateChangeInterceptor', function ($q, $window,$rootScope) {
return {
'response': function(response) {
var isValid = true;//Write your logic here to validate the user/action.
/*
* Here you need to allow all the template urls and ajax urls which doesn't
*/
if(isValid){
return response;
}
else{
$rootScope.$broadcast("notValid",{statusCode : 'INVALID'});
}
},
'responseError': function(rejection) {
return $q.reject(rejection);
}
}
})
Then handle the message 'notValid' as follows
.run(function($state,$rootScope){
$rootScope.$on("notValid",function(event,message){
$state.transitionTo('whereever');
});
})
I'm not really sure why this is happening. I'm trying to listen to each state change in my Angular application and push the user back to login if they're not Auth. I'm storing their encrypted sessionID in a cookie, checking if that cookie is undefined and the redirecting the user as such.. or at least that's what I'm trying to do. I will be using the toState and toParams arguments down the road for user role's etc. so you can ignore those as part of this task for now.
Any feedback would be helpful. Thanks in advance!
.run(($rootScope, $state, $cookies) => {
$rootScope.$on('$stateChangeStart', (evt, toState, toParams) => {
if(!$cookies.get('SessionID')){
evt.preventDefault();
$state.go('login');
}
})
})
Since you're doing this on every state change, you've created an infinite loop. you might pretty this up a bit, but this should get you there:
.run(($rootScope, $state, $cookies) => {
$rootScope.$on('$stateChangeStart', (evt, toState, toParams) => {
if(!$cookies.get('SessionID'))
{
if (toState.name !== "login") {
evt.preventDefault();
$state.go('login');
}
}
})
})
The above will get you started, but as you move forward you'll find that there are other states that you want to allow access to without cookies or authentication as well. The best way to handle this is probably with a custom property that you define on each state. So when you define your states you would do something like:
.state('home', {url: '/home', templateUrl: 'views/home.html', authRequired: false})
.state('someSecureUrl', {url: '/someSecure.url', templateUrl: 'views/someSecureUrl.html', authRequired: true})
then modify the code above to be something like:
.run(($rootScope, $state, $cookies) => {
$rootScope.$on('$stateChangeStart', (evt, toState, toParams) => {
if(!$cookies.get('SessionID') && toState.authRequired)
{
evt.preventDefault();
$state.go('login');
}
})
})
You can also look at doing this using a resolve function on your states.
I'm using ui-router in my angular application. Currently I've two routes /signin & /user.
Initially it shows /signin when the user clicks on the login button, I'm sending a ajax request and getting the user id. I'm storing the user id in localstorage and changing the state to /user.
Now, what I want, if a user is not loggedin, and user changes the addressbar to /user, it'll not change the view, instead it'll change the addressbar url to /signin again.
I'm try to use resolve, but it's not working. My code is:-
module.exports = function($stateProvider, $injector) {
$stateProvider
.state('signin', {
url: '/signin',
template: require('../templates/signin.html'),
controller: 'LoginController'
})
.state('user', {
url: '/user/:id',
template: require('../templates/user.html'),
resolve:{
checkLogin: function(){
var $state = $injector.get('$state');
console.log("in resolve");
if (! window.localStorage.getItem('user-id')) {
console.log("in if")
$state.go('signin');
}
}
},
controller: 'UserController'
})
}
Please help me to solve this problem.
I don't think it's allowed to change states in the middle of a state transition.
So, the way to address it is to have the checkLogin resolve parameter (I changed it below to userId) to be a function that either returns a value or a promise (in this case, a rejected promise, if you can't get the user-id).
You'd then need to handle this in $rootScope.$on('$stateChangeError') and check the error code.
resolve: {
userId: function ($q, $window) {
var userId = $window.localStorage.getItem('user-id');
if (!userId) {
return $q.reject("signin")
}
return userId;
}
}
And redirect in the $stateChangeError handler:
$rootScope.$on('$stateChangeError', function (event, toState, toParams, fromState, fromParams, error) {
if (error === "signin") {
$state.go("signin");
}
});
If someone has this problem, you can solve it, using timeout service. It will put state switching call at the end of queue.
Also, you should use promises. Rejecting it will prevent initialization of that state:
resolve:{
checkLogin: function(){
var deferred = $q.defer();
var $state = $injector.get('$state');
if (!window.localStorage.getItem('user-id')) {
$timeout(function(){$state.go('signin');});
deferred.reject();
} else {
deferred.resolve();
}
return deferred.promise;
}
},