eta-dev / eta

Embedded JS template engine for Node, Deno, and the browser. Lighweight, fast, and pluggable. Written in TypeScript

Home Page:https://eta.js.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

2.0.0 breaks Express.js res.render views configuration

SamuelGaona opened this issue · comments

The configuration for the new version 2.0.0 of eta breaks res.render views configuration.

To Reproduce
Steps to reproduce the behavior:

  1. Use the proposed configuration
var eta = require("eta")
eta.configure({ views: "./OTHER-VIEWS-FOLDER", cache: true })
app.engine("eta", eta.renderFile)
app.set("view engine", "eta")
  1. Use res.render(templateName);
  2. It looks over the default views folder of express.js, './views'

Expected behavior
res.render should render as if it was configured with app.set("views", "./OTHER-VIEWS-FOLDER")

renderFile works just fine.

Summary

Continue using app.set("views", "./OTHER-VIEWS-FOLDER") would be fine.

Detail

When we visit / in the following code, express.js will call renderFile(this.path, options, callback).
Eta.js will then set the renderConfig.filename to this.path, so renderConfig.filename will be fine.

this.path is resolved by express.js by using the setting from app.set("views", "./OTHER-VIEWS-FOLDER").
If we use eta.configure({ views: "./views", cache: true }) instead, express.js will then use its default value to resolve this.path (since it only tells eta.js where to resolve the file, not express.js).

var express = require("express")
var app = express()
var eta = require("eta")

var eta = require("eta")
app.engine("eta", eta.renderFile)
app.set("views", "./OTHER-VIEWS-FOLDER")
app.set("view cache", true)
app.set("view engine", "eta")

app.get("/", function (req, res) {
  res.render("template", 
  req.query
  )
})

app.listen(8000, function () {
  console.log("listening to requests on port 8000")
})

There is another thing worth mentioning about the cache option. (I think its relevent to this issue.)

The following code set the cache option of eta.js, which caches templateFunc.

eta.configure({ views: "./OTHER-VIEWS-FOLDER", cache: true })

The following code set the cache option of express.js, which caches view.

app.set("view cache", true)

THEY ARE DIFFERENT.

commented

@Ching367436 app.set("views", "...") appears to be fine when configured like so but if you set up multiple directories, it seems that it won't attempt to resolve partials using all of them. This works fine in 1.14.2 but fails in 2.0.0 (Stackblitz):

index.js

const express = require('express');
const app = express();
const eta = require('eta');

app.engine('eta', eta.renderFile);

app.set('view engine', 'eta');

app.set('views', ['./views1', './views2']);

app.get('/', function (req, res) {
  res.render('template', {
    favorite: 'Eta',
    name: 'Ben',
    reasons: ['fast', 'lightweight', 'simple'],
  });
});

app.listen(3000);

views1/template.eta

My favorite template engine is <%= it.favorite %> because it is: <%= it.reasons.join(', ') %>

<%~ includeFile('./footer', it) %>

views2/footer.eta

<footer>Signed: <%= it.name %> </footer>

The error:

Eta Error: Could not find the template "./footer". Paths tried: /home/projects/node-n8sqsu/views1/footer.eta
    at EtaErr (file:///home/projects/node-n8sqsu/node_modules/eta/dist/eta.umd.js:48:17)
    at getPath (file:///home/projects/node-n8sqsu/node_modules/eta/dist/eta.umd.js:679:15)
    at includeFile (file:///home/projects/node-n8sqsu/node_modules/eta/dist/eta.umd.js:799:17)
    at Object.includeFileHelper [as includeFile] (file:///home/projects/node-n8sqsu/node_modules/eta/dist/eta.umd.js:851:31)
    at eval (eval at _0x579686 (https://noden8sqsu-el15-d7et1jf1.w-credentialless.staticblitz.com/blitz.685d3c93544a5e80be2c9e6906d68c0f4bd854cc.js:15:338547), <anonymous>:10:5)
    at tryHandleCache (file:///home/projects/node-n8sqsu/node_modules/eta/dist/eta.umd.js:759:9)
    at View.renderFile [as engine] (file:///home/projects/node-n8sqsu/node_modules/eta/dist/eta.umd.js:834:12)
    at View.render (file:///home/projects/node-n8sqsu/node_modules/express/lib/view.js:135:8)
    at tryRender (file:///home/projects/node-n8sqsu/node_modules/express/lib/application.js:640:10)
    at Function.render (file:///home/projects/node-n8sqsu/node_modules/express/lib/application.js:592:3)

@SamuelGaona @Ching367436 @gkentr thanks for giving details on this issue -- it's one that I didn't expect.

It seems to me that the above problems could be solved in two ways:

  1. Within Eta, merge the values of data.settings.views and data.settings["view cache"] with config.views and config["view cache"]. This is the behavior of 1.14.2.
    @Ching367436 you're right that Eta's cache option and Express's "view cache" option are different. Perhaps we should only merge data.settings.views and config.views, then instruct users to run both eta.configure({ cache: true }) and app.set("view cache", true).

However, if a developer passes in req.query as template data, there is a chance that malicious users could change the views option and gain access to another folder on the user's machine.

  1. Instruct users to set both Express.js config values and Eta.js config values:
app.engine("eta", eta.renderFile)
eta.configure({  views: "./views-folder", cache: true })
app.set("views", "./views-folder")
app.set("view cache", true)
app.set("view engine", "eta")

This is slightly annoying, but may be safer.

What do you all think?

The second option would be better.

If we choose the first option, an attacker could do more than just gain access to another folder on the user's machine under certain circumstances.
For example:
If an attacker could control files on the server (like file upload functionality), the attacker could possibly perform server-side template injection.
In @gkentr's example above, if we change app.get('/', .) to the following and assume that the app has file upload functionality, the attacker could upload footer.eta to THE_UPLOAD_FOLDER.
Then when visiting /?reasons[0]=&settings[views][0]=THE_UPLOAD_FOLDER, THE_UPLOAD_FOLDER/footer.eta will be included (server-side template injection!).

app.get('/', function (req, res) {
  res.render('template', 
    req.query
  );
});

Server-side template injection could sometime lead to remote code execution.
I haven't tested out if server-side template injection in Eta could lead to remote code execution yet, maybe it could.


Update: Server-side template injection in Eta could lead to remote code execution.

@Ching367436 I think you're right. I'll update the docs to update recommended ExpressJS usage.