如何在启用 CSRF 安全性的情况下测试 Sails.js v1.0 登录控制器(使用 mocha、supertest)?

How can I test the Sails.js v1.0 login controller with CSRF security enabled (using mocha, supertest)?

我有一个几乎全新的 Sails.js 1.0.2 应用程序,我可以使用浏览器和 Postman 登录。但是,我似乎无法在我的测试运行程序中执行相同的过程。

下面的测试应该会导致成功登录,其中会返回一个带有新会话 ID 的 cookie。如果我将安全配置更改为禁用 CSRF protection,它会完美运行。但是启用安全性后,该请求将被禁止 (403)。我在 Postman 中发送的内容之间的唯一实质性区别似乎是 mocha 在不同的端口上运行应用程序(Postman 发送到 localhost:1337,express' res 变量说 PUT /api/v1/entrance/login HTTP/1.1 Host: 127.0.0.1:56002

有人看到我遗漏了什么吗?

这是测试文件:

/**
 * /test/integration/controllers/entrance/login.test.js
 */

'use strict';

const supertest = require('supertest');  // also tried supertest-session

describe('Entrance controllers', () => {

  describe('/api/v1/entrance/login', () => {

    before(() => {
      return supertest(sails.hooks.http.app)
      .get('/login')
      .then(res => {
        const reTokenCapture = /_csrf:\s*unescape\('([^']+)'\)/;
        const found = reTokenCapture.exec(res.text);
        this._csrf = sails.config.security.csrf ? found[1] : '';
        this.url = '/api/v1/entrance/login';
      });
    });

    it('should return a session cookie in response headers', () => {
      return supertest(sails.hooks.http.app)
      .put(this.url)
      .set('x-csrf-token', this._csrf)
      .send({
        emailAddress: 'admin@example.com',
        password: 'abc123',
        // _csrf: this._csrf,  // I tried this too; no luck
      })
      .expect(200)  // if sails.config.security.csrf is enabled, status is 403
      .then(res => {
        // console.log('res:', res);  // this shows the correct header
        res.headers['set-cookie'].should.be.an('array');
        const hasSid = res.headers['set-cookie'].map(cookie => {
          const reSid = /^sails\.sid=[^;]+;\sPath=\/;(?:\sExpires=[^;]+GMT;)?\sHttpOnly$/;
          return reSid.test(cookie);
        });
        hasSid.should.include.members([true]);
      });
    });

  });

});

我是 运行 node v8.11.3,sails v1.0.2,mocha v5.2.0,supertest v3.1.0,chai v4.1.2

仅供参考,这是 Postman 提出的工作正常的请求(CSRF 令牌由先前的 Postman 请求手动复制到 GET /login):

PUT /api/v1/entrance/login HTTP/1.1
Host: localhost:1337
x-csrf-token: mjWXQTa2-RFEHu78Tr-JGJwhWeryKGRJI4S8
Cache-Control: no-cache
Postman-Token: e3d920fe-6178-4642-80e4-8005b477fd98

{"emailAddress": "admin@example.com", "password":"abc123"}

知道了!我以为我打算在登录后从 set-cookie header 获取 session ID。相反,我打算同时捕获 CSRF 令牌和 session ID仍然注销,然后提交电子邮件和密码,然后在后续请求中使用令牌和 ID。我在 Postman 中错过了这个细节,因为我没有注意到在请求之间持续存在的 cookie。

这是固定的测试文件(现在可以在启用 CSRF 保护的情况下使用)

/**
 * /test/integration/controllers/entrance/login.test.js
 */

'use strict';

const supertest = require('supertest');  // also tried

describe('Entrance controllers', () => {

  describe('/api/v1/entrance/login', () => {

    before(() => {
      this._url = '/api/v1/entrance/login';

      return supertest(sails.hooks.http.app).get('/login')
      .then(getRes => {
        const reTokenCapture = /_csrf:\s*unescape\('([^']+)'\)/;
        const foundToken = reTokenCapture.exec(getRes.text);
        this._csrf = sails.config.security.csrf ? foundToken[1] : '';
        this._cookie = getRes.headers['set-cookie'].join('; ');
      });

    });

    it('should accept the session ID & CSRF token procured by GET /login', () => {
      return supertest(sails.hooks.http.app)
      .put(this._url)
      .set('Cookie', this._cookie)
      .set('X-CSRF-Token', this._csrf)
      .send({
        emailAddress: 'admin@example.com',
        password: 'abc123',
      })
      .expect(200);
    });

    it('should reject requests without a CSRF token', () => {
      return supertest(sails.hooks.http.app)
      .put(this._url)
      .set('Cookie', this._cookie)
      .expect(403);
    });

    it('should reject requests without a session cookie', () => {
      return supertest(sails.hooks.http.app)
      .put(this._url)
      .set('Cookie', '')
      .set('x-csrf-token', this._csrf)
      .expect(403);
    });

    it('should reject requests with invalid tokens', () => {
      return supertest(sails.hooks.http.app)
      .put(this._url)
      .set('Cookie', 'sails.sid=foo; Path=/; HttpOnly')
      .set('X-CSRF-Token', 'foo')
      .send({
        emailAddress: 'admin@example.com',
        password: 'abc123',
      })
      .expect(403);
    });

    it('should reject requests with invalid credentionals', () => {
      return supertest(sails.hooks.http.app)
      .put(this._url)
      .set('Cookie', this._cookie)
      .set('X-CSRF-Token', this._csrf)
      .send({
        emailAddress: 'user@example.com',
        password: 'password'
      })
      .expect(401);
    });

    it('should reject get requests', () => {
      return supertest(sails.hooks.http.app)
      .get(this._url)
      .set('Cookie', this._cookie)
      .set('X-CSRF-Token', this._csrf)
      .send({
        emailAddress: 'admin@example.com',
        password: 'abc123',
      })
      .expect(404);
    });

    it('should reject post requests', () => {
      return supertest(sails.hooks.http.app)
      .post(this._url)
      .set('Cookie', this._cookie)
      .set('X-CSRF-Token', this._csrf)
      .send({
        emailAddress: 'admin@example.com',
        password: 'abc123',
      })
      .expect(404);
    });

  });

});