I am using Node.JS with Express, Angular.JS and the node module connect-roles for ACL. I want to allow a user with user.status of "Platinum" to access "Platinum" but not "Gold" and vice versa.
I have the ACL part working, if I enter /Platinum into the navigation bar I can't access /Gold, but when I try to access /Platinum I only get the template but not the root shell, so what comes up is this:
You made it!
You have the {{status}} status!
If I click on a link in angular to /Platinum, everything works as it should. If I enter any neutral address in the navigation bar, everything works as it should.
This should be an easy fix, but I've not figured it out.
Here is the code that sets up authorizations, I'm pretty sure everything here is okay.
ConnectRoles = require('connect-roles')
var user = new ConnectRoles({
failureHandler: function(req, res, action){
var accept = req.headers.accept || '';
res.status(403);
if(accept.indexOf('html')) {
res.render('access-denied', {action: action});
} else {
res.send('Access Denied - You don\'t have permission to: ' + action);
}
}
});
var app = express();
app.use(user.middleware());
// Setting up user authorizations,
// i.e. if req.user.status = "Platinum", they are given Platinum status
user.use('Platinum', function(req) {
if (req.user.status == 'Platinum') {
return true;
}
});
user.use('Gold', function(req) {
if (req.user.status == 'Gold') {
return true;
}
});
user.use('Admin', function(req) {
if (req.user.status == 'Admin') {
return true;
}
});
That sets up authorizations, now the problem lies below with the routing.
app.post('/login', passport.authenticate('local',
{ successRedirect: '/', failureRedirect: '/login' }));
app.get('/Platinum', user.is('Platinum'), function(req, res) {
//Obviously the code below is wrong.
res.render('templates/support/Platinum');
});
app.get('/Gold', user.is('Gold'), function(req, res) {
res.render('templates/support/Gold');
});
The way you are configuring your routes on server side (using express) is not correct. For a single page app like AngularJS, you need to do all of the routing for pages on the client (i.e. in Angular). The server still defines routes for API requests (e.g. getting and posting data) and static resources (index.html, partial HTML files, images, javascript, fonts, etc), though.
Thus the following code is wrong in your server side JS:
app.get('/Platinum', user.is('Platinum'), function(req, res) {
//Obviously the code below is wrong.
res.render('templates/support/Platinum');
});
app.get('/Gold', user.is('Gold'), function(req, res) {
res.render('templates/support/Gold');
});
Just remove those lines.
Instead, you need to define the routes that the server will handle, such as your /login post one first, and how to get static files (I suggest prefixing them all with /pub in the URL). Then you need to do something like the technique in this answer to return your index.html page if no routes are matched.
That way, when a user types http://localhost:port/Gold, express will see there is no route defined for /Gold, so it will return index.html, which will load AngularJS, run your Angular app, which will then look at the URL and see if that matches any of the routes your AngularJS app has configured, and if so, fetch the partial for that page and insert it into your ng-view (if using the core router).
Related
I've searched all around and i've found a few ways of how to do this, but none seem to specifically fit my needs and i can't get them to work. My latest attempt is below, using express-subdomain with express and trying to server two separate AngularJS apps based on the incoming sub domain. This code currently seems to serve the correct app, but none of the Angular modules are included from what i can see. The browser console has many many errors of 'Uncaught SyntaxError: Unexpected token <'. Which from previous experience i believe means AngularJS was not loaded correctly. Keep in mind, i have no issue running only one AngularJS app, it is only when i try to bring in sub-domain and serve that second static app.
I've already tried vhost with similar failed results.
server.js
app.use(subdomain('app1',express.static(__dirname + '/public/app1')));
app.use(subdomain('app2',express.static(__dirname + '/public/app2')));
require('./app/routes')(app); // configure our routes
app.listen(port);
routes.js
app.get('*', function(req, res) {
var firstIndex = req.get('host').indexOf('.');
var subdomain = req.get('host').substr(0,firstIndex).toLowerCase();
if (subdomain === 'app1'){
res.sendFile(root + '/public/app1/views/index.html');
}else if (subdomain === 'app2'){
res.sendFile(root + '/public/app2/views/index.html');
}else{
res.sendFile(root + '/public/app1/views/notfound.html');
}
});
if (subdomain === 'app1'){
res.sendFile(root + '/public/app1/views/index.html');
This seems wrong.
This will send index.html to any request, i.e. request for js file.
Should be i.e.:
if (subdomain === 'app1' && req.url.indexOf('.') === -1){
} else if (subdomain === 'app2' && req.url.indexOf('.') === -1){
} else { /* return static asset */ }
...
If you create a general json whit diferent instance whit the diferents aplicacitions and create all the call, you can emulate this. This is the example:
const routes = {
routeApp1: {
appTest1:{
url: '/appTest1/appTest1',
file: './mock/appTest1.json',
},
appTest2:{
url: '/appTest1/appTest2',
file: './mock/appTest2.json',
},
}
routeApp2: {
appTest1:{
url: '/appTest2/appTest1',
file: './mock/appTest1.json',
},
appTest2:{
url: '/appTest2/appTest2',
file: './mock/appTest2.json',
},
}
};
app.get(routes.routeApp1.appTest1.url, (req, res) => {
res.send(JSON.stringify(require(routes.routeApp1.appTest1.file)));
});
app.get(routes.routeApp2.appTest2.url, (req, res) => {
res.send(JSON.stringify(require(routes.routeApp2.appTest2.file)));
});
app.listen(3000, () => {
console.log(`Mocks app listening on port ${port}!`);
});
I am trying to run AngularJS, using Angular Router, with a NodeJS server. I do not plan on serving the various views in Node, but instead I want to use the angular router. This first page is served correctly with no errors but when I try to click on another link, the browser displays the following
error code. Below is the relevant code from the server script, the routing script, and where the link in the HTML is.
HTML Link
<li>Add Workout</li>
Server.js
app.get('/', function(req, res) {
res.sendFile(path.join(__dirname + '/public/app/views/home.html'));
});
app.use(express.static('public'));
App.js
var app = angular.module("fitness2Uapp", ["ngRoute"]);
app.config(function($routeProvider) {
$routeProvider
.when("/", {
templateUrl : "./app/views/home.html"
})
.when("/browse", {
templateUrl : "./app/views/browse.html"
})
.when("/add", {
templateUrl : "./add.html"
})
.when("/workout", {
templateUrl : "./app/views/workout.html"
});
});
Try putting in a "catch all" route that just redirects back to the main page. This will in turn allow the angular routing mechanism to kick in. Right now the problem is that it's trying to find an endpoint path of '/add' on the node server, but nothing is found. This should be the very last route established on your server.
I personally use AngularJS and had to do this, and everything works great. Not sure if this will also perform the same way as Angular2+
// Catch all if all other routes fail to match.
app.get('*', (req, res) => {
res.sendFile(path.resolve(`${__dirname}/path/to/home.html`));
});
Is there a way to force the user to log in first in an app generated by the angular full stack yeoman ?
I tried to add the following code in the run part of app.js but was not successful
Auth.isLoggedIn(function(loggedIn) {
console.log(loggedIn);
if (!loggedIn) {
console.log("redirecting");
// event.preventDefault();
$state.go('login');
}
});
I found authentication controls in api index.js files but none for the / landing page ...
Thx
I did not use google as i should have !
To force authentication for a state, just add
authenticate: true
in the state (or all states in my case)
Without more code or information on which router you are using (generator-angular-fullstack supports both the default NgRouter and UIRouter) it is tough to give a complete answer. By your answer to your question I am assuming you have UI Router and have figured out how to do client side authentication within the generated fullstack code. However, you will also need to implement something similar to what they have done in their 'users' api to protect your api end points on the server side and return a 401/403 error.
'use strict';
var express = require('express');
var controller = require('./user.controller');
var config = require('../../config/environment');
var auth = require('../../auth/auth.service');
var router = express.Router();
router.get('/', auth.hasRole('admin'), controller.index);
router.delete('/:id', auth.hasRole('admin'), controller.destroy);
router.get('/me', auth.isAuthenticated(), controller.me);
router.put('/:id/password', auth.isAuthenticated(), controller.changePassword);
router.get('/:id', auth.isAuthenticated(), controller.show);
router.post('/', controller.create);
module.exports = router;
In the above code (which can be found by navigating to the server folder, then the api folder, then the user folder and looking at index.js) you will see that they are calling a couple of functions.
They are calling auth.hasRole('admin') and auth.isAuthenticated().
Those are functions which can be found in the server side auth/role service under the folder auth and in the auth.service.js file.
function hasRole(roleRequired) {
if (!roleRequired) throw new Error('Required role needs to be set');
return compose()
.use(isAuthenticated())
.use(function meetsRequirements(req, res, next) {
if (config.userRoles.indexOf(req.user.role) >= config.userRoles.indexOf(roleRequired)) {
next();
}
else {
res.status(403).send('Forbidden');
}
});
}
I think it is important to understand how this is working on the server side also. So, if you navigate to localhost:9000/admin and open console you will see that there is a 401 or 403 error depending on whether or not you are logged in and/or logged in as an admin user.
Just Paste authenticate:true on main.js
angular.module('testcesarApp')
.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'app/main/main.html',
controller: 'MainCtrl',
authenticate:true
});
});
hi I'm new to node and I'm building a simple MEAN stack app, to reduce the code I'm sending front end files like this
app.use(express.static(path.join(__dirname, 'public')));
i have also build a simple middleware for simple authentication
requireLogin = function (req, res, next) {
if (!req.user) {
console.log('redirecting :)');
res.redirect('/');
} else {
next();
}
};
app.use('/rooms',requireLogin);
I'm trying to use this middleware on routes made in angular.
but this is not working when i navigate through my angular app (it works when i directly put the URL to the address bar) i have also removed the /#/ which is added by angular.
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
I'm using ui-router for routing.
You should do redirecting on angular, but not on node.js application. For example,
requireLogin = function (req, res, next) {
if (!req.user) {
console.log('User does not exist.');
return false;
//
} else {
next();
}
};
app.use('/rooms', requireLogin);
Then, /rooms won't be able to access unless there is user logged-in.
Backend routes (express ones): Those are the routes that an end user won't have to know about or even use them (your angular app will use them to communicate with the backend to work with its data but an end user wouldn't put them directly on the browser)).
Frontend routes (angular ones): Are the routes that maps to different pages of your application and because of that, end users can use them to access some parts of your application directly.
Read Express.js or angular for handling routes in a MEAN application? for more details.
I'm using html5Mode=true with AngularJS routing. Works fine. When I access the site with IE, Angular routing falls back to Hashbang URI's like http://example.com/#!/route-name. That's all fine. Except in Express I need to know the route, because it tells me which html file to serve from Express. The # part of the url is not sent to the server.
My question is, how can I let my server know which AngularJS route is being requested with hashbang routing in IE? I was thinking of configuring Angular somehow to send the route as an http header that I can read in express, but I don't know how to do that. Any ideas?
Update: Based on feedback I got, let me tell you that the site has templates. One for the homepage and one for all the other pages. They are both pretty different. Based on the route, the server needs to know when to serve the file for the homepage and when to serve the file for the other pages.
Just to be clear: this won't send the hashbang on the main page request. Based on the HTTP specifications there is no way of doing that.
You can send it on subsequent AJAX requests, but this won't really help you with solving this problem. There is no solution other than not supporting browsers that don't support html5mode.
Browsers won't send the hashbang to the server automatically, so yes you will need to send it manually. Using a header is a good option.
Take a look at $httpProvider.interceptors in the docs.
You can register a request interceptor for $http (AngularJS uses it internally for any AJAX request and so should you) that attaches a custom header. You can then set that header value to be the actual hashbang using $location.path():
var app = angular.module('MyApp',[]);
app.factory('httpInterceptor', function($q, $location) {
return {
'request': function(config) {
if(config.url.indexOf('product')) {
config.headers.Hashbang = $location.path();
}
return config;
}
};
});
app.config(function($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
});
app.controller('Ctrl', function($scope, $http) {
$http({method: 'GET', url: '/echo/json/'}).
success(function(data, status, headers, config) {
console.log(data);
});
});
Here's the fiddle. You can test it like so. Check out the sent headers in developer tools.
Notice that I'm not testing with a true hashbang (#!) but just a hash because the fiddle does not allow it.
I had a similar problem where I needed the parameters of the URL before they were removed by the redirect. As the URL is redirected in angular from the original one the previous URL is in the referer header.
So I used this express 3.x middleware to rewrite the original parameters from the referer to the query on requests to the root URL:
var _ = require('lodash');
var url = require('url');
var qs = require('querystring');
app.use(function(req, res, next) {
if (req.url === '/' && req.headers && req.headers.referer) {
var referer = url.parse(req.headers.referer);
// rewrite original parameters
_.forEach(qs.parse(referer.query), function(value, key) {
req.query[key] = value;
});
// do something with pathname
console.log(referer.pathname);
}
});
You could do the same in your case for the path, that is in referer.pathname.
There seems no client-side answer as per the answer of Sergiu Paraschiv. So I have investigated a server-side solution that requires only one thing on the client. Make sure you always links as you would do in html5Mode which is linking to /xxxxx and not /#!/xxxxx.
Then In html5Mode and IE9 and lower what happens is that AngularJS redirects everything /xxxxx to /#!/xxxxx. In Express this makes the url / and the referer /xxxxx. This is something that you can check for quite easily.
If you want to cater for IE8 and lower to, it's unfortunate that Angular uses window.location.href in this fall back scenario redirecting to /#!/xxxxx. See github / angular.js / src / ng / browser.js line 169.
Using window.location.href causes IE8 and lower to not send the referer.
Here's a server-side (Express 3.x) solution that resolves the Angular hashbang route from the referer value or a previousUrl session variable for IE8 and lower.
app.use(function(req, res, next) {
if (req.url.match(/\/api/))
return next(); // /api/something should proceed to next in middleware
console.log(req.url, req.headers)
//IE does not support html5Mode so /xxxxx is redirected to /#!/xxxxx
//effectively making the url / and the referer /xxxxx
//I check for that here and if xxxxx is not home I present page.html
if (req.headers['user-agent'] &&
req.headers['user-agent'].match(/MSIE ([7-8]{1}[.0-9]*)/) &&
req.url.match(/^\/[\w-]+$/)
) {
//However IE8 does not report the referer when doing a redirect using window.locarion.href
//so I store the initially requested url /xxxxx in session...
req.session.previousUrl = req.url;
//console.log('IE8 saving the url here', req.session.previousUrl);
}
if (req.headers['user-agent'] &&
req.headers['user-agent'].match(/MSIE ([7-8]{1}[.0-9]*)/) &&
!req.headers['referer'] &&
req.url === '/' &&
req.session.previousUrl &&
req.session.previousUrl !== '/home'
) {
//continuation on the IE* referer story (aka the next redirected request)...
//... and checked for the url stored in session here ;)
return res.sendfile('public\\page.html');
}
if (req.headers['user-agent'] &&
req.headers['user-agent'].match(/MSIE ([7-9]{1}[.0-9]*)/) &&
req.url === '/' &&
req.headers['referer'] &&
req.headers['referer'].match(/^https?:\/\/(?:www)?\.example\.com\/(?!home$)([\w-]+)$/)
) {
return res.sendfile('public\\page.html');
}
if (req.url.match(/\/home|\/$/))
return res.sendfile('public\\home.html'); //for the home pages we're going to use home.html
res.sendfile('public\\page.html'); //for everything else we're going to use page.html
});
I'm sure that there are scenario's where this fails, but it worked for me in my tests and if it fails, that will only fail for IE9- browsers (as per the regexp's).