I working on an SPA that works with a REST server on the backend.
My goal is to create an interface, that will be mutual to all of the roles.
for instance:
On a product page, a guest can view the product and the comments, a registered user also has a text box where he can comment.
The administrator can edit both comments and the product it self, and everything is done within the same view at the SPA.
So in fact we have DOM element that should not be 'compiled' for some users, but should be 'compiled' for others.
What I am doing in order to control the access to my application, is resolving a factory that grantees that the use has the sufficient priviledges to access a certain page, this factory also populates the rootScope with his access level.
Then on the compile function of the xLimitAccess directive I check if the access level of the current user is sufficient to view the content within the directive and then remove it.
Problem: there is no way to access the $rootScope from the compile function(because it doesn't exist yet), and if I'll do it in the link function, it is already too late, and the element cannot be removed from the DOM
HTML code example:
<div class="product">...</div>
<div class="manageProduct" x-limit-access x-access-level="admin">...</div>
<div class="commnet" x-limit-access x-access-level="user, admin">...</div>
<div class="commnet" x-limit-access x-access-level="admin">...</div>
Javascript code:
var app = angular.module('home', []);
// var host = 'http://127.0.0.1:8000';
app.config(function($routeProvider){
$routeProvider.
when('/',
{
templateUrl: 'views/home.html',
controller: 'homeCtrl',
resolve: {auth : 'authUser'} //this is a page only for logged in users
}).
when('/login',
{
templateUrl: 'views/login.html',
controller: 'loginCtrl',
resolve: {}
}).
when('/logout',
{
templateUrl: 'views/logout.html',
controller: 'logoutCtrl',
resolve: {auth : 'authUser'} //this is a page only for logged in users
}).
when('/register',
{
templateUrl: 'views/register.html',
controller: 'registerController',
resolve: {}
}).
when('/admin',
{
templateUrl: 'views/admin/home.html',
controller: 'registerController',
resolve: {auth: 'authAdmin'}
}).
otherwise({ redirectTo: '/'});
// $locationProvider.html5Mode(true);
}).
run(function($rootScope, $location, $http, authUser){
$rootScope.$on("$routeChangeError", function(event, current, previous, rejection){
if(rejection.status == 401)
$location.path('/login');
})
$http.get('/users/me', {withCredentials: true}).
success(function(data, status, headers, config){
$rootScope.roles = data.roles;
}).
error(function(data, status, headers, config){
});
});
app.factory('authUser', function($http){
return $http.head('/users/me', {withCredentials: true});
});
app.directive('xLimitAccess', function(){
return{
restrict: 'A',
prioriry: 100000,
scope: {
xAccessLevel: '='
}
compile: function(element,$rootScope){//this is essentially the problem
if(scope.xAccessLevel != $rootScope.roles)
element.children().remove();
elemnet.remove();
}
}
})
Only looking at the specific problem, of not having $rootScope in your directive's compile function: you can inject it into your directive instead of into your compile function as follows: app.directive('xLimitAccess', function ($rootScope) { }. The compile function does not support injection—it gets passed a set of values directly.
Related
Looking to build web app in Node.js with ability for user to log in (authentication), which has 3 non secure pages (/home, /contact, /about) and one secure page (/admin). As an aside, I've been referencing the scotch.io Mean Machine book.
The issue I'm having is that I've build everything out, and the login mechanism works in that when I log in, I get directed to /admin; however, when I go to /admin in the URL without logging in, I can still access the page. I.e. I'm not sure where to put the actual protection.
A bit below on how I've laid out my app. Hoping for as much a conceptual answer to suggest how I should be doing things, rather than necessarily only a code answer.
Services:
auth service posts to server the inputted username/password and returns either false or success (with user info and JWT token)
auth service also injects as AuthInterceptor the token (if there is one) into each HTTP header
Router:
angular.module('routerRoutes', ['ngRoute'])
.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/home.html',
controller: 'homeController',
controllerAs: 'home'
})
.when('/about', {
templateUrl: 'views/about.html',
controller: 'aboutController',
controllerAs: 'about'
})
.when('/contact', {
templateUrl: 'views/contact.html',
controller: 'contactController',
controllerAs: 'contact'
})
.when('/login', {
templateUrl: 'views/login.html',
controller: 'adminController',
controllerAs: 'login'
})
.when('/admin', {
templateUrl: 'views/admin/admin.html',
controller: 'adminController',
controllerAs: 'admin'
});
$locationProvider.html5Mode(true);
});
Controllers:
homeController, aboutController, contactController are generally empty for now
adminController:
.controller('adminController', function($rootScope, $location, Auth) {
var vm = this;
vm.loggedIn = Auth.isLoggedIn();
$rootScope.$on('$routeChangeStart', function() {
vm.loggedIn = Auth.isLoggedIn();
window.alert(vm.loggedIn); // this gives correct answer and works
Auth.getUser()
.success(function(data) {
vm.user = data;
});
});
vm.doLogin = function() {
vm.error = '';
Auth.login(vm.loginData.username, vm.loginData.password)
.success(function(data) {
vm.user = data.username;
if (data.success)
$location.path('/admin');
else
vm.error = data.message;
});
};
vm.doLogout = function() {
Auth.logout();
vm.user = {};
$location.path('/login');
};
});
And finally, below is my index.html (just the body):
<body class="container" ng-app="meanApp" ng-controller="adminController as admin">
<i class="fa fa-home">Home </i>
<i class="fa fa-shield">About </i>
<i class="fa fa-comment">Contact</i>
<i class="fa fa-comment">Admin</i>
<ul class="nav navbar-nav navbar-right">
<li ng-if="!admin.loggedIn">Login</li>
<li ng-if="admin.loggedIn" class="navbar-text">Hello {{ admin.user.username }}</li>
<li ng-if="admin.loggedIn">Logout</li>
</ul>
<main>
<div ng-view>
</div>
</main>
</body>
I won't paste the other html pages that get injected into since there isn't anything on them yet (the login.html has just the two input fields and submit button).
So a couple of questions:
In my index.html, when I click on /admin, it takes me to the admin page even if I'm not logged in. Where should I put the protection for that to not happen?
Any general comments on my setup and best practices that I'm not following?
Another nit:
I read that "li ng-if=" wouldn't show up in 'view source' if that branch of the decision tree wasn't hit, but it does. Was I misled or am I doing something wrong?
I took a custom property route to secure the routes in my application. Every state change taking place is listened for and inspected if it has this property. If it has this property set then it checks if user is logged in, if they are not, it routes them to the 'login' state.
I used UI-ROUTER in my current project where I have implemented this. I made a custom parameter called "data" that I used within the route.
Within a .config block to declare my opening routes:
$stateProvider
.state('login', {
url: '/login',
templateUrl: 'login/login.html',
controller: 'LoginController',
controllerAs: 'vm'
})
.state('home', {
url: '',
templateUrl: 'layout/shell.html',
controller: 'ShellController',
controllerAs: 'vm',
data: {
requireLogin: true
}
})
Then I add this to a .run on the application where I'm looking for ui-router's $stateChangeStart event and looking at my custom property ('data') on the state declaration:
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
var requireLogin = toState.hasOwnProperty('data') && toState.data.requireLogin;
if (requireLogin && !authService.isLoggedIn()) {
event.preventDefault();
authService.setDestinationState(toState.name);
$state.go('login');
}
if (toState.name !== 'login') {
authService.setDestinationState(toState.name);
}
});
In case you're wondering what the authService.setDestinationState does... it preserves the URL that the user was attempting to visit... once they successfully login it forwards them to that state automagically (see below):
function login() {
authService.authLogin(vm.credentials)
.then(loginComplete)
.catch(loginFailed);
function loginComplete(data, status, headers, config) {
vm.user = data;
$rootScope.$broadcast('authorized');
$state.go(authService.getDestinationState());
}
function loginFailed(status) {
console.log('XHR Failed for login.');
vm.user = undefined;
vm.error = 'Error: Invalid user or password. ' + status.error;
toastr.error(vm.error, {closeButton: true} );
}
}
When you define your Admin route, you can define a property called resolve. Each property within resolve is a function (it can be an injectable function). This function should return a promise, the promise's result can be injected into the controller.
For more information on resolve, look at http://odetocode.com/blogs/scott/archive/2014/05/20/using-resolve-in-angularjs-routes.aspx.
You can use resolve as follows to do an authentication check.
var authenticateRoute = ['$q', '$http' , function ($q, $http) {
var deferred = $q.defer();
$http.get("http://api.domain.com/whoami")
.then(function(response) {
if (response.data.userId) deferred.resolve(response.data.userId);
else window.location.href = "#/Login"
});
return deferred.promise();
}]
// ...
.when('/admin', {
templateUrl: 'views/admin/admin.html',
controller: 'adminController',
controllerAs: 'admin',
resolve: {
authenticatedAs: authenticateRoute
}
});
With this you could pass the authenticated User Id through - even if null - and let the controller deal with it, if for instance, you want a contextual message.
Else, you could do as above and only do so if there is a user Id from the authentication request, otherwise redirect to your login route.
Hope this helps! /AJ
I'm using routeProvider to set the controller and a route param when my application is configured. Here's the code that I'm using:
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/person/:uuid/report', { controller: 'CandidateCtrl' }).
when('/person/:uuid/confirm', { controller: 'ConfirmCtrl', }).
when('/person/add', { controller: 'AddCtrl' })
}]);
However, the controller is not being set correctly. Additionally, when I set the controller with ng-controller in the page itself, the routeParams object is empty. What am I doing wrong?
Edit:
I've also tried this, which also isn't associating the controller with the page nor setting the route-params.
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/person/:uuid/report', { controller: 'CandidateCtrl', template: 'templates/report.html' }).
when('/person/:uuid/confirm', { controller: 'ConfirmCtrl', template: 'templates/confirm.html' }).
when('/person/add', { controller: 'AddCtrl', template: 'templates/add.html' })
}]);
Here's the controller that I'm testing this out with:
appController.controller('CandidateCtrl', ['$routeParams',
function($routeParams) {
console.log($routeParams);
}]);
Try to add templateUrl
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/person/:uuid/report', {
templateUrl: 'partials/CandidateCtrl.html', //TODO : replace with the good template
controller: 'CandidateCtrl'
}).
when('/person/:uuid/confirm', {
templateUrl: 'partials/ConfirmCtrl.html', //TODO : replace with the good template
controller: 'ConfirmCtrl'
}).
when('/person/add', {
templateUrl: 'partials/AddCtrl.html', //TODO : replace with the good template
controller: 'AddCtrl'
}).
otherwise({
redirectTo: 'Something' //TODO : replace with the good url
});
}]);
I was serving the views by via my web-mvc. I've since changed it to serve a layout and have the partial pages be kept in the resource folder.
From below angularJS code I want to get parameter namee in templateUrl please help me to generate dynamic templateurl.
sampleApp.config(['$routeProvider',
function($routeProvider){
$routeProvider.
when('/AddNewOrder/:namee', {
controller: 'AddOrderController',
templateUrl:'templates/'+namee +'.html';
}).
when('/ShowOrders/:name', {
templateUrl: 'templates/show_orders.html',
controller: 'ShowOrdersController'
}).
otherwise({
redirectTo: '/AddNewOrder/add_orders'
});
}]);
You can pass a function as a value to templateUrl. The function return value will be the value for templateUrl. Function have access to the url attributes.
Note that the wildcard url name will be the value that you want to access. In your case its namee. console.log(attrs), if you want to see details of the attrs.
$routeProvider.
when('/AddNewOrder/:namee', {
controller: 'AddOrderController',
templateUrl: function(attrs) {
return 'templates/'+ attrs.namee +'.html';
}
})
I have an rest api, in which the api is sending instruction to redirect (301 is being sent).
And I have my angular js code like this:
var request = $http.post(LOGIN_URL,{username:'tes',password:'test'})
request.success(function(html)
{
if(html.failure) {
console.log("failure")
$scope.errorMessage = html.failure.message
}
else {
console.log("Success here....")
$location.path("route")
}
})
I can see in the browser log that it is coming in the else part ( Success here..... is being printed). But the url is not changed. $location.path doesnt do anything; I have also tried $location.url which also results the same thing.
And also I'm injecting the $location to my controller.
Where I'm making mistake?
Thanks in advance.
Try something like this
$location.path("/myroute")
You have to have a ng-view on the page as well.
Also make sure you have a corresponding view with that name
and when you're registering your controller you have the '$location' var being injected in the declaration of your controller like this example:
controllers.controller('MyCtrl', ['$scope', '$route', '$location', 'MYService',
function($scope, $route, $location, MyService) {
// ... controller code
}])
also you might want to debug your route changing to see what is happening with a location change listener, like in this example:
return app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/home', {
templateUrl: 'partials/home.html',
controller: 'MyCtrl'
}).
when('/login', {
templateUrl: 'partials/login.html',
controller: 'LoginCtrl'
}).
when('/error/:errorId', {
templateUrl: 'partials/error.html',
controller: 'ErrorCtrl'
}).
otherwise({
redirectTo: '/home'
});
}]).run(function($rootScope, $injector, $location) {
$rootScope.$on("$locationChangeStart", function(event, next, current) {
console.log('current=' + current.toString());
console.log('next=' + next.toString());
});
});
});
I configure my app in the following run block. Basically I want to preform an action that requires me to know the $routeParams every $locationChangeSuccess.
However $routeParams is empty at this point! Are there any work rounds? What's going on?
app.run(['$routeParams', function ($routeParams) {
$rootScope.$on("$locationChangeSuccess", function () {
console.log($routeParams);
});
}]);
UPDATE
function configureApp(app, user) {
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
when('/rentroll', {
templateUrl: 'rent-roll/rent-roll.html',
controller: 'pwRentRollCtrl'
}).
when('/bill', {
templateUrl: 'bill/bill/bill.html',
controller: 'pwBillCtrl'
}).
when('/fileroom', {
templateUrl: 'file-room/file-room/file-room.html',
controller: 'pwFileRoomCtrl'
}).
when('/estate-creator', {
templateUrl: 'estate/creator.html'
}).
when('/estate-manager', {
templateUrl: 'estate/manager.html',
controller: 'pwEstateManagerCtrl'
}).
when('/welcomepage', {
templateURL: 'welcome-page/welcome-page.html',
controller: 'welcomePageCtrl'
}).
otherwise({
redirectTo: '/welcomepage'
});
}]);
app.run(['$rootScope', '$routeParams', 'pwCurrentEstate','pwToolbar', function ($rootScope, $routeParams, pwCurrentEstate, pwToolbar) {
$rootScope.user = user;
$rootScope.$on("$locationChangeSuccess", function () {
pwToolbar.reset();
console.log($routeParams);
});
}]);
}
Accessing URL:
http://localhost:8080/landlord/#/rentroll?landlord-account-id=ahlwcm9wZXJ0eS1tYW5hZ2VtZW50LXN1aXRlchwLEg9MYW5kbG9yZEFjY291bnQYgICAgICAgAoM&billing-month=2014-06
this worked for me, inside your run callback:
.run(function($rootScope, $routeParams, $location) {
$rootScope.$on("$locationChangeSuccess", function() {
var params = $location.search();
console.log(params);
console.log('landlord-account-id:', params['landlord-account-id']);
console.log('billing-month', params['billing-month']);
});
})
By accessing /rentroll you will get empty $routeParams because your $routeProvider
configuration for that path is not expecting any variable on URL.
$routeParams is used for getting variable value of your URL. For example :
$routeProvider
.when('/rentroll/:variable', {...});
and access it on /rentroll/something will give you $routeParams as below :
$routeParams.variable == 'something';
https://docs.angularjs.org/api/ngRoute/service/$routeParams
Explains a bit about when the $routeParams are available and why.
To solution here might be to use $route.current.params