I am trying to serve a static directory dynamically by adding a pathname parameter to the URL.
I am able to serve the directory just fine with the following line, which renders the html and subdirectories in the browser without having to readFile etc:
app.use('/', express.static('/Users/virtuload-beta/backend/uploads/folder/subfolder/'))
This helped for testing but I need it to be dynamic as I am getting the directory name depending on a path variable from MongoDB, and then serving the directory based on the URL.
I've tried multiple solutions, this is my current one:
app.js:
app.use('/static', express.static(path.join(__dirname, '../uploads', )), serveRouter)
routes.js:
router.get('/:id', FileCtrl.servepath);
-FileCtrl.js:
const servepath = async (req, res) => {
try {
let id = req.params.id
Upload.findById(id)
.populate('Upload')
.select('relPath') //relPath = /folder/subfolder
.exec(function(err, upload) {
if (err) {
res.send(err)
} else {
const filepath = `${upload.relPath}`
console.log(filepath) //logs folder/subfolder
//parse http object coming from client
const urlObject = url.parse(req.url, true)
console.log(urlObject)
var myUrl = new URL(`http://localhost:8000/static/${filepath}`)
return myUrl;
}
})
} catch (e) {
console.error(e)
}
}
I'm not getting any error but it's not working.
Manipulate the req.url and return next()
First your route
router.get('/:id', FileCtrl.servepath);
Controller(addednext):
const servepath = async (req, res, next) => {
try {
let id = req.params.id
Upload.findById(id)
.populate('Upload')
.select('relPath') //relPath = /folder/subfolder
.exec(function (err, upload) {
if (err) {
res.send(err)
} else {
const filepath = `${upload.relPath}`
req.url = `/static/${pathfile}/index.html`
return next();
}
})
} catch (e) {
console.error(e)
}
}
Last your static route (note: define it after all other routes)
app.use('/static', express.static(path.join(__dirname, '../uploads')), serveRouter)
Related
I would like to get the data from session variable (req.user.username) then use it for posting. I'm using passportjs as authentication. I'm using router. Here is my code:
router.use('/login', passport.authenticate("local-register", async (err, user, info) => {
if (err) {
return next('Error');
}
if (!user) {
return next('Error');
}
req.user = user;
return req.login(user, (error: Error) => {
if (error) {
return next('Error');
}
return req.session.save((erro: Error) => {
if (erro) {
return next('Error');
}
return next();
});
});
})(req, res, next);)
router.get('/', async (req, res) => {
console.log(req.user.username) // working just fine
});
router.post('/upload', async (req, res) => {
const uploaderName = req.user.username // I'm getting undefined
const upload = await database.query('INSERT INTO user WHERE username=$1', [uploaderName])
console.log(uploaderName);
})
So I finally found the answer to the question. For those who will encounter the problem in the future. You just add the session middleware AGAIN on the top of the routes. If your routes are separated to the main server file.
/src/routes/routes.ts -> add again the middleware on top.
const app = router();
app.use(sessions) // -> right here you need to add the middleware again to //access the req.user session variable
app.get('/', async (req, res) => {
console.log(req.user.username) // working just fine
});
app.post('/upload', async (req, res) => {
const uploaderName = req.user.username // I'm getting undefined
const upload = await database.query('INSERT INTO user WHERE username=$1', [uploaderName])
console.log(uploaderName);
})
I am running a simple website with Node.js, Express, and Pug/Jade. Instead of repeatedly copy pasting code fragments for every page (like below), I was hoping to use a regular expression.
app.get('/notes/document', function(req, res) {
res.status(200).render('pages/notes/document', {
});
});
Since I am organizing my directory structure nearly identical to the page structure, I thought I could take the requested URL, dissect it, and return the appropriate page. I wrote the below code using a regular expression to access all pages in the "pages/notes" directory.
app.get(/\/notes(\/*)*/, function(req, res) {
const rootDirectory = path.join(__dirname, 'pages/notes/');
var providedPath = path.join(rootDirectory, req.originalUrl.substring(1).replaceAll('-', ''));
if(!pathSecure(rootDirectory, providedPath.concat('.*'))) {
return res.status(404).send('Page could not be found.');
}
else {
fs.access(providedPath.concat('.*'), fs.F_OK, (err) => {
if (err) {
res.status(404).send('Page could not be found.');
console.error(err);
return;
}
res.status(200).render(providedPath, {
});
});
}
});
// Function used to ensure user input is secure
function pathSecure(rootDir, filename) {
// Poison null byte attack
if (filename.indexOf('\0') !== -1) {
return false;
}
// Attempting to access file outside intended directory
else if (providedPath.indexOf(rootDir) !== 0) {
return false;
}
else {
return true;
}
}
However, I realized that taking user input on the server side and providing access to my file structure may create some serious security vulnerabilities. I am a novice with this sort of stuff, so I was unsure if the method I used above is safe. Are there any alternatives that are more secure or efficient? Also, are there any other security vulnerabilities I should protect against?
Here is the full code:
'use strict';
const express = require('express');
const favicon = require('serve-favicon');
const path = require('path');
const fs = require('fs');
const app = express();
app.locals.basedir = __dirname;
app.set('views', __dirname);
app.set('view engine', 'pug');
app.use('/scripts', express.static(path.join(__dirname, '/scripts')));
app.use('/images', express.static(path.join(__dirname, '/images')));
app.use(favicon(path.join(__dirname, 'images', 'favicon.ico')));
app.listen(8000, () => console.log('Listening on port 8000.'));
app.get('/', function(req, res) {
res.status(200).render('pages/index', {
});
});
app.get('/about', function(req, res) {
res.status(200).render('pages/about', {
});
});
app.get(/\/notes(\/*)*/, function(req, res) {
const rootDirectory = path.join(__dirname, 'pages/notes/');
var providedPath = path.join(rootDirectory, req.originalUrl.substring(1).replaceAll('-', ''));
if(!pathSecure(rootDirectory, providedPath.concat('.*'))) {
return res.status(404).send('Page could not be found.');
}
else {
fs.access(providedPath.concat('.*'), fs.F_OK, (err) => {
if (err) {
res.status(404).send('Page could not be found.');
console.error(err);
return;
}
res.status(200).render(providedPath, {
});
});
}
});
app.get('/tic-tac-toe', function(req, res) {
res.status(200).render('pages/tictactoe', {
});
});
// Function used to ensure user input is secure
function pathSecure(rootDir, filename) {
// Poison null byte attack
if (filename.indexOf('\0') !== -1) {
return false;
}
// Attempting to access file outside intended directory
else if (providedPath.indexOf(rootDir) !== 0) {
return false;
}
else {
return true;
}
}
I was hoping someone can help me figure out what I'm doing wrong here. I've been searching and I can find a lot of similar issues, but nothing that I'm smart enough to solve my issue from. I'm getting the following error:
CastError: Cast to ObjectId failed for value "customers" at path "_id" for model "customer"
It was working before and I managed to break it, and I undid everything I thought I changed and I'm still getting the error.
Here is my Schema:
const Schema = mongoose.Schema;
const CustomerSchema = new Schema({
custName: {
type: String,
required: true
},
custStreet: {
type: String
},
custCity: {
type: String
},
custState: {
type: String
},
custZip: {
type: String
}
});
module.exports = Customer = mongoose.model('customer', CustomerSchema);
My Routes:
const router = express.Router();
const customerController = require('../../controllers/customer')
const Customer = require('../../models/Customer');
router.route('/')
.get(customerController.index)
.post(customerController.newCustomer);
router.route('/:customerID')
.get(customerController.getCustomer)
module.exports = router;
And my controller:
module.exports = {
index: async (req, res, next) => {
try {
const customers = await Customer.find({})
res.status(200).json(customers);
} catch(err) {
next(err);
}
},
newCustomer: async (req, res, next) => {
try {
const newCustomer = await Customer(req.body);
const customer = await newCustomer.save();
res.status(201).json(customer);
} catch(err) {
next(err);
}
},
getCustomer: async(req, res, next) => {
try {
const { customerID } = req.params;
const customer= await Customer.findById(customerID);
res.status(200).json(customer);
} catch(err) {
next(err);
}
}
};
Also, here is the whole message I get:
CastError: Cast to ObjectId failed for value "customers" at path "_id" for model "customer"
at model.Query.exec (C:\Users\natha\Desktop\Coding\HAMMER\node_modules\mongoose\lib\query.js:4371:21)
at model.Query.Query.then (C:\Users\natha\Desktop\Coding\HAMMER\node_modules\mongoose\lib\query.js:4463:15)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
Something else that is confusing me, I have another Route file and Controller for another collection in my database. It's basically the same code, except instead of routing to '/:Customers' it routes to '/:Tickets' and it works just fine. I don't see how this code is working and the Customers code is not.
Routes:
const router = express.Router();
const ticketController = require('../../controllers/ticket');
router.route('/')
.get(ticketController.index)
.post(ticketController.newTicket);
router.route('/:ticketID')
.get(ticketController.getTicket)
.put(ticketController.replaceTicket)
.patch(ticketController.updateTicket);
module.exports = router;
Controller:
module.exports = {
index: async(req, res, next) => {
try {
const tickets = await Ticket.find({})
res.status(200).json(tickets);
} catch(err) {
next(err);
}
},
newTicket: async (req, res, next) => {
try {
const newTicket = await Ticket(req.body);
const ticket = await newTicket.save();
res.status(201).json(ticket);
} catch(err) {
next(err);
}
},
getTicket: async(req, res, next) => {
try {
const { ticketID } = req.params;
const ticket = await Ticket.findById(ticketID);
res.status(200).json(ticket);
} catch(err) {
next(err);
}
},
replaceTicket: async(req, res, next) =>{
try {
const { ticketID } = req.params;
const newTicket = req.body;
const result = await Ticket.findByIdAndUpdate(ticketID, newTicket);
res.status(200).json({ Success: true });
} catch(err) {
next(err);
}
},
updateTicket: async(req, res, next) =>{
try {
const { ticketID } = req.params;
const newTicket = req.body;
const result = await Ticket.findByIdAndUpdate(ticketID, newTicket);
res.status(200).json({ Success: true });
} catch(err) {
next(err);
}
}
};
I'd appreciate any help!
Thanks
-N8
I found the problem. I had this in my server.js file:
app.use('/', customers);
app.use('/customers', customers);
app.use('/materials', materials)
app.use('/tickets', tickets)
Once I got rid of the app.use('/', customers); all is good.
You have an error in getCustomer handler.
You shouldn't use the string id to find the customer, instead, you must first use mongoose.Types.ObjectId(STRING_ID).
getCustomer: async(req, res, next) => {
try {
const { customerID } = req.params;
const customer= await Customer.findById(mongoose.Types.ObjectId(customerID));
res.status(200).json(customer);
} catch(err) {
next(err);
}
}
I upvoted the solution of the OP. To save a lot of time, I just want to explain why that is an issue and what someone else could look out for as I. So in OP server.js, '/' comes before other routes which defy the rule to list specific routes first. Meaning, if '/' was moved after other routes problem is solved.
More importantly, where does this error come from? when you call any of the other routes, your app calls '/' first and uses the customers' routes while passing the route paths (i.e. '/customers', '/materials', '/tickets') as id params and then mongo is trying to cast customer, materials, and tickets as an id inside the customer collection.
app.use('/customers', customers);
app.use('/materials', materials)
app.use('/tickets', tickets)
app.use('/', customers);
This will have solved the problem for OP.
In my case this was the problem:
router.get('/', getAllUser);
router.get('/:id', getUserById)
router.get('/facebook', facebookPassport)
router.get('/google', customersPassport);
So here I get this error Cast error for value "facebook"/"google" at path "_id" for model User because the strings, "facebook" and "google" in the last two routes are specific but will never be reached because the '/:id' route will accept any value that comes after the '/' as an id. When I rearrange like this:
router.get('/', getAllUser);
router.get('/facebook', facebookPassport)
router.get('/google', customersPassport);
router.get('/:id', getUserById)
Take away: move specific routes to the top of dynamic routes (routes with params) both at the app level and in your route files.
I need to remove an image file from my backend, the folder is: /uploads. When i call the function deleteProduct it removes the product from the data base but the image of the product its still in folder.
deleteProduct: (req, res) => {
let productId = req.params.id;
Product.findById(productId, (err, res) =>{
var imageResponse = res.image;
console.log(imageResponse);
});
//console.log(imageResponse);
//fs.unlink('./uploads' + imageResponse );
When i try to access imageResponse outside the findById, console prints: "imageResponse" is not defined. Then i need to delete that file with fs. Im not sure if i wrote correct the unlink function. Thanks in advance.
For fs.unlink
Have you made sure to:
Include fs = require('fs')?
Used __dirname?
Include file extension (.png, .jpg, .jpeg)?
const fs = require('fs');
fs.unlink(__dirname + '/uploads' + imageResponse + ".png", (err) => {
if (err) throw err;
console.log('successfully deleted file');
});
For image response being undefined
You didn't provide information on the Product constructor, but I assume Product.findById is asynchronous. You may need to use an async function
const fs = require('fs');
async function deleteProduct (req, res) => {
let productId = req.params.id;
Product.findById(productId, (err, res) =>{
var imageResponse = res.image;
console.log(imageResponse);
fs.unlink(__dirname + '/uploads' + imageResponse + ".png", (err) => {
if (err) throw err;
console.log('successfully deleted file');
});
});
}
Further reading:
Node File API: https://nodejs.org/api/fs.html
Async functions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Finally it seems to be working, the file succesfully vanished from the folder, im still open for advices, thanks.
deleteProduct: (req, res) => {
let productId = req.params.id;
Product.findById(productId, (err, res) =>{
if(err) return res.status(500).send({message: 'Error'});
fs.unlink('./uploads/' + res.image, (err) => {
if(err) return res.status(500).send({message: 'Error'});
})
});
Before I call expressApp.use(express.static(path.join(__dirname, '/../frontend/dist'))); I need to modify the html-code. What I basically need to do is inserting meta tags in two middleware functions. I figured out how to do this. But with my solution I call a middleware-functions inside another one.
app.js
let frontend = await fs
.readFileSync(path.join(__dirname, '/../frontend/dist/index.html'))
.toString('utf8');
expressApp.use((req, res, next) => {
//...
frontend = frontend.replace(
'<meta device="ABC" />',
'<head><meta device="' + deviceId + '"/>'
);
next();
});
expressApp.use((req, res, next) => {
const language = req.get('language') || 'en_GB';
logger.info('language:' + language);
this._languageModule.setLanguage(language);
frontend = this._languageModule.insertSIDs(frontend);
logger.info(frontend);
expressApp.use(express.static(path.join(__dirname, '/../frontend/dist'))); // nested middleware function
next();
});
/** set up all the express routes **/
expressApp.get('/', (req, res) => {
res.send(frontend);
});
Edit
If I don't call expressApp.use(express.static(path.join(__dirname, '/../frontend/dist'))); nested - like this:
expressApp.use((req, res, next) => {
const language = req.get('language') || 'en_GB';
logger.info('language:' + language);
this._languageModule.setLanguage(language);
frontend = this._languageModule.insertSIDs(frontend);
logger.info(frontend);
next();
});
expressApp.use(express.static(path.join(__dirname, '/../frontend/dist')));
the HTML will not be served modified.
You probably should write your own middleware that handles the modification of the files. Here's an example not tested. But it's rough. It's based on the express.static function
const fs = require("fs");
var parseUrl = require('parseurl')
app.use((req, res, next) => {
var originalUrl = parseUrl.original(req)
var path = parseUrl(req).pathname
// make sure redirect occurs at mount
if (path === '/' && originalUrl.pathname.substr(-1) !== '/') {
path = ''
}
// We only answer to GET
if (req.method !== 'GET' && req.method !== 'HEAD') {
return next()
}
let path = path;
fs.exists(path, (exists) => {
if(!exists)
{
// file don't exists skip this middleware
return next();
}
fs.readFile(path, (err, data) => {
if (err)
{
// Can't send the file skip this middle ware
return next();
}
// Do whatever you need with the file here?
// ...
// Setup mime type of the file
res.setHeader("content-type", "text/html");
// send the client the modified html
res.send(data);
});
console.log(exists ? 'it\'s there' : 'no passwd!');
});
});
For the original source please take a look at this github page:
https://github.com/expressjs/serve-static/blob/master/index.js