Hoshi
Hoshi

Reputation: 607

How to keep Express middleware sub-stack attached to specific route?

I have a subsite in my app that allows editing and serving of some JSON. The editor is served as a static resource and there are GET and POST routes for the data, used both by the editor and external consumers.

Since I only want admins to modify the data, but everyone to view, I wanted to secure both the '/' and POST routes but not the GET route. To do this, I expected that using a middleware sub-stack in each route spec would be the proper solution. So, there's a common auth.js file and its exported function is specifically included in the 2 secured routes, but omitted in the GET.

The problem is, when I had the code for the secured '/' route above the code for the free-to-all GET route, I was getting the authentication challenge for the GET route! Now, I know that generally, app.use middleware will work in the order defined, but I was under the notion that a sub-stack specific to a route was ONLY used for that route.

One thing this might illuminate: does Express start calling middleware while parsing the request path? I mean, if I have a route /jfile/jones/tamari/ is it going to call the / route handler, then the /jfile route handler then the /jfile/jones route handler and then the /jfile/jones/tamari route handler? Shouldn't it match routes in their entirety and not bit-by-bit and just call the most-specific handler for any request?

The easy solution was: "just put the authenticated routes after the free route" - which yeah, works, but WHY? Why is the sub-stack middleware leaking out to other routes? What other issues will this cause later?

Here's some code:

auth.js

const pass  ='Basic WW91IGFyZSBzbyBuYXVnaHR5IQ==';

module.exports.isAuth = function(req, res, next) {
  if (  req.headers.authorization !== pass ) {
    res.status(401).send('Unauthorized');
  } else {
    next();
  }
}

jfile.js

const fs = require('fs');
const path = require('path');
const busboy = require('connect-busboy');
const auth = require('./auth.js');
const dataFolder = './data';
const webPath = '/jfile';

app.use(busboy());  // handles POST data

function setNoCache(res, path) {
  res.setHeader('Cache-Control', 'max-age=0');
  res.setHeader('Expires', 0);
}

const fname = 'jason.json';

app.use('/', auth.isAuthorized, static('./public',
  { cacheControl: true, setHeaders: setNoCache })
);


app.get(webPath, function (req, res) {
  var download = false;

  if (typeof req.query.download != 'undefined') {
    download = true;
  }

  var file = path.join(dataFolder, fname);

  setNoCache(res);
  // Since this route deals only with 1 type of file, we set a static public name for it
  // This also masks the date part of our manual version control system. :-\
  if (download) {
    res.setHeader('Content-Disposition',`Attachment;filename=${fname}`);
  }

  res.type('json');

  try {
    var stream = fs.createReadStream(file);
    stream.pipe(res);
  } catch (err) {
    res.status(500).send(JSON.stringify(err));
    return;
  }

});


app.post(webPath, auth.isAuthorized, function (req, res) {
  var newFileName = futil.newFileIn(dataFolder);
  if(!req.busboy) {
    res.status(500).send('Missing library');
    return;
  }
  req.busboy.on('file', function (field, file, filename) {
    file.pipe(fs.createWriteStream(filename));
  });
  busboy.on('finish', function() {
    res.writeHead(303, { Connection: 'close', Location: '/' });
    res.end();
  });
  req.pipe(busboy);
});

Upvotes: 0

Views: 543

Answers (1)

Mukhammadsher
Mukhammadsher

Reputation: 192

if I have a route /jfile/jones/tamari/ is it going to call the / route handler?

Yes, because app.use('/', ...) matches all rotes.

If you use app.use('/jfile', ...) the middleware will be called to all requests which starts with /jfile

Use your middleware only in routs you want to prevent from unauthorized access

Example

app.get(path, yourMiddleware, callBack) only this rout will be using the middleware

Code Example

auth.js

module.exports = function(req, res, next) {
  if (req.headers.token) {
    // verification of token 
    next();
  } else {
    res.send({ status: 401, message: "Unauthorized" });
  }
};  

index.js

const auth = require("./middleware/auth");

app.get("/", auth, function(req, res) {
  res.send({ status: 200, message: "With auth" });
});

app.post("/jfile", auth, function(req, res) {
  res.send({ status: 200, message: "With auth" });
});

app.get("/jfile", function(req, res) {
  res.send({ status: 200, message: "Without auth" });
});

Upvotes: 0

Related Questions