I make a single page application with Sails and AngularJS. And now i'm trying to make an authentication.
I have this angular code on client side:
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/', {
templateUrl:'pages/postList.html',
controller:'PostListController'
}).
when('/createPost', {
templateUrl:'pages/postCreation.html',
controller:'PostCreationController'
})
}])
Here all .html files are stored at assets/ folder, which is public. But i don't want to have a public access to postCreation.html, i want to permit access to this file for some users using policies.
I think i can put all .html files in views folder, create a controller methods for each file and than use policies. But i'm not sure this is a good solution.
So, how to use policies in Sails + Angular SPA?
You cannot use sails policies for this. Instead, you can use the resolve parameter in your $routeProvider.
From $routeProvider docs:
An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated. If all the promises are resolved successfully, the values of the resolved promises are injected and $routeChangeSuccess event is fired. If any of the promises are rejected the $routeChangeError event is fired.
I have something along the likes of following configured in an app of mine, which is working great (using $stateProvider, but the concept should work just as well for $routeProvider):
.state('stateOnlyCertainUsersCanSee', {
url: "/stateOnlyCertainUsersCanSee",
templateUrl: "myTemplate.html",
controller: 'myController',
resolve: {
validate: function($q, $sails, $state) {
var defer = $q.defer();
$sails.get("/me") // gets user info
.then(
function(response) {
if (response.user.canAccessThisPage) { // Condition on which to pass or fail an user
defer.resolve();
}
else {
defer.reject("No access to page");
$state.go("home"); // Redirect wherever you want
}
}
);
return defer.promise;
}
}
})
Related
I am attempting to prevent navigation in AngularJS based upon the result of an HTTP GET to an authorization endpoint (which ties into my Spring Security architecture, but that's not important to this question).
I have attached the following to a run() block attached to my top-level module:
$rootScope.$on("$locationChangeStart", function(event, newUrl, oldUrl) {
var path = $location.path();
$http.get('/svc/authorize/view?urlPath=' + path).then(response => {
if (response.data.result === "NOT_AUTHORIZED") {
event.preventDefault();
console.log("Prevented unauthorized location change");
$ui.showError("Unable to Navigate to " + newUrl);
}
});
});
(Note: $ui is our service, not an AngularJS or third-party tool).
Unfortunately, due to the asynchronous $http.get(), the page loads before the call completes.
In case it helps, here is an example of our of our route definitions:
$routeProvider.when('/problem', {
templateUrl: '/app/management/problem/problem.tmpl.html',
controller: 'problemCtrl'
});
Can anyone help me? I'm hoping I just made a stupid error in handling the asynchronous call.
One normally uses a resolve function in the route to prevent unauthorized routes from loading.
$routeProvider.when('/problem', {
templateUrl: '/app/management/problem/problem.tmpl.html',
controller: 'problemCtrl',
resolve: {
authorized: function($http) {
return $http(someUrl).then(function(response) {
var data = response.data;
if (response.data.result === "NOT_AUTHORIZED") {
console.log("Prevented unauthorized location change");
throw "NOT AUTHORIZED";
};
//ELSE
return data;
});
}
}
});
When resolve functions return promises, the router will wait for the promises to be resolved or rejected before the controller is instantiated. If all the promises are resolved successfully, the values of the resolved promises are injected and $routeChangeSuccess event is fired. If any of the promises are rejected the $routeChangeError event is fired.
For more information, see
AngularJS ngRoute $routeProvider API Reference - .when method
Is there a way to associate the function in authorized with all routes in one single place, instead of modifying each individual route?
The code can be re-factored to use a service:
$routeProvider.when('/problem', {
templateUrl: '/app/management/problem/problem.tmpl.html',
controller: 'problemCtrl',
resolve: { auth: (AuthService,$route) => AuthService.check($route) }
})
Use $route.current.params to access the proposed new route's parameters.
I am curious as to what others are doing in regards to route security for their applications. At first we implemented our security by calling our security service from the .run method in angular
app.run(function ($rootScope, $state, SecuirtyService){
SecurityService.initSecurity().then(function(data){}); }
SecurityService.initSecurity would then make a rest call to see what permissions the user has. The problem with this is that since .initSecurity returns a promise the rest of the application would execute until $http decided to execute the call therefor loading the UI without the security checks being finished.
So in order to combat this, in our ui-router i added a resolve to each of our states. This makes sure that our .initSecurity is fulfilled before the controller is loaded.
Header: {
templateUrl: "header/headerBar.html",
controller: "HeaderController as headerCtrl",
resolve: {
security: function(SecurityService){
return SecurityService.initSecurity();
}
}
},
The problem with this is that .initSecurity is called before each page loads which seems very redundant.
In a perfect world the .run method wouldn't finish until the promise has been fulfilled but https://github.com/angular/angular.js/issues/4003 looks like that is not happening.
My question would be how are you implementing security checks on you application? Are you firing it off from the .run method somehow? Or is having it be apart of the resolve a valid choice?
You can create a parent state (we call it auth) and resolve security from there.
$stateProvider
.state('auth', {
abstract: true,
resolve: {
security: function(SecurityService) {
// Will resolve once if transferring between auth states
return SecurityService.initSecurity();
}
}
})
.state('auth.profile', {
...
});
You can use deferIntercept to pause the initial routing until your async check is complete. Then use listen and sync to begin routing.
myapp.config(function ($urlRouterProvider) {
$urlRouterProvider.deferIntercept();
})
myapp.run(function (SecurityService, $urlRouter) {
SecurityService.initSecurity()
.then(function(data){
$urlRouter.listen();
$urlRouter.sync();
});
});
Currently I'm loading my firebase data right in my controller like this:
app.controller("MyCtrl", function ($scope, $firebaseObject) {
$scope.users = {};
var myDB = new Firebase('https://mydb.firebaseio.com/');
$firebaseObject(myDB.child("users")).$bindTo($scope, "user");
});
This way after the page is loaded, there is a delay during which the data is retrieved from firebase, therefore at first the page appears to be empty until the data arrives. This is unconvinient.
In order to prevent this flashy behavior I wanted to resolve the required data in my $routeProvider but wasn't able to get a promise.
How to properly get rid of the content flashing?
Is there a way to get a promise so one can resolve the $firebaseObject before the view is shown?
Please do not offer quick-and-dirty-solutions containing ng-cloak, ng-hide, ng-show etc.
You can use the $loaded() promise in your resolve function.
app.config(["$routeProvider", function($routeProvider) {
$routeProvider.when("/home", {
// the rest is the same for ui-router and ngRoute...
controller: "HomeCtrl",
templateUrl: "views/home.html",
resolve: {
"myObject": ["$firebaseObject", function($firebaseObject) {
var ref = new Firebase("...");
return $firebaseObject(ref).$loaded();
}]
}
})
https://www.firebase.com/docs/web/libraries/angular/guide/synchronized-objects.html
Check out the AngularFire docs on routing in authentication for better guidance on handling routes with promises in general.
https://www.firebase.com/docs/web/libraries/angular/guide/user-auth.html
I am relatively new to Angular but I am quite an experienced developer. So far I have made quite some progress in building my application to work with a CMS. I am a bit lost however on what the 'correct' approach would be to handle data in my model.
This is best described with an example:
Because I am hooking up my angular frontend with a CMS, the routing (pages) exist only in the CMS context. This means that the routing should be dynamic as well. I have managed to get the dynamic routes thing to work, but when I try to do things the right way (actually getting data from a server) I run into some issues...
app.config(function($provide, $routeProvider) {
$provide.factory("$routeProvider", function() {
return $routeProvider;
});
});
// Load the dynamic routes from the API...
app.run(function($routeProvider, $http, $scope, logger, siteRoutes) {
$routeProvider.when('/', { templateUrl: '__views/', controller: 'ContentPageController' });
$routeProvider.otherwise({redirectTo: '/'});
});
In other words, I inject a service into my app.run method (siteRoutes) and this one should connect to the API.
So my siteRoutes is a service:
cmsModule.service('siteRoutes', function siteRouteFactory(apiConnection, logger)
// SNIP
And in this service I inject my generic apiConnection service:
cmsModule.factory('apiConnection', ['$q', '$http', '$timeout', 'logger', function apiConnectionService($q, $http, $timeout, logger)
What I want is this:
I would like the siteRoutes service to load the data once and not execute the connection every time. I did this in the following way:
bla.service('example', function() {
var service = {
get: function(apiStuff) { // DO API CONNECT WITH .THEN HERE },
data: {}
}
service.get();
return service;
}
I would like one entry point towards the Api that handles all the $q stuff (my factory) I assumed I need to handle all the .then() stuff in my siteRoutes object, which is what I did.
Now, what happens in my app.run method is that I don't get the siteRoutes object with any data. So I recon I need to do a .then there as well?
But that made me question the entire design of putting all logic in a separate factory for the connection, because I basically like my app to just use the data and have my library deal with the async stuff (if you get what I am saying)...
Hope this is clear.
TL;DR -> How to make your services / factories handle async stuff without making your 'app' deal with it?
The templateUrl property can also be a function that takes the url parametes as input.
In the example below all routes will load a template with same name.
Eg. domain.com/#/blabla.html will load the view blabla.html from the server.
myApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/:templateName',
{
templateUrl: function (params) {
return params.templateName + ".html";
}
}
)
.otherwise({ redirectTo: '/main' });
}]);
This might seem like a silly question.. but how do I pass req.user.username (for example) to all pages / globally after the user signs in with passport. This question can apply to any data I would like accessible for all pages...
On the server side, I have the below which sends allows routeProvider to handle all client side routing.
app.get('*',
function(req, res) {
res.sendfile('./public/index.html')
// load the single view file (angular will handle the page changes on the front-end)
})
I'm not sure if the solution is specific to passport... express or involves both...
The client side routing is handled by something like:
.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/', {templateUrl: 'views/home.html'})
.when('/login', {templateUrl: 'views/login.html'})
.when('/users', {templateUrl: 'views/users.html', controller: 'UserController'})
...
You have two options. Either
BAD - include that data in your compiled view that is initially served (./public/index.html) or
GOOD/COMMON - fetch the data you need inside something like an Angular controller which is in your view; eg.
$routeProvider.when('/', {
templateUrl: 'views/home.html',
controller: function($scope, myThings) {
//myThings is a service that async fetches some data from api
myThings().then(function(user) {
$scope.user = user;
});
}
});
This obviously means you are exposing data on an api endpoint, but how else would angular be fetching the bits it needs since you said this is a single-page app?
Services are the way to go if you want to share global data among controllers, directives and other services.
Depending upon the type of data, you can expose services that load data from the server or services which do not need to make remote call to load data (like some custom view settings).
For example if in case you want to get the current logged in user.
The first thing is to create a method on the server that return the current logged in user data in json format.
Then create something like a UserService or SessionService that call this server method to load currently loggedin user data.
Something like
angular.module('myApp').factory('SessionService',['$http',function($http) {
var service={};
service.getCurrentUser=function() {
return $http('/user');
};
return service;
}]);
Inject this service into your controllers to get the current user. You can optimize it to cache the user data.
If you want to use the data in routeProvider use the resolve property
.when('/users', {templateUrl: 'views/users.html', controller: 'UserController',
resolve: {
currentUser:function(SessionService) {
return SessionService.getCurrentUser();
}
}}})