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

car*_*iem 2 testing csrf mocha.js supertest sails.js

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

下面的测试应该会导致成功登录,其中会返回一个带有新会话 ID 的 cookie。如果我更改安全配置以禁用CSRF 保护,它会完美运行。但是在启用安全性的情况下,请求被禁止 (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]);
      });
    });

  });

});
Run Code Online (Sandbox Code Playgroud)

我正在运行 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"}
Run Code Online (Sandbox Code Playgroud)

car*_*iem 5

知道了!我以为我打算set-cookie在登录后从标题中获取会话 ID 。相反,我打算在注销时同时捕获 CSRF 令牌和会话 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);
    });

  });

});
Run Code Online (Sandbox Code Playgroud)