express.js unique directory for each user - javascript

I have an express.js app, and I am using drywall in order to manage the user system.
When a user signs up, I want a directory to be generated for that user, and I want that user to be able to upload files to that directory and view those files through his or her account.
I am not entirely sure, but I think that most likely for the directory generation I will have to do that inside views/signup/index.js, and that the user can only upload files to his or her directory if logged in.
However, I'm a bit stuck when it comes to saving and displaying the files. I have little experience with server side code, so implementing actions such as accessing files is slightly beyond my scope.
Thanks in advance to those who help.

So first you should create a folder for each user by using fs.mkdir:
http://nodejs.org/api/fs.html#fs_fs_mkdir_path_mode_callback
Let's say you want to create these folders into your app root / images:
Example:
var fs = require('fs');
fs.mkdir(__dirname + '/images/' + userId, function(err) {
if (err) {
/* log err etc */
} else {
/* folder creation succeeded */
}
});
You should probably use the userId for the folder name (since it's easier than trying to strip out the bad characters from the username itself, and this will also work in the future if the user changes his username).
The second thing you need to do is to allow the user to upload files (but only if he is logged in and into the right folder). It's better to not include the bodyParser middleware for all routes, but instead include the json && urlencoded middleware for all routes (http://www.senchalabs.org/connect/json.html && http://www.senchalabs.org/connect/urlencoded.html) and the multipart middleware only for the upload url ( http://www.senchalabs.org/connect/multipart.html && example: https://github.com/visionmedia/express/blob/master/examples/multipart/index.js ).
An example:
app.post('/images', express.multipart({ uploadDir: '/tmp/uploads' }), function(req, res, next) {
// at this point the file has been saved to the tmp path and we need to move
// it to the user's folder
fs.rename(req.files.image.path, __dirname + '/images/' + req.userId + '/' + req.files.image.name, function(err) {
if (err) return next(err);
res.send('Upload successful');
});
});
Note: in the example above I've taken into consideration that req.userId is populated with the id of the user by an auth middleware.
Showing the images to the user if he has the rights to see them (the auth middleware should be applied for this path as well):
app.get('/images/:user/:file', function(req, res, next) {
var filePath = __dirname + '/images/' + req.userId + '/' + req.params.file;
fs.exists(filePath, function(exists) {
if (!exists) { return res.status(404).send('Not Found'); }
// didn't attach 'error' handler here, but you should do that with streams always
fs.createReadStream(filePath).pipe(res);
});
});
Note: in production you might want to use send instead, that example was just demoing the flow ( https://github.com/visionmedia/send ).

Related

Generate static html files from express server

I've got a nodejs web project running with express-js and ejs. After making it, it appered to me that it can be host throught static html files on Netlify instead of an nodejs app on Heroku. Indeed, the data only change each month so I only have to build it once a month. Like that, it will be the best hosting option regarding the price.
Is there a way (npm package, GitHub action, ...) to compile an entire express server + ejs application into a folder of static html/css files in order to be hosted on Netlify ?
I've been looking for a while and I couldn't find anything solving my problem.
Thanks for your help.
You can loop through all of the routes, and execute app.render for each of them, then store results in a file.
Sample code:
//express server should be started before this
const fs = require('fs')
['/', '/about', '/contact'].forEach( path => {
app.render(path, {
// optional metadata here
}, (err, res) =>{
if (err)
console.log('Error rendering ' + path, err)
else {
fs.writeFile(__dirname + '/public/' + path + '.html', res, function(err, res) {
if (err)
console.log('error saving html file', path, err)
})
}
})
})

Node.js - How to hide html pages?

I have html pages which shouldn’t be seen by the users who haven’t logged in. I used the command below and my html pages became public.
app.use(express.static('public'));
For example, I do not want users who are not logged in to see this page.
http://localhost:3000/admin.html
NOTE: what I'm talking about is not a cookie. When you enter the address of the html page in the toolbar, if it is not logged in, it should not be able to reach that page.
Create a custom static middleware, with the middleware you can validate the path(filename for this case).
I will try to explain with comments in a example code:
// path.join here makes it work cross platform with Windows / Linux / etc
var statics = express.static(path.join(__dirname, 'public'));
function secureStatic(pathsToSecure = []) {
return function (req, res, next) {
if (pathsToSecure.length === 0) {
return statics(req, res, next); // Do not secure, forward to static route
}
if (pathsToSecure.indexOf(req.path) > -1) {
return res.status(403).send('<h1>403 Forbidden</h1>'); // Stop request
}
return statics(req, res, next); // forward to static route
};
}
// add public files. List all "private" paths (file)
app.use(secureStatic(['admin.html'])); // instead of app.use(express.static('public'));
But, with this middleware, no one can request to admin.html file via you express server.

Creating dynamic link by requesting data from front-end in Node.js and Express.js

This is kind of hard for me to explain but I am trying to shorten my code by creating one link instead of 50 for downloading a form. I will try to explain this a bit better using my code.
I have 5 get requests that do exactly the same thing but download a different file.
router.get('/form1', function (req, res) {
var file = __dirname + '/../public/forms/form1.pdf';
res.download(file);
});
router.get('/form2', function (req, res) {
var file = __dirname + '/../public/forms/form2.pdf';
res.download(file);
});
etc...
and my front-end link are;
FORM 1
FORM 2
etc...
Is there anything I can do to make this a more dynamic? The only way I can think of is something like this;
router.get('/:formName', function (req, res) {
// some how do a "req.params.formName"
var file = __dirname + '/../public/forms/' + req.params.formName + '.pdf';
res.download(file); // Set disposition and send it.
});
But I don't know how I will get the formName or if thats even possible.
Here are some more options to clarify:
Option 1: If you have a folder on the server with a fairly manageable directory structure, simply use express.static to map the physical folder to a virtual one with automatic download:
app.use('/download', express.static(path.join(__dirname, '/public/forms/')))
This will result in any link from the front-end with href='/download/something.pdf' working as long as that file is on the server in the path you mapped (i.e. in /public/forms).
Option 2 (which David E above answered in essence): In your original code, if you wanted to generate a path handler for a link that looks like /download/form1, /download/form2, it's a very minor modification:
router.get('/download/:formNumber', function (req, res) {
var file = __dirname + '/public/forms/' + req.params.formNumber + '.pdf';
res.download(file);
});
Option 3: You want to authenticate access to the files and potentially support multiple, complex URL schemes to a single handler that can lookup the appropriate physical path and send the file:
router.get('/download/:path[forms|images|files]/:formNumber1', fileRequestHandler);
router.get('/public/downloadFolder2/:formNumber2', fileRequestHandler);
router.get('/public/downloadFolder3/:formNumber3', fileRequestHandler);
function fileRequestHandler(req, res) {
// Check authentication here - example below from Passport
if(!req.isAuthenticated())
return res.status(401).send({err: 'Unauthorized'});
// Check which form number is supplied and map to appropriate physical file
if(req.params.formNumber1) {
// in this example, req.params.path can be one of three allowed sample values - forms or images or files
var file = __dirname + '/public/' + req.params.path + '/' + req.params.formNumber + '.pdf';
res.download(file);
} else if(req.params.formNumber2) {
// etc.
}
}
Note: Ezra Chang's point about path validity is important.
This response assumes that your route lives in index.js.
router.get('/form/:formName', (req, res, next) => {
res.download(`${__dirname}/../public/forms/${req.params.formName}.pdf`);
});
FORM 2
Be careful about your path. I don't know whether you can start at a directory, go up a level, then down again.

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

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...

Difficulty setting up a 'post' route in an express server issue

I'm attempting to validate that my client can post info to it's server. I've set up this 'route' in my Express server.
// server.js this is the server for the PvdEnroll application.
//
var express = require("express"),
http = require("http"),
mongoose = require( "mongoose" ),
app = express();
// static file directory for default routing
app.use(express.static(__dirname + "/client"));
app.use(express.urlencoded());
// create Express-powered HTTP server
http.createServer(app).listen(3000);
console.log("Server listening at http://127.0.0.1:3000/");
// set up a post route in the server
app.post("/selections", function (req, res) {
console.log("data has been posted to the server!");
});
app.post("/selections", function (req, res) {
console.log("Some data has been posted to the server from app.js");
})
The client uses this file:
var main = function () {
"use strict";
$.getJSON("../data/checkBoxesA.json", function(checkBoxTxt) {
checkBoxTxt.forEach(function (data) {
var $checkbox = "<input type ='checkbox' name = "
+ data.label + "id = 0 UNCHECKED/>";
$(".enroll_actions").append($checkbox);
$(".enroll_actions").append(' ' + data.label + "<br/>");
$(".enroll_actions").append(' ' + data.note + "<br/>");
$(".enroll_actions").append(' '+ "<br/>");
});
});
$(".comment-input").on("click", function (event) {
console.log("Hello World!");
// here we'll do a post to our selections route
$.post("selections", {}, function (response) {
console.log("Client says - We posted and the server responded!");
console.log("Response from server :", response);
console.log("STUBB1");
});
});
console.log("STUBB2");
};
$(document).ready(main);
In the Chrome console I'm getting:
POST file:///Users/*******/Projects/r_PE/app/PvdEnroll/client/html/selections net::ERR_FILE_NOT_FOUND
A path is being sought but a tutorial's example (which works!) and is structurally identical to mine(?) uses a name i.e. "selections" to establish a route between client and server.
I'm running the server on my Mac using Virtual Box and Vagrant.
Thanks for any clarification.
On restarting the server the log message is now "POST 127.0.0.1:3000/html/selections 404 (Not Found).
Okay. This is some helpful information!
Basically, your file structure is more or less this (some file names will be different, just look at the general structure):
Node.js code (including main .js file and the module.json)
client: a folder for your static content
html: a folder
index.html: the file you are currently using
Anyway, jQuery.post() is fed a relative path (as apposed to an absolute path). That means that, based on the location of where the code was, it will "guess" the location of the file.
That means that it was trying to find a route at [locahost:port]/html/selections when your server is listening at [localhost:port]/selections! You'll need to have jQuery post to /selections instead of just selections.
For more information, this blog post is helpful (relative paths work the same in Javascript/CSS). The helpful snippet:
Here is all you need to know about relative file paths:
Starting with "/" returns to the root directory and starts there
Starting with "../" moves one directory backwards and starts there
Starting with "../../" moves two directories backwards and starts there (and so on...)
To move forward, just start with the first subdirectory and keep moving forward

Categories

Resources