How can I access secure files with express? - express

nodeapp
-public
-CSS
-style.css
-pictures
-secretImage.png
-views
-index.hbs
-login.hbs
-profile.hbs
-server.js
const staticFiles = path.join(__dirname, './public')
app.use(express.static(staticFiles))
app.set('view engine', 'hbs')
I'm keeping my css in the public directory accessed in html like this:
href="/css/style.css"
which is fine, but I need to store some pictures that should only be available to users that are logged in. If my pictures are in the pictures folder, how can I access them?

You can use the sendFile method...
app.get('/picture/:pictureName', (req, res) => {
const valid = /* Do your logic to grant access */
if (valid === false) {
return res.status(403).send('Not allowed')
}
res.sendFile('your file path')
})

Related

Set content-type to XHTML in static files

I want to have a route for my static files:
// server.js
app.use('/', require('./routes/ui/templates'));
The thing is that I cannot change the content-type from html->xhtml. Here's my route:
const express = require('express');
const router = express.Router();
// Path configs
const pathRoot = __dirname
const pathPublic = pathRoot + "/../../public/"
router.use('/', express.static(pathPublic));
router.get('/', (req, res) => {
console.log(pathPublic)
res.sendFile('index.html', {root: pathRoot});
})
router.use((req, res, next) => {
res.type('application/xhtml+xml');
next();
})
module.exports = router;
Note that for some reason, if I don't add the router.use(...)
my index file is not served at all. From what I understand, the middleware I've
written should be last as I am trying to capture the response and modify it.
Please correct me if I am wrong.
If you want to manage the Content-Type for specific types of files sent by express.static(), you can use the setHeaders option like this:
app.use(express.static(path.join(__dirname, "public"), {
setHeaders: function(res, path, stat) {
// if file is a .xml file, then set content-type
if (path.endsWith(".xml")) {
res.setHeader("Content-Type", "application/xhtml+xml");
}
}
}));
Some other things you may also be asking about:
Once your express.static() route matches a file, no further routing is done. The response is sent and none of the route handlers that follow will be called. So, you can't impact the content-type elsewhere with later routes.
If the request route path is /, then express.static() will look for an index.html file in the pathPublic you're passing it. If it finds it, it will send that and no further routing will happen.
res.type() does not do what you seem to be trying to use it for. You pass it a file extension and it sets the content-type according to a mime lookup for that file extension. As you can see in my code example above, you can set the content type yourself with res.setHeader("Content-Type", "application/xhtml+xml").
Try res.setHeader('content-type', 'application/xhtml+xml');

Multiple Errors serving HTML pages with Express

I'm trying to route pages using Express, but I can't seem to route from my main page. I'm using HTML as the view engine and I feel like everything is set up properly, but I'm seeing the error:
"TypeError: path must be absolute or specify root to res.sendFile"
Here is my file structure:
root
-public
--images
--index.html
--main.css
--sponsors.html
--team.html
app.js
I've tried multiple file routes, res.render versus res.sendFile.
var express = require("express")
var app = express()
const port = 3000
var http = require("http")
const path = require('path')
app.set("view engine", "html")
app.listen(port, () => console.log(`Example app listening on port
${port}!`))
app.use(express.static(__dirname + '/public'));
app.get('/sponsors', function(req, res) {
res.sendFile('sponsors')
})
app.get('/', function(req, res) {
res.sendFile('index')
})
app.get('/team', function(req, res) {
res.sendFile('team')
})
To render from a view engine, use res.render(), not res.sendFile().
And, your view engine will need to be able to find a file with that name and an appropriate file extension in the path for the view engine.
If you want to use res.sendFile() without the view engine, then you must specify an actual filename or include the root option that tells it where to look. You must also use the actual file extension on the filename.
Remove the following route method.
// remove the following code
app.get('/', function(req, res) {
res.sendFile('index')
});
Update the default path to the static method you had already written.
app.use('/', express.static(__dirname + '/public'));

Express: serve static files on subroute

I'm trying to get routing work using Express and create-react-app.
My goal is to address the user to the homepage of the application when the URL is / and to the login page when the URL matches /login.
In my server.js I have two routes defined:
var mainRoutes = require("./routes/mainRoutes");
var apiRoutes = require("./routes/apiRoutes");
[...]
app.use("/", mainRoutes);
app.use("/api", apiRoutes);
While apiRoutes contains all the api routing definitions, mainRoutes is responsible for the main navigation (at least this was the idea):
var express = require("express");
var path = require("path");
let router = express.Router();
router.route("/").get((req, res, next) => {
res.sendFile("index.html", { root: "./client/build/" });
});
router.route("/login").get((req, res, next) => {
res.send("This is the login page");
});
module.exports = router;
Somewhere I read about serving the static asset generated by the building process of create-react-app so I added:
// Priority serve any static files.
app.use(express.static(path.join(__dirname, "client/build")));
// All remaining requests return the React app, so it can handle routing.
app.get("*", function(req, res) {
res.sendFile(path.join(__dirname + "/client/build/index.html"));
});
Adding these lines, I successfully see my index.html but I can't visit both /login and /apisubroutes since it redirect me on the main page (index.html) each time.
It's like I need to serve the static files on my subroute mainRoutes but I don't have an idea on how to do that.
How can I make this work?
app.get('*') would match every single route that you have.
You should do something like this:
var mainRoutes = require("./routes/mainRoutes");
var apiRoutes = require("./routes/apiRoutes");
[...]
app.use(express.static(path.join(__dirname, "client/build")));
app.use("/", mainRoutes);
app.use("/api", apiRoutes);
// If the app reaches this point, it means that
// the path did not match any of the ones above
app.use(function(req, res, next){
// redirect the user to where we serve the index.html
res.redirect('/');
});
create-react-app I believe handles routing different, you cannot hook up the browser's route to the route you want to serve because you're running a single page application", unless you do universal routing with server and the js bundle

Serve gzip html page in node

I use webpack-compression-plugin ta compress all my static files and hml files beforehand to gzip and brotli format. If browser supports it I use brotli, if not gzip and last option is original file. So I would have something like this for example after bundling.
bundle.js
bundle.js.gz
bundle.js.br
On server I use express-static-gzip to serve static files and everything is working fine. All my client static assets are compressd and served like that.
import expressStaticGzip from 'express-static-gzip'
const app: Express = new Express()
process.env.PWD = process.cwd()
app.set('view engine', 'ejs')
app.set('views', path.join(process.env.PWD + '/src/server/views'))
app.use(expressStaticGzip(path.join(process.env.PWD + '/src/dist'), {indexFromEmptyFile: false, enableBrotli: true, maxAge: '1y'}))
app.use((req, res, next) => {
res.set('Cache-Control', 'no-cache')
return next()
})
/* Use server side rendering for first load */
app.use(appRenderer)
// Routes
app.get('*', (req, res) => {
res.render('index')
})
app.listen(PORT, () => {
console.log(`
Express server is up on port ${PORT}
Production environment
`)
})
The problem I have is with my html file, root. Although I also have gzip and br version of it, it is not served like that. I make it by bundling server side code. Express compression module doesn't work and I also want static compression. I am not using nginx.
With the help of this plugin and as was suggested here I got it working
My code:
Ensure that you've pre-gzipped .js and .css files
const checkForHTML = req => {
const url = req.url.split('.');
const extension = url[url.length -1];
if (['/'].indexOf(extension) > -1) {
return true; //compress only .html files sent from server
}
return false;
};
var compress = require('compression');
app.use(compress({filter: checkForHTML}));
const encodeResToGzip = contentType => (req, res, next) => {
req.url = req.url + '.gz';
res.set('Content-Encoding', 'gzip');
res.set('Content-Type', contentType);
next();
};
app.get("*.js", encodeResToGzip('text/javascript'));
app.get("*.css", encodeResToGzip('text/css'));
I wanted compression to happen only for .html because I'm using .ejs template, so need to compress .html on runtime. Compressing static files(js/css) using express compression isn't good idea because it will do it on every request and those are static files.
Or else, cache your results as suggested here
Other solution using nginx, as you posted in your comments also seems nice.

express-subdomain won't render views, falls through to main domain

I'm using express-subdomain 1.0.5 with express 4 to route a subdomain for my site. I have localhosts set up and that's working fine. In my main application.js, I have this:
//views
app.engine('ejs', engine);
app.set('views',__dirname + '/views');
app.set('view engine', 'ejs');
//subdomains
var developerRoutes = require('./developerRoutes');
app.use(subdomain('test-developer', developerRoutes));
// main routes
var routes = require('./routes');
app.use(routes);
The developerRoutes file looks like this:
var developer = require('../src/developer');
var devRouter = require('express').Router();
devRouter.get('/', developer.developerHome);
module.exports = devRouter;
and developer looks like this (I expect to have ~10 routes so I'm using a separate file):
exports.developerHome = function(req, res) {
console.log('hi developer got here') //this fires, so I know this route is being called
res.render('developerHome', {
data: {
testData: 'hi'
}
});
}
When I try test-developer.localhost.dev, it 404s (and developerHome.ejs exists). When I try any other route that exists for the main domain but is not present in the developer routes file (e.g. test-developer.localhost.dev/about), it renders the main view. If I try a plain res.send('hi!'); in developer.js, that renders fine.
Is there some ordering of the middleware that I'm missing? I've tried putting the view engine lines both before and after the main and subdomain lines, with no change. Is there any way to make the subdomain routes fall through to the express errorhandling middleware instead of (as it seems) the main routes?
Editing to add -- If I force status and send HTML as below it works, but why?
exports.developerHome = function(req, res) {
console.log('hi developer got here')
res.render('developer/developerHome',
{data:
{
testData: 'hi'
}
},
function(err, html){
if(err) console.log(err);
res.status(200).send(html);
}
);
}