Как протестировать конечные точки, защищенные csrf в node.js/express

Я реализовал защиту csrf (кросс-сайт-подделку) в выражении вроде:

...
app.use(express.csrf());
app.use(function (req, res, next) {
  res.cookie('XSRF-TOKEN', req.csrfToken());
  next();
});
...

Это отлично работает. Angularjs использовал токен csrf во всех запросах, сделанных через службу $http. Запросы, которые я выполняю через мое приложение angular, работают отлично.

Моя проблема заключается в тестировании этих конечных точек api. Я использую mocha для запуска своих автоматических тестов и модуля запроса для тестирования конечных точек api. Когда я делаю запрос к конечной точке, использующей csrf (POST, PUT, DELETE и т.д.) С использованием модуля запроса, он терпит неудачу, хотя он правильно использует файлы cookie и т.д.

Есть ли у кого-нибудь другое решение? Кому-нибудь нужна дополнительная информация?

Пример теста:

function testLogin(done) {
  request({
    method: 'POST',
    url: baseUrl + '/api/login',
    json: {
      email: '[email protected]',
      password: 'mypassword'
    } 
  }, function (err, res, body) {
    // do stuff to validate returned data
    // the server spits back a 'FORBIDDEN' string,
    // which obviously will not pass my validation
    // criteria
    done();
  });
}

Ответ 1

Фокус в том, что вам нужно обернуть свой тест POST внутри GET и проанализировать нужный токен CSRF из файла cookie. Во-первых, это предполагает, что вы создаете Angular -совместимый CSRF файл cookie следующим образом:

.use(express.csrf())
.use(function (req, res, next) {
  res.cookie('XSRF-TOKEN', req.session._csrf);
  res.locals.csrftoken = req.session._csrf;
  next();
})

Затем ваш тест может выглядеть так:

describe('Authenticated Jade tests', function () {
  this.timeout(5000);

  before(function (done) {
    [Set up an authenticated user here]
  });

  var validPaths = ['/help', '/products'];

  async.each(validPaths, function (path, callback) {
    it('should confirm that ' + path + ' serves HTML and is only available when logged in', function (done) {
      request.get('https://127.0.0.1:' + process.env.PORT + path, function (err, res, body) {
        expect(res.statusCode).to.be(302);
        expect(res.headers.location).to.be('/login');
        expect(body).to.be('Moved Temporarily. Redirecting to /login');

        var csrftoken = unescape(/XSRF-TOKEN=(.*?);/.exec(res.headers['set-cookie'])[1]);
        var authAttributes = { _csrf: csrftoken, email: userAttributes.email, password: 'password' };

        request.post('https://127.0.0.1:' + process.env.PORT + '/login', { body: authAttributes, json: true }, function (err, res) {
          expect(res.statusCode).to.be(303);

          request.get('https://127.0.0.1:' + process.env.PORT + path, function (err, res, body) {
            expect(res.statusCode).to.be(200);
            expect(body.toString().substr(-14)).to.be('</body></html>');

            request.get('https://127.0.0.1:' + process.env.PORT + '/bye', function () {
              done();
            });
          });
        });
      });
    });

    callback();
  });
});

Идея состоит в том, чтобы на самом деле войти в систему и использовать сообщение с токеном CSRF, который вы получаете из файла cookie. Обратите внимание, что в верхней части файла теста мокко вам нужно следующее:

var request = require('request').defaults({jar: true, followRedirect: false});

Ответ 2

то, что я делаю, выставляет токен csrf только в непроизводственном:

if (process.env.NODE_ENV !== 'production') {
  app.use('/csrf', function (req, res, next) {
    res.json({
      csrf: req.csrfToken()
    })
  })
}

то пусть это будет первый тест и сохранит его как глобальный. вам придется использовать агент в своих тестах, чтобы вы последовательно использовали один и тот же сеанс.

Ответ 3

@dankohn отличный ответ был наиболее полезен. С тех пор ситуация немного изменилась, касаясь как supertest, так и csurf. Поэтому, помимо этого ответа, я обнаружил, что в POST необходимо передать следующее:

  it('should ...', function(done) {
    request(app)
      .get('/...')
      .expect(200)
      .end(function(err, res) {
        var csrfToken = unescape(/XSRF-TOKEN=(.*?);/.exec(res.headers['set-cookie'])[1]);
        assert(csrfToken);
        request(app)
          .post('/...')
          .set({cookie: res.headers['set-cookie']})
          .send({
            _csrf: csrfToken,
            ...
          })
          .expect(200)
          .end(done);
      });
  });