Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Express Handlebars Won't Render Data

I am working with NodeJS and Express using Express-Handlebars template engine.

Handlebars is throwing the following error when trying to render a template:

Handlebars: Access has been denied to resolve the property "username" because it is not an "own property" of its parent. You can add a runtime option to disable the check or this warning: See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details

According to the above link:

From version 4.6.0 on, Handlebars forbids accessing prototype properties and methods of the context object by default. The reason are various security issues that arise from this possibility.

My app.js contains the following:

const exphbs = require('express-handlebars');
const express = require('express');
// Init Express
const app = express();
// VIEW ENGINE
app.engine('handlebars', exphbs({
  defaultLayout: 'main'
}));
app.set('view engine', 'handlebars');

My route file fetches from MongoDB via Mongoose:

//@GET - View
router.get('/', authMiddleware, (req, res, next) => {
  // Mongoose
  Model.find({ user: req.user._id })
    .sort({ date: -1 })
    .then(model => {
      res.render('/overview', { model: model })
    })
    .catch(err => {
      if (err) throw err;
      req.flash('error_msg', 'No Model Found');
    })

})

model is an array of objects

This issue only started happening after I began to mess around with adding handlebar helpers. I have removed the helpers and reverted to my original configuration settings (above) to no avail. I've tried deleting node_modules folder and reinstalling npm.

What's being sent is an array of objects, and I am trying to loop over the properties of the objects using the {{#each model}} helper and reference the individual properties via {{prop1}} within the each.

According to Handlebars, this disabling of proto properties is false by default, and this change shouldn't break anything.

My question:

  1. Am I sending data to handlebars incorrectly? If so, what is the correct method (not exposing my server to security holes) to send data to the express-handlebars template for rendering?

Thank you in advance.

like image 828
CoatCat Avatar asked Jan 15 '20 14:01

CoatCat


2 Answers

I had the same issue when I upgraded the handlebars package. To get your system back online as quick as possible remove the handlebars entry in package.json And then insert this line in its place.

"handlebars": "4.5.3",

From version 4.6.0 onward Handlebars forbids accessing prototype properties and methods of the context object by default. This is related to a security issue described here: https://mahmoudsec.blogspot.com/2019/04/handlebars-template-injection-and-rce.html

Refer to https://github.com/wycats/handlebars.js/issues/1642

An example from the above URL shows the Mongoose response converted into JSON:

app.get('/test', function (_req, res) {
    Kitten.find({}).then(kittens => {
        res.render('test.hbs', {
            kittens: kittens.map(kitten => kitten.toJSON())
        })
    })
});

If you are certain that only trusted developers and no end users have access to the handlebars templates it's possible to allow prototype access by installing the following package:

npm install @handlebars/allow-prototype-access

Here is an example of it's use:

const express = require('express');
const Handlebars = require('handlebars')
const expressHandlebars = require('express-handlebars');
const {allowInsecurePrototypeAccess} = require('@handlebars/allow-prototype-access')

const app = express();

app.engine('handlebars', expressHandlebars({
    handlebars: allowInsecurePrototypeAccess(Handlebars)
}));
app.set('view engine', 'handlebars');
...

Another option is to use the mongoose .lean() function. This has the benefit of being much faster than a traditional mongoose query. But it does have some cons as well. By default, Mongoose queries return an instance of the Mongoose Document class. These objects contain a lot of internal state for change tracking and have additional methods such as .save(). Enabling the lean option tells Mongoose to skip instantiating a full Mongoose document and just return the plain javascript object.

like image 151
Andrew Taylor Avatar answered Sep 22 '22 10:09

Andrew Taylor


Correct! I used to work with Sequelize and toJSON() did the trick.

If you tried it already and it didn't work, I think the same result in Mongoose could be achieved by using lean – mas 2 hours ago

I added .lean between .sort() and .then(), This worked!

like image 27
CoatCat Avatar answered Sep 22 '22 10:09

CoatCat