Several individual static directories with different paths using Express - node.js

I would like to be able to get Express to treat several directories (not just one) as "static" -- that is, if the file is there, then serve it.
Connect's static() module seems to be geared up for people who want to make files in a specific directory available in the server's root. However, that's not what I want. What I am after, is end up with something like this:
GET /modules/MODULE1 -> Return files in modules/MODULE1/public
GET /modules/MODULE2 -> Return files in modules/MODULE2/public
GET /modules/MODULE3 -> Return files in modules/MODULE3/public
I am looking at the source of static, which in turns uses send, which in turns defines SendStream, which takes the file path straight from the request (which is not what I want).
Are there easy ways to do this?
Merc.

what's wrong with
app.use('/modules/MODULE1', express.static('modules/MODULE1/public'))
for each module?

The answer is here:
https://groups.google.com/forum/?fromgroups=#!topic/express-js/kK9muR0mjR4
Basically:
var st = express.static(
__dirname + app.set('path.static'),
{ maxAge : app.set('static.expiry') }
);
app.get(/^\/static\/(v.+?\/)?(.+$)/, function (req, res, next) {
req.url = req.params[1];
st(req, res, next);
});
Basically, since Static consider req.url, it's a matter of hacking it so that it "looks right" when/if the path matches.
I asked TJ if he would add it as an option, he (rightly) answered:
this isn't something send() should do, you can already do this easily with connect/express with several static() middleware, you would just have to do the same but more manual with send()
https://github.com/visionmedia/send/issues/10#issuecomment-8225096

Related

How can I stay organized when changing asset locations used by express.js server?

I have been learning express and everything seems to be clicking into place so I am trying now to concentrate on organization and keeping things clean and intuitive. I am wondering if there is a convention for storing where certain files are stored to be accessed by my server? The idea is for example I have a directory something like this:
root/
|--html
|--html1.html
but I want to change the structure mid project to:
root/
|--assets
|--html
|--html1.html
Or for example as a site scales I decide to move my public directory to S3 or some other cloud storage instead.
Now I have to go back everywhere that sends that file and change the path or the entire middleware to send an s3 file. My solution is this, and it works, but I am not sure if it is optimal or if there is a convention that might be better.
I can store everything in an asset module like this which has methods to return a file:
function getFile(path){
return fs.readFileSync(path)
}
module.exports = {
html1: getFile(`${__dirname}/html/html1.html`),
html2: getFile(`${__dirname}/html/html2.html`,
image1: getFile(`${__dirname}/image/image1.html`
}
and import it into my server as "assets" and use it like this:
app.get('/*', (req, res, next) => {
res.set('Content-Type', 'text/html');
res.send(assets.html1);
});
Now if I want to change the path or even use an asset from s3 I can simply change the logic/function/path etc all in one place rather than going through all my routers manually etc.
Any feedback or guidance on where to look for more info is greatly appreciated!

How to ignore specific files to be loaded when I use route parameters in Express

When I make a GET request with route parameters in express with mongoose like the following code, I sometimes see that the browser tries to load some unexpected files such as favicon.ico, robots.txt, humans.txt, sitemap.xml, ads.txt, etc., and 404 error shows up in the browser console.
app.get("/:userId", ...);
By refering to this Q&A, I figured out that if I don't use the route parameters right after the root route like the following code, it doesn't happen.
app.get("/user/:userId", ...);
In the same Q&A, however, there seem to be another way that uses req.url to ignore those unexpected files to be loaded, but it isn't explained in detail.
How do you do that?
All that's meant in that other answer is that you could examine req.url in your route handler and make sure it is not a known special name. In this specific case, it's probably simpler to use req.params.userId instead of req.url, but you could also use req.url in the same way.
const specials = new Set(["favicon.ico", "robots.txt", "humans.txt", "sitemap.xml", "ads.txt"]);
app.get("/:userId", (res, res, next) => {
// if it's a special URL, then skip it here
if (specials.has(req.params.userId)) {
next();
return;
}
// process your route here
});
Personally, I wouldn't recommend this solution because it presupposes a perfect knowledge of all possible special filenames. I don't use a top level wildcards ever because they ruin the ability to use your server for anything else.

Apostrophe CMS - Protecting Static Content

I am working with apostrophe cms right now and have put the entire application behind a SAML IDP using Apostrophe-Saml. however, I have noticed that files that users upload, are placed into a 'public' directory and can be viewed without logging in. Is there a way that uploaded images/videos/files can be secured behind authentication?
The apostrophe-express module has a middleware option, but this does not appear to be used when accessing static content.
My next attempt was to override an apostrophe-assets method
self.servePublicAssets = function() {
var middleware = [];
if (self.lessMiddleware) {
// bc: only if the new implementation of enableLessMiddleware is in place.
// If it's an old override, it'll already be added to Express and
// this property won't be set
middleware.push(self.lessMiddleware);
}
//THIS NEXT LINE IS THE LINE OF INTEREST
middleware.push(self.apos.express.static(self.apos.rootDir + '/public'));
//SEE THE LINE ABOVE
self.expressMiddleware = {
// Run really early, before all of the stuff apostrophe-express normally
// puts in, for performance reasons. Preempts expensive
// queries related to `apostrophe-global` on every static file
when: 'beforeRequired',
middleware: middleware
};
};
I essentially tried to swap the 'line of interest' out for something that looks like this:
middleware.push(self.apos.app.use(self.apos.rootDir + '/public', authMethod(), self.apos.express.static(self.apos.rootDir + '/public')));
But this did not appear to work either. Does anyone have any ideas or know exactly how to do this? Thanks.
While difficult at the time the question was asked, this is now straightforward: install and configure the apostrophe-secure-attachments module.

Where should I put custom errors in sails.js?

I was wondering what's the best practice and if I should create:
a directory in which declare statically all the errors my application uses, like api/errors/custom1Error
declare them directly inside the files
or put the files directly inside the dir that needs that error, like api/controller/error/formInvalidError
other options!?
A neat way of going about this would be to simply add the errors as custom responses under api/responses. This way even the invocation becomes pretty neat. Although the doc says you should add them directly in the responses directory, I'm sure there must be a way to nest them under, say, responses/errors. I'll try that out and post an update in a bit.
Alright, off a quick search, I couldn't find any way to nest the responses, but you can use a small workaround that's not quite as neat:
Create the responses/errors directory with all the custom error response handlers. Create a custom response and name it something like custom.js. Then specify the response name while calling res.custom().
I'm adding a short snippet just for illustration:
api/responses/custom.js:
var customErrors = {
customError1: require('./errors/customError1'),
customError2: require('./errors/customError2')
};
module.exports = function custom (errorName, data) {
var req = this.req;
var res = this.res;
if (customErrors[errorName]) return customErrors[errorName](req, res, data);
else return res.negotiate();
}
From the controller:
res.custom('authError', data);
If you don't need logical processing for different errors, you can do away with the whole errors/ directory and directly invoke the respective views from custom.js:
module.exports = function custom (viewName, data) {
var req = this.req;
var res = this.res;
return res.view('errors/' + viewName, data);//assuming you have error views in views/errors
}
(You should first check if the view exists. Find out how on the linked page.)
Although I'm using something like this for certain purposes (dividing routes and so on), there definitely should be a way to include response handlers defined in different directories. (Perhaps by reconfiguring some grunt task?) I'll try to find that out and update if I find any success.
Good luck!
Update
Okay, so I found that the responses hook adds all files to res without checking if they are directories. So adding a directory under responses results in a TypeError from lodash. I may be reading this wrong but I guess it's reasonable to conclude that currently it's not possible to add a directory there, so I guess you'll have to stick to one of the above solutions.

Is this method of static file serving safe in node.js? (potential security hole?)

I want to create the simplest node.js server to serve static files.
Here's what I came up with:
fs = require('fs');
server = require('http').createServer(function(req, res) {
res.end(fs.readFileSync(__dirname + '/public/' + req.url));
});
server.listen(8080);
Clearly this would map http://localhost:8080/index.html to project_dir/public/index.html, and similarly so for all other files.
My one concern is that someone could abuse this to access files outside of project_dir/public. Something like this, for example:
http://localhost:8080/../../sensitive_file.txt
I tried this a little bit, and it wasn't working. But, it seems like my browser was removing the ".." itself. Which leads me to believe that someone could abuse my poor little node.js server.
I know there are npm packages that do static file serving. But I'm actually curious to write my own here. So my questions are:
Is this safe?
If so, why? If not, why not?
And, if further, if not, what is the "right" way to do this? My one constraint is I don't want to have to have an if clause for each possible file, I want the server to serve whatever files I throw in a directory.
No, it's not safe.
For the reason you mention.
Use fs.realpath() or fs.realpathSync() and check that the normalized path points to where you want it to.
It is not safe so use something like that:
if (req.url.indexOf('..') !== -1) {
res.writeHead(404);
res.end();
}
PS: by the way, use readFile instead of readFileSync and watch for errors... your implementation is too bad even for an example -_-

Resources