How to properly serve private static files with Node.js? - javascript

I would like to protect images which are uploaded. When users upload images, they are saved to the following path:
public/dogs/{userId}/{imageName}
Each user has it's own directory where images are saved. I've figured out I can easily serve those images if I do something like this but the images are then accessible by everybody:
app.use(express.static('public'));
My user validation works in the way that I check does the request url contains /api/admin in the path. Example:
router.get('/api/admin/dogs', dog.getAll);
I would like to serve the images in the similar way to just allow admin to access them. Is it possible to have something like this:
router.get('/api/admin/dogs/images/:userId/:imageName', image.getOne);
Thank you for your help.

There is a sendFile function available on response object. You can use it to send the file based on you user validation.
It would be something like this:
router.get('/api/admin/dogs/images/:userId/:imageName', function(req, res, next){
currentUserHasAccessTo(userId, imageName, function(err) {
if (err)
next(err)
else
res.sendFile(`public/dogs/${userId}/${imageName}`)
})
});
currentUserHasAccessTo is your validation function that would query a database or something else.
If an error is returned, it will be passed to the error handler middleware so that it can show a default image or an error page.

you can also use stacking of handlers:
router.get('/api/admin/dogs/images/:userId/:imageName', function(req, res, next){
currentUserHasAccessTo(userId, imageName, function(err) {
if (err)
next(err); // or res.send(401) if unauthorized
else
res.next() ; // this will "jump" to the express.static below
})
, express.static('public') });
In this case, you have two handlers for a single route. The second one will be reached only if next() is called...

Related

How to disable access to certain parts of an express server but be able to enable it later

I have a server that is fully functioning, but I only want it to be accessable when I say. I do this via a discord bot which works fine. I currently have a boolean variable server_on and an if (server on) { do } in all of my app.get and app.post functions. Is there a cleaner way to do this, or is this if statement in every function the only way?
Edit:
Final working code
var block_server_middleware = function (req, res, next) {
if (!server_on) { res.send("server is currently unavailable") }
else { next() }
}
app.use(block_server_middleware)
and the other app.get and app.post functions were not changed at all
This was the only few lines added that made the whole idea work
You can define one middleware function that goes before all your routes are defined:
app.use((req, res, next) => {
if (!server_on) {
// stop all routing
res.send("server not on!");
} else {
// let routing continue
next();
}
});
This will keep your routes from ever getting run until server_on is back to true. If you have any routes you want to leave open all the time, then just place them before this middleware.
You can replace res.send("server not on!"); with whatever is appropriate for your use. You can return a single web page or you can send back a 4xx or 5xx error status (perhaps a 503 error).

Node.Js webserver won't access data from mongodb

I just went through a tutorial and made a simple restful api. Afterwards, I added an admin user to my database. When I run the server, the authentication works, but if I try to access data via the api from the browser I get the eternal "waiting for response from localhost". I'm not sure what else I need to do for the request to be processed now that I've included a user authentication in mongodb.
var MongoClient = require('mongodb').MongoClient;
MongoClient.connect("mongodb://admin:password#localhost/bookstore?authSource=admin");
app.get("/", function(req, res){
res.send("Hello World");
});
When going to localhost:3000/ I see hello world, but localhost:3000/api/books won't return anything
app.get("/api/books", function(req, res){
Genre.getBooks(function(err, books){
if(err){
throw err;
}
res.json(books);
});
});
Here's the getBooks function
module.exports.getBooks = function(callback, limit){
Book.find(callback).limit(limit);
};
You're not in any way handling the callbacks from Node correctly. You also haven't defined what Genre or Book is in your answer.
That said, a couple notes. First, if you are in a callback, you never want to throw an error (as in don't use the syntax throw err), instead you want to either handle it or pass it back to your calling code.
A common approach I use would be like this:
app.get("/api/books", function(req, res, next){ // note adding next here
Genre.getBooks(function(err, books){
if(err){
return next(err);
}
return res.json(books);
});
});
// sometime later, a generic error middleware. You can make yours more useful
app.use((err, req, res, next) => {
return res.status(500).send({ message: 'An error has occurred' });
});
Your mongoclient connection and getBooks syntax are also probably wrong, but I can't advise you the best way to fix it without knowing if you're using Mongoose or if you're doing something else.
Ok, so since you're using Mongoose, you should remove the MongoClient code you have. Instead, add at app startup:
const mongoose = require('mongoose');
mongoose.connect('your connectionstring here');
and your getBooks should look like this:
module.exports.getBooks = function(callback, limit){
Book.find({}).limit(limit).exec(callback);
};
As a sidenote, node apps typically use the callback as the final argument in a parameter list, so you might want to change the function signature to (limit, callback) and adjust the calling code accordingly.
I figured out the problem. I didn't have mongoose properly set as the client, so when referencing the exported code for finding the book data nothing happened.

URL rewriting not working in Express 4.14.1

I've found in quite a few SO posts that in order to rewrite a URL in Express 4 I would do something like the following:
router.use('/one/:someId', (req, res, next) => {
req.url = `/two/${req.params.someId}`;
next();
});
router.get('/one/:someId', (req, res) => {
res.send("reached /one/:someId");
});
router.get('/two/:someId', (req, res) => {
res.send("reached /two/:someId");
});
But when I try this, not only does the URL does not change to my expected "/two/some integer" and stays being "/one/some integer" but it gets to the 404 - Not Found page I have set up in my app file.
This routes are in a router file and I have also tried setting the URL to:
req.url = `/routerPath/two/${req.params.someId}`;
but the result is exactly the same.
So what could I be missing?
Thank you.
You have to distinguish two kinds of redirects:
Internal redirects work on the server, without the client noticing. They are a convenience for your server programming and never necessary - you could always introduce a helper method that gets called by all endpoints.
HTTP redirects advise the client (e.g. a web browser) to go to a different URL. Since you expect the URL to change, that's the one you want.
Simply call res.redirect, making sure to encode special characters:
router.get('/one/:someId', (req, res) => {
res.redirect(`/two/${encodeURIComponent(req.params.someId)}`);
});
router.get('/two/:someId', (req, res) => {
res.render("reached /two/:someId");
});

undefiend function using connect-flash middleware

In my app.js
app.use(flash());
app.use(function (req, res, next) {
res.locals.messages = require('express-messages')(req, res);
next();
});
and in my jade file I have
!= messages()
I open the page it says undefiend is not a function function. I already restarted my server, I don't know what's wrong.
If the != messages() causes the problem then the error must occur on the server while rendering the jade file. The jade compiler is povided with a data object that contains a message method.
This message method is provided with res.locals.messages and set with the express-messages call. From all I know up to now I would asume that you positioned the code above after you configured the route to page. Then the page is rendered before you set the message method. In that case reorder the app.use calls, so the route is defined at last.
Hope that help!

Express Middleware to populate a Jade variable for all app.get()'s

I have a Jade file that all of my templates extend called layout.jade. In it I want to be able to have a logout button if the user is currently logged in (this is kept track of in req.session).
So layout.jade will have something like,
-if (loggedin)
a.navButton(href="/logout") Log Out
The route for a page would look something like,
app.get("/foo", function(req, res) {
res.render("foo", {loggedin: req.session.isValidUser});
});
The thing is, I don't want to have to populate the loggedin variable manually in every single route. Is there a way I can use Express middleware to automatically set some default options for the object sent to res.render? Or is there a better method to do this?
Basically I'm asking how I can have some set of variables always sent to templates, as well as the ability to have certain custom variables available in certain templates by manually setting them in the routes.
It seems this is actually a documented feature I just had trouble finding, unless anyone has a better way of doing it; From the latest Express documentation,
app.locals: Application local variables are provided to all templates
rendered within the application. This is useful for providing helper
functions to templates, as well as app-level data.
So in my login success function has,
req.session.username = profile.username;
app.locals.username = profile.username;
My logout function,
app.get('/logout', function (req, res) {
delete app.locals.username;
req.session.destroy();
res.redirect('/login');
});
And finally in layout.jade/all of my templates,
- if(username)
a.navButton(href="/logout") Logout
If you set res.locals.loggedin in the /login route, as hexacyanide suggests, this local will not be available in the route for /foo. res.locals is cleared upon every request.
you could instead try placing this above other routes:
app.all('*', function(req, res, next){
if(req.user){
res.locals.loggedin = true;
res.locals.currentuser = req.user;
};
next();
});
Pretty sure that if you modify req.user during your route, the res.locals.currentuser that you set before won't updated to be the new req.user. but not certain about that.
I actually use a custom render function for each page where I render a template, it looks like this:
function myRender(templateName){
return function(req, res){
res.locals.currentuser = req.user || null;
res.render(templateName);
};
};
and I use it like this:
app.get('/foo'
, function(req, res, next){
res.locals.bar = req.query['bar'] || "";
console.log('passing request to myRender middleware');
next();
}
, myRender('foo-jade-template')
)
This has the advantage of only setting res.locals.currentuser when I am ready to render something, instead of before executing my route. So if I change req.user it is guranteed to have the most recent version at render time.
There is a line of code that is rather useful to you in the Express source:
// merge res.locals
options._locals = self.locals;
Therefore, when you run res.render(), Express will also take any locals that are stored in res.locals and pass them into the render. Therefore, all you have to do is set res.locals.loggedin to true, and then run res.render() as usual.
app.get('/login', function(res, res) {
res.locals.loggedin = true;
});
app.get('/foo', function(req, res) {
res.render('foo', {});
});

Categories

Resources