Flatiron js - director - how to do async routing from a table? - node.js

I'm starting to get things set up using flatiron as the toolset for a web app.
I'm using director with app.plugins.http, and can't seem to figure out how to create a "catchall" route for static files & 404s - It appears that .get("<RegEx>") only matches the first folder position, so if <RegEx> is /.*, it'll match /foo, but not /foo/bar.
Here's my code, as a better example:
in routes.js:
var routes = {
/* home
* This is the main route, hit by queries to "/"
*/
"/" : {
get: function(){
getStatic("html/index.html",_.bind(function(err,content){
if(err) throw err;
renderContent(this,content);
},this));
}
},
/* static files
* Last rule, if no other routes are hit, it's either a static resource
* or a 404. Check for the file then return 404 if it doesn't exist.
*/
'/(.*)' : {
get : function(){
getStatic(this.req.url,_.bind(function(err,content){
if(!err){
renderContent(this,content);
} else {
this.res.writeHead(404);
// TODO: fancier 404 page (blank currently)
this.res.end();
}
},this))
}
}
}
and in my main app file:
/* Define the routes this app will respond to. */
var routes = require('./lib/routes');
/* set up app to use the flatiron http plugin */
app.use(flatiron.plugins.http);
/* loop through routes and add ad-hoc routes for each one */
for(var r in routes){
var route = routes[r];
if(!routes.hasOwnProperty(r)) continue;
for(var method in route){
if(!route.hasOwnProperty(method)) continue;
app.router[method](r,route[method]);
}
}
/* Start the server */
app.listen(8080);
I'd like to be able to keep my routes in a separate module and import them - I'm pretty unclear on if this method or using director and a vanilla http server would be better, but I've tried both ways without any luck.
Here's what I get:
localhost:8080/
>> (content of index file - this works)
localhost:8080/foo
>> (blank page, 404 header)
localhost:8080/foo/bar
>> (no static file for this - I get a 404 header, but the body is now "undefined" - where is this coming from??)
localhost:8080/css/min.css
>> (this file should exist, but the route is never called. I do however still get a 404 header, and get the "undefined" body)
so, I'm assuming the "undefined" body is default behavior for undefined routes.
Is there a way to create a catchall route without adding rules for each depth?

You could try using node-ecstatic which is a static file serving add-on for flatiron. It works well for me and you can find it at:
https://github.com/colinf/node-ecstatic

Try using onError:
app.use(flatiron.plugins.http,{
onError: function (err) {
this.res.end('Nope');
}
});
To manage your static files I suggest you to use flatiron/union + connect.static

Related

Serve multiple protected static folders using Express

I'm trying to write a very simple Express app, which is supposed to serve multiple static folders.
I have a root folder "stories" which contains multiple folders (story-1, story2, etc...). Each story folder contains static assets (scripts, CSS stylesheets, subpages...).
My users can unlock each of those stories, so each story folder must be protected. (If anyone tries to access http://backend/stories/story-1, it should give a 401 Forbidden).
My initial thought was to generate a one-time JWT upfront (like a signed url; not a bearer), add it to query params like http://backend/stories/story-1?jwt=the-jwt-token, then do some backend logic to test if the user has access to this content before serving it.
I tried fiddling with a basic express configuration + a custom authorization middleware :
Project structure :
...
-- /src
-- /stories ⬅️ custom public folder
-- /story-1 ⬅️ public but protected
- index.html
- /subpages
-page2.html
-page3.html
- /styles
- /scripts
-- /story-2 ⬅️ public but protected
- index.html
- /subpages
-page2.html
-page3.html
- /styles
- /scripts
-- /story-3 ⬅️ public but protected
- index.html
- /subpages
-page2.html
-page3.html
- /styles
- /scripts
etc...
index.js :
const express = require("express");
const { authorized } = require("./middlewares/authorized");
const app = express();
const port = 3000;
app.use("/stories/:story", authorized);
app.use("/stories", express.static(__dirname + "/stories"));
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
authorized.js :
exports.authorized = (req, res, next) => {
const jwt = req.query.jwt;
if (!jwt) return res.sendStatus(401);
// todo : custom logic to test if the user has access to this content, if yes we do next(), if no we return a 401.
return next();
};
This simple example works partially, when I try to go to http://localhost:3000/stories/first-story (without JWT), I get a 401 (that's ok).
But when I add the jwt :
http://localhost:3000/stories/first-story/?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
The middleware runs for every assets that are linked in the index.html, but those assets urls don't have the JWT query params, which leads to a 401.
I guess it's totally normal because that's how middlewares are intended to work. My guess is that i'm configuring express router wrong :
app.use("/stories/:story", authorized);
app.use("/stories", express.static(__dirname + "/stories"));
I would like to run the middleware only once, when any of the /:story subfolders inside /stories are asked to be served.
You write:
I would like to run the middleware only once, when any of the /:story subfolders inside /stories are asked to be served.
But if every .html (sub-)page is served by a separate HTTP request, each of these requests must be protected, assuming that the HTML contains material that is worthy of protection. (The styles and scripts may not need this extra protection.)
Therefore it is OK that the authorized middleware runs for each such request. And if the JWT was in a cookie (as suggested by Kaneki21), it would be present automatically in each request.
I would separate out the access control logic from the identity logic. You can use your jwt to verify that the user is who they say the are, and then use your existing knowledge of who that user is to grant them access.
I put a simple example using cookie-backed sessions below, note that you can add sequential middleware a, b, and c all in one function via app.use('/foobar',a,b,c).
// other setup
...
const session = require('express-session'),
fs = require('fs'),
{ Router } = require('express');
const secret = (() => {
let secretFile = '/path/to/my/secret.txt';
try {
// try reading a saved secret
return fs.readFileSync(secretFile,'utf8');
}
catch(err) {
// otherwise generate secret and save it
let random = require('crypto').randomBytes(128).toString('base64');
fs.writeFileSync(secretFile,random);
return random;
}
})();
// Add the session middleware to the app
app.use(session(
{ secret,
name: 'stories-and-whatnot',
cookie: { sameSite: true } }
));
// Create a router for stories and add it to the app
let storyRouter = Router();
app.use('/stories', storyRouter);
// add identity middleware to storyRouter
storyRouter.use( authorized);
let storyMax = 10;
for(let i=0; i<storyMax; i++) {
// set up the individual story routers
storyRouter.use(
`/story-${i}`,
function(req,res,next) {
if(!req.session.storyAccess || !req.session.storyAccess[i]) {
// If the user's session doesn't show it has access, reject with 401
res.status(401).end(`You do not have access to story ${i}`);
}
else {
// Otherwise let them proceed to the static router
next();
}
},
express.static(require('path').join(__dirname,`stories/story-${i}`)
);
}
...
// And somewhere else you have something like this
app.get('/access-granted', authorized, function(req,res,next) {
let { id } = req.query;
if(!req.session.storyAccess)
req.session.storyAccess = {};
req.session.storyAccess[id] = true;
res.end(`Access granted to story ${id}`);
});
You might consider, not using a middleware at all for the serving of content. But rather, to setup the user's set of "approved" paths.
That way a user, failing authentication, would have no valid paths, other then perhaps a preset collection of "base" paths.
This way, later after your authentication middleware the "routing" can be constrained to just that users set of "granted" paths.
Essentially model the access using sessions, which are established on first request, and then updated and maintained as things progress.
One solution is that you check if the user has the right to view the page on the client side. You'll need some JavaScript on the client side to do this.
You can store the token in LocalStorage after login. Then, at the beginning of the protected HTML file, you include your JS code to retrieve the token, and send a request to the server to check if the user is authenticated or not. Then, based on the response of the server you show the content or hide it.
To be honest, I rarely see the JWT in the URL. People talk about it here, here, here... You should revise your current approach carefully.

Serving create-react-blog in subdirectory

I need to serve a blog created with create-react-blog in the /blog subdirectory on my main site. It is served alongside other separate projects created w/ create-react-app (the NodeJS code looks like this):
app.use("/blog", express.static(path.join(__dirname, "projects/blog")))
app.get("/blog", (req, res) => {
res.sendFile(path.join(__dirname, "projects/blog/index.html"));
});
app.get("/blog*", (req, res) => {
res.sendFile(path.join(__dirname, "projects/blog/index.html"));
});
app.use(express.static(path.join(__dirname, 'build')));
app.get('/*', function (req,res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
Where the 'build' folder contains the react production build for the main site and 'projects/blog' contains the build files for the blog created with create-react-blog
Navigating to http://localhost:3001/blog with this system does indeed render the blog project, but presumably because Navi was built to used from the head directory the blog returns its 404 page. I can then only view the blog if I use the return link on the 404 page to navigate to http://localhost:3001/ (note: just navigating to http://localhost:3001/ without going through the blog page properly renders the other react project)
I've tried changing the homepage in the package.json of the blog to both /blog and ., changing the basename of the Router in the blog project's index.js to /blog, changing the PUBLIC_URL environment variable to blog and http://localhost:3000/blog, and changing the navi.config.js file to add /blog in front of each item in the getPagePathname function (from
import path from 'path'
export const renderPageToString = require.resolve('./src/renderPageToString')
/**
* Get the file to write each URL to during the build
*/
export function getPagePathname({ url }) {
if (url.pathname === '/rss') {
return 'rss.xml'
}
if (url.pathname === '/') {
return 'index.html'
}
return path.join(url.pathname.slice(1), 'index.html')
}
to
import path from 'path'
export const renderPageToString = require.resolve('./src/renderPageToString')
/**
* Get the file to write each URL to during the build
*/
export function getPagePathname({ url }) {
if (url.pathname === '/blog/rss') {
return 'rss.xml'
}
if (url.pathname === '/blog/') {
return 'index.html'
}
return path.join('/blog/', url.pathname.slice(1), 'index.html')
}
), none of which worked. I've also tried random combinations of the above approaches, which were also unsuccessful.
Please let me know how this would be accomplished. Thanks
After many hours, I was able to move the blog in a fairly roundabout way.
Starting from the template found in the main Github repo mentioned in the first paragraph of the question, I had to do the following combination of things:
Change all '/' routes in routes/index.js to /blog/<route> (i.e. / became /blog, /page became /blog/page etc.
Change the blogRoot in the withContext statement to just state /blog
Change isViewingIndex to req.path === '/blog' || /^\/page\/\d+$/.test(req.path)
In the crawlRoutes function in tags.js, add root='/' to the beginning of the function
Change navi.config.js to
import path from 'path'
export const renderPageToString = require.resolve('./src/renderPageToString')
/**
* Get the file to write each URL to during the build
*/
export function getPagePathname({ url }) {
if (url.pathname === '/blog/rss') {
return 'rss.xml'
}
if (url.pathname === '/blog') {
return 'index.html'
}
return path.join('/blog', url.pathname.slice(1), 'index.html')
}
Change renderPageToString.js to
import renderReactPageToString from 'react-navi/create-react-app'
import renderRSSFeedToString from './renderRSSFeedToString'
/**
* navi-scripts will call this function for each of your site's pages
* to produce its statically rendered HTML.
*/
async function renderPageToString(props) {
if (props.url.pathname === '/blog/rss') {
return await renderRSSFeedToString(props)
}
return renderReactPageToString(props)
}
export default renderPageToString
Of course, you can make this cleaner by changing /blog to be an environment variable (which I highly recommend).
If there is any better solution I am also open to it.

Node.js REST API - URI Sanitizing?

I would like to require pages in my Node.js server based on the requested URI.
However I concern that this could be a severe security issue since user can inject some malicous chars into the url, something like ../../ and reach to my root server point and reveal all of the code.
So just like throwing a bottle of water to a big fire, I have eliminated the option to send . to the request.
This is not a silverbullet, probably :)
Maybe is there some standard/best practice/guide or keypoints about URI sanitizing in REST API based on Node.js?
Edit - here the code uses the require
// app.js
app.use(require('./services/router')(app));
// router.js middleware
function router(app) {
return function(req, res, next) {
try {
// checking for . in the url
if (req.url.indexOf(".")!=-1) cast.badRequest();
// req.url.split('/')[2] should be customers, users or anything else
require('../../resources/' + req.url.split('/')[2] + '/' + req.url.split('/')[2] + '-router')(app);
next();
} catch(err) { cast.notFound(); }
}
}
module.exports = router;
// rides-router.js (this could be users-router.js or customers-router.js)
module.exports = function(app) {
// GET ride - select a ride
app.get("/v1/rides/:id", dep.verifyToken(), require('./api/v1-get-ride'));
// POST ride - insert a new ride
app.post("/v1/rides", dep.verifyToken(), require('./api/v1-set-ride'));
app.use((req, res, next) => {
cast.notFound();
});
}
You asked how to do it safer. My recommendation is that you put all the resources in an array and run all the app.use() statements with one loop that pulls the resource names from the array at server startup.
I don't like running synchronous require() during a request and I don't like loading code based on user specified characters. Both are avoided with my recommendation.
// add routes for all resources
const resourceList = ['rides', 'products', ...];
for (let r of resourceList) {
app.use(`/${r}`, require(`./resources/${r}/${r}-router`));
}
This seems like less code and 100% safe and no running of synchronous require() during a request.
Advantages:
Fully whitelisted.
No user input involved in selecting code to run.
No synchronous require() during request processing.
All routes installed at server initialization time.
Any errors in route loading (like a missing route file) occur at server startup, not during a user request.

one node process for 2 or more different web page

I am starting using nodejs. To date I can open a web page index.html using an app.js node application.
Example from http://blog.kevinchisholm.com/javascript/node-js/making-a-simple-http-server-with-node-js-part-ii/ :
//step 1) require the modules we need
var
http = require('http'),//helps with http methods
path = require('path'),//helps with file paths
fs = require('fs');//helps with file system tasks
//a helper function to handle HTTP requests
function requestHandler(req, res) {
var
content = '',
fileName = path.basename(req.url),//the file that was requested
localFolder = __dirname + '/public/';//where our public files are located
//NOTE: __dirname returns the root folder that
//this javascript file is in.
if(fileName === 'index.html'){//if index.html was requested...
content = localFolder + fileName;//setup the file name to be returned
//reads the file referenced by 'content'
//and then calls the anonymous function we pass in
fs.readFile(content,function(err,contents){
//if the fileRead was successful...
if(!err){
//send the contents of index.html
//and then close the request
res.end(contents);
} else {
//otherwise, let us inspect the eror
//in the console
console.dir(err);
};
});
} else {
//if the file was not found, set a 404 header...
res.writeHead(404, {'Content-Type': 'text/html'});
//send a custom 'file not found' message
//and then close the request
res.end('<h1>Sorry, the page you are looking for cannot be found.</h1>');
};
};
//step 2) create the server
http.createServer(requestHandler)
//step 3) listen for an HTTP request on port 3000
.listen(3000);
But I don't know if it is possible to open different web pages that will use the same app.js. It has to be possible.But how to modify the code above?
Problem:
how to modify the code above to use app.js able to open index.html and other page web index2.html?. The content of index.html is different from index2.html but both use the same app.js
You will need a routing system to route different requests to different handlers. There are some node modules that provide you this functionality. Some of them are: Connect, Express.js, and Restify.js

How to Redirect to Single Page Web App in Express for Node

I am writing a website with a single page web app (the rest of the website is just static files which are served). I am trying to write a piece of middleware for express to redirect all requests that follow the pattern 'example.com/app' to 'example.com/app' so that requests such as 'example.com/app/my/specific/page/' will all result in the same page being sent. The key issue with this is that the url in the address bar of the browser must not change so that the javascript app itself can interpret it and display the correct thing.
I could have done something like this:
app.use( '/app', function ( req, res ) {
res.redirect('/app');
});
However, this causes the url of the page to change and a separate HTTP request is assumedly made.
The most obvious alternative solution is to do something like this:
app.use( '/app', function ( req, res ) {
res.sendfile(__dirname + '/public/app/index.html');
});
The issue here is that resources from the page after requests like 'example.com/app/my/specific/page/' will look in the wrong location. For example, if I have an image on the page such as then it will look for example.com/app/my/specific/page/image.jpg. Since no image is returned, it will not display on the page. This happens for all external scripts or stylesheets.
I also tried something like this:
app.use( '/app', function ( req, res ) {
res.sendfile(__dirname + '/public/beta' + url.parse(req.url).pathname);
});
but that was very stupid of me for obvious reasons.
In the end I used this middleware to serve the app's page when appropriate
// all unmatched requests to this path, with no file extension, redirect to the dash page
app.use('/dash', function ( req, res, next ) {
// uri has a forward slash followed any number of any characters except full stops (up until the end of the string)
if (/\/[^.]*$/.test(req.url)) {
res.sendfile(__dirname + '/public/dash/index.html');
} else {
next();
}
});
I then set used a base HTML element with the href attribute pointed to the root.
If you're still trying to accomplish this I may have found a starting point. Alexander Beletsky has a Backbone.js + Express SPA boilerplate repo Located Here.
For a brief article on how it came about you can read his article on Dzone.

Resources