I am currently using ui.router for my page routing in my Angular application but it am starting to get a bit of issues with exploiting parts of my applications to users that don't have permissions to it.
I have tried to find documentation on this with no luck so fare so hope somebody in here have an approach for this.
In my example below i show that there are three pages in my application, but not all are allowed to go to the moderators page. If they do i will of course validate the permission server side and redirect them, but i feel that showing all possible pages in my application is a bit of exploiting it, can this in some way be limited with ui.router, and not by my auto generating the router file server side, or is that just something i need to live with :)
$stateProvider
.state('default', {
url: '/',
views: {
'mainViewContainer': {
templateUrl: 'pages/default.html'
}
}
})
.state('news', {
url: '/news',
views: {
'mainViewContainer': {
templateUrl: 'pages/news.html'
}
}
})
.state('moderators', {
url: '/moderators',
views: {
'mainViewContainer': {
templateUrl: 'pages/moderators.html'
}
}
});
Try adding using resolve - a list of things that are preconditions for a state.
.state(..., {
...
resolve: {
adminPermission: (AdminService) => {
return AdminService.validate();
}
}
...
})
Then, if AdminService returns a promise that fails, the app will not enter the state (and you can catch it using a $onStateChangeError event)
Edit:
How about writing the moderator state in an if clause i.e.
If(isAdmin)
stateProvider.state(...)
Related
Hi I have an application made in Ionic and AngularJS that have a login view and many other views that are show only after the user login (With his Fb account or as a guest).
I have some bugs relating to the change of account type (Fb, Guest) and I think all this problems can be solve destroying the state of all other controller when the user logout of his account and re-created when the user login.
How can I do that?
PostData: Everytime I login into the application a new instance of the controller are created so if I login 20 times 20 controller of the same type will be created...
I really appreciate your help. Thank you!
You can achieve this by simply cache:false in your route
.state('some_page', {
url: "/page1",
cache:false, // add this line and it'll works!!
views: {
'some-tab': {
templateUrl: "templates/page1.html",
controller: "PageCtrl"
}
}
});
I have a meanjs starter template (with yeoman generator).
Where can I add specific permissions to my modules? For instance,
'use strict';
// Configuring the Articles module
angular.module('adminpanel').run(['Menus',
function(Menus) {
// Set top bar menu items
//Menus.addMenuItem('topbar', 'admin panel', 'adminpanel/', 'adminpanel');
Menus.addMenuItem('topbar', 'Admin Panel', 'adminpanel', 'dropdown', '/buildings(/create)?');
Menus.addSubMenuItem('topbar', 'adminpanel', 'List Collections', 'adminpanel/collections');
}
]);
and the routes like so
'use strict';
//Setting up route
angular.module('adminpanel').config(['$stateProvider',
function($stateProvider) {
// Adminpanels state routing
$stateProvider.
state('listCollections', {
url: '/adminpanel/collections',
templateUrl: 'modules/adminpanels/views/list-collections.client.view.html'
}).
state('showCollection', {
url: '/adminpanel/collections/:collectionName',
templateUrl: 'modules/adminpanels/views/show-collection.client.view.html'
}).
state('showCollectionItem', {
url: '/adminpanel/collections/:collectionName/:itemId',
templateUrl: 'modules/adminpanels/views/show-item.client.view.html'
});
}
]);
Are these the correct places to add role-based authentication (on the client side), with added measure on the serverside (I've already done that)?
Does anybody know how I can add an option to the Menus.(some function), such as 'admin.hasPermission', without breaking it? Any resources on this sort of thing?
Thanks for the help!
I don't believe it is right practice to put your authentication, authorization code at the client side as well as server side. They should be on the server side only.
The point is, you have to replicate your authentication and authorization code in the client, anyone can read your mechanism to handle these situation and once a loophole is discovered, it would simply be followed by your server code as well.
I believe authentication and authorization logic should be restricted to server side only. If I am up against someone professional, it would at least make his task tougher.
In case you insist, you can create a wrapper around $http service, maintain a key value pair of what role can do what, and ensure all AJAX request go through your wrapper service where you can check whether it should be allowed. If yes, you can simply forward the request using $http and if not, throw an error.
Not sure about any previous version, but with version 0.4.0 there are parameter in the client config to control the visibility:
If you set isPublic: false and add a roles array you can set the user that can see the menu entry:
// Add the dropdown listCollentcions item
Menus.addSubMenuItem('topbar', 'adminpanel', {
title: 'listCollections',
isPublic: false,
roles:['admin'],
state: 'adminpanel.listCollections'
});
The implementation is in the core module (menu.client.services.js):
// A private function for rendering decision
var shouldRender = function(user) {
if (user) {
if (!!~this.roles.indexOf('*')) {
return true;
} else {
for (var userRoleIndex in user.roles) {
for (var roleIndex in this.roles) {
if (this.roles[roleIndex] === user.roles[userRoleIndex]) {
return true;
}
}
}
}
} else {
return this.isPublic;
}
return false;
};
Maybe you can give version 0.4.0 a try or have a look at the code and try to implement it urself.
I am developing an app using angularjs and this is my first hands on using angular. Although, I have started understanding it and have developed some part of the app but I am stuck at one particular point.
I am trying to implement login functionality, so as the page loads, I am authenticating user and redirecting him to login page. On successful login, I am storing some values of user in one of the config provider.
Now I am using an API which has their own method of authentication and they have expose the ajax method which I can use to authenticate a user.
I have provided a snippet below. What I am primarily doing is using the external API, authenticating the user and once authenticated, I am getting roles associated to that user using another ajax method of the API, called "GetUserDetails".
And inside the response of the "GetUserDetails", I am injecting a provider and setting some values, so I can use this across my app.
The problem here is the app.config method is never called/executded. I mean the ajax request is returning response, and the alert is displayed on my page, but app.config is never executed.
But the same app.config if I call inside the done() of GetUser method, the app.config gets executed and stores values in my provider. But I want the GetuserDetails values also to be stored before I do anything in my app as I want to execute certain functionality based on user.
Below is my function in main.js file
function(angular,angularRoute,app,routes,configService){
var $html = angular.element(document.getElementsByTagName('html')[0]);
angular.element().ready(function() {
$.c.authentication.getUser()
.done(function(response){
if(response.userName!="anonymous"){
$.c.ajax({
method: "GetUserDetails",
parameters: {
User: response.user
}
})
.done(function(res) {
alert("I have reached the destination").
app.config(['configServiceProvider', function(configServiceProvider){
configServiceProvider.setLoginStatus(true);
configServiceProvider.setUserName(response.userName);
configServiceProvider.setUserObject(response);
configServiceProvider.setUserRoleDetails(res);
}]);
})
.fail(function(res) {
alert("Error while getting user roles ."+res);
});
angular.resumeBootstrap([app['name']]);
}
else
{
app.config(['configServiceProvider', function(configServiceProvider){
configServiceProvider.setLoginStatus(false);
configServiceProvider.setUserName(response.userName);
}]);
//Show Login Screen
var url = window.location.href.split("#")[0];
window.location.href = url + "#/Login";
angular.resumeBootstrap([app['name']]);
}
})
.fail(function(response){
$rootScope.isLoggedIn=false;
});
});
Here is my configServiceProvider
define(['../app'],function(app){
return app.provider('configService', function(){
var options={};
this.setLoginStatus = function(status){
//$rootScope.isLoggedIn = status;
options.isLoggedIn=status;
};
this.setPreLoginInfo=function(info){
options.preLoginInfo=info;
};
this.setUserName=function(name){
options.username=name;
}
this.setUserObject = function(userObject) {
options.userObject = userObject;
}
this.setUserRoleDetails = function(userRoleDetails) {
options.userRoleDetails = userRoleDetails;
}
this.$get=[function(){
if(!options){
}
return options;
}];
});
})
Can anyone please explain me what's going wrong here or what I am missing ?
Also, is there any alternative to achieve the same functionality ?
No luck in figuring out why the above scenario was not working. Since I had already spent lot of time behind this, I have found a workaround to achieve the same with the use of services.
I'd like to know the best practice, how to set up routing and templates in AngularJS to show a different front & login area to visitors, and then show a dashboard to logged in users on the same base url ('/').
The two pages are structurally completely different, and also different assets are needed.
Is it better to setup two different apps for the 2 parts of the website, but then how would I manage the session between the 2?
Or is it better to make an "empty" layout with nothing between the body tags an load the different templates into that, and make separate routing for the front part and the dasboard part?
I'm looking for kind of like the way Facebook's login is made. To stay on the root domain after logging in.
I spent my afternoon Googling and searching SO, but couldn't find any guides on this. Any ideas how you usually do this kind of separation in AngularJS would be very welcome.
Martin's answer is fine, but I'd rather solve the problem with ui-router module:
Create three states: root, dashboard and landing.
Capture URL with root state and redirect to dashboard or landing depending on authorization status.
dashboard and landing will have controller and templateUrl defined in one place together with other application states, which is nice.
Code example:
angular
.module("app", ["ui.router"])
.value("user", {
name: "Bob",
id: 1,
loggedIn: true
})
.config(function($stateProvider) {
$stateProvider
.state("root", {
url: "",
template: "<section ui-view></section>",
controller: function($state, user) {
if ($state.is("root")) $state.go(user.loggedIn ? "dashboard" : "landing");
}
})
.state("landing", {
templateUrl: "landing.html",
controller: "LandingCtrl"
})
.state("dashboard", {
templateUrl: "dashboard.html",
controller: "DashboardCtrl"
});
})
.controller("DashboardCtrl", function($scope, user, $state) {
$scope.logout = function() {
user.loggedIn = false;
$state.go("root");
}
})
.controller("LandingCtrl", function($scope, user, $state) {
$scope.login = function() {
user.loggedIn = true;
$state.go("root");
}
})
Complete example on Plunker.
You can use the same master template, include different partials depending on if the user is logged in or not.
<ng-include=" 'views/loggedout.html' " ng-if="!loggedIn"></ng-include>
<ng-include=" 'views/loggedin.html' " ng-if="loggedIn"></ng-include>
The Background:
I am using ui-router for my Angular page routing needs. It's working great so far, however I'm running into an issue. When I load a state and I resolve my user object. I use restangular to make the call to the database and it returns a promise. Everything works great. If I then log out, and log in as another user. Then navigate back to that same page it shows the previous user object.
Things that I've discovered:
The rest api call is being made every time when the state loads, and
it is the correct information.
If I place a break point inside my controller the user object that the resolve passes is the cached
information.
Theories:
The rest API end point is /users/me/, which is the same end point for
every user. We just deliver different information based off of the
JWT token we pass. Somewhere must things since it's the same call
don't bother delivering the goods it already got.
Things I've tried:
I've confirmed that the API call isn't cached, and it is delivering
the correct information to angular
I've tried grabbing the
$cacheFactory of $http and .removeAll.
Sample code:
angular.module('services.user', [ ])
.factory('User', function(Restangular) {
return Restangular.service('users');
});
angular.module('settings.profile', [
'ui.router',
'services.user'
])
.config(function($stateProvider){
$stateProvider
.state('settings.profile',{
url: '/profile',
templateUrl: 'app/settings/profile/settings.profile.html',
controller: 'SettingsProfileCtrl',
authenticate: true,
resolve: {
user: function(User) {
var user = User.one('me').get()
return user;
}
}
});
})
.controller('SettingsProfileCtrl',
function($scope, $location, user, $http, apiUrl){
$scope.user = user;
}
I had the same problem, however in my case the data requested in the resolve property wasn't coming from an API so HTTP caching definitely wasn't the problem.
I added {reload: true} for the options property in the troublesome $state.go call and this seems to have forced ui-router to refresh the resolve property. I no longer get the previous user's roles and permissions, which is nice :)
Your REST API parameter does not change i.e. it stays the same /users/me/ in all the requests. While the browser may not cache - which is why you see different correct information the cache.
You can try configuring Restangular to validate the theory by doing as below:-
RestangularProvider.setDefaultHttpFields({cache: true});
However I advise you to use URLs and REST API in the spirit of REST style i.e. use something like...
/users/me/username
where username changes based on the user OR if you have some constraints do the following
/users/me/?t=timestamp
Try adding cache: false to the state configuration object. But I also recommend adding a different parameter to the requests like userId for example.