| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- /* eslint-env mocha */
- import { expect } from 'chai';
- import sinon from 'sinon';
- import { Meteor } from 'meteor/meteor';
- import { Accounts } from 'meteor/accounts-base';
- describe('attachmentApi authentication', function() {
- let findOneStub, hashStub;
- beforeEach(function() {
- hashStub = sinon.stub(Accounts, '_hashLoginToken');
- findOneStub = sinon.stub(Meteor.users, 'findOne');
- });
- afterEach(function() {
- if (hashStub) hashStub.restore();
- if (findOneStub) findOneStub.restore();
- });
- // Mock request/response objects
- function createMockReq(headers = {}) {
- return {
- headers,
- on: sinon.stub(),
- connection: { destroy: sinon.stub() },
- };
- }
- function createMockRes() {
- return {
- writeHead: sinon.stub(),
- end: sinon.stub(),
- headersSent: false,
- };
- }
- describe('authenticateApiRequest', function() {
- it('denies request with missing X-User-Id header', function() {
- const req = createMockReq({ 'x-auth-token': 'sometoken' });
- const res = createMockRes();
- // Simulate the handler behavior
- let errorThrown = false;
- try {
- if (!req.headers['x-user-id'] || !req.headers['x-auth-token']) {
- throw new Meteor.Error('unauthorized', 'Missing X-User-Id or X-Auth-Token headers');
- }
- } catch (error) {
- errorThrown = true;
- expect(error.error).to.equal('unauthorized');
- }
- expect(errorThrown).to.equal(true);
- });
- it('denies request with missing X-Auth-Token header', function() {
- const req = createMockReq({ 'x-user-id': 'user123' });
- let errorThrown = false;
- try {
- if (!req.headers['x-user-id'] || !req.headers['x-auth-token']) {
- throw new Meteor.Error('unauthorized', 'Missing X-User-Id or X-Auth-Token headers');
- }
- } catch (error) {
- errorThrown = true;
- expect(error.error).to.equal('unauthorized');
- }
- expect(errorThrown).to.equal(true);
- });
- it('denies request with invalid token', function() {
- const userId = 'user123';
- const token = 'invalidtoken';
- const req = createMockReq({ 'x-user-id': userId, 'x-auth-token': token });
- hashStub.returns('hashedInvalidToken');
- findOneStub.returns(null); // No user found
- let errorThrown = false;
- try {
- const hashedToken = Accounts._hashLoginToken(token);
- const user = Meteor.users.findOne({
- _id: userId,
- 'services.resume.loginTokens.hashedToken': hashedToken,
- });
- if (!user) {
- throw new Meteor.Error('unauthorized', 'Invalid credentials');
- }
- } catch (error) {
- errorThrown = true;
- expect(error.error).to.equal('unauthorized');
- }
- expect(errorThrown).to.equal(true);
- expect(hashStub.calledOnce).to.equal(true);
- expect(findOneStub.calledOnce).to.equal(true);
- });
- it('allows request with valid X-User-Id and X-Auth-Token', function() {
- const userId = 'user123';
- const token = 'validtoken';
- const req = createMockReq({ 'x-user-id': userId, 'x-auth-token': token });
- const hashedToken = 'hashedValidToken';
- hashStub.returns(hashedToken);
- findOneStub.returns({ _id: userId }); // User found
- let authenticatedUserId = null;
- try {
- const hashed = Accounts._hashLoginToken(token);
- const user = Meteor.users.findOne({
- _id: userId,
- 'services.resume.loginTokens.hashedToken': hashed,
- });
- if (!user) {
- throw new Meteor.Error('unauthorized', 'Invalid credentials');
- }
- authenticatedUserId = userId;
- } catch (error) {
- // Should not throw
- }
- expect(authenticatedUserId).to.equal(userId);
- expect(hashStub.calledOnce).to.equal(true);
- expect(hashStub.calledWith(token)).to.equal(true);
- expect(findOneStub.calledOnce).to.equal(true);
- const queryArg = findOneStub.getCall(0).args[0];
- expect(queryArg._id).to.equal(userId);
- expect(queryArg['services.resume.loginTokens.hashedToken']).to.equal(hashedToken);
- });
- it('prevents identity spoofing by validating hashed token', function() {
- const victimId = 'victim-user-id';
- const attackerToken = 'attacker-token';
- const req = createMockReq({ 'x-user-id': victimId, 'x-auth-token': attackerToken });
- hashStub.returns('hashedAttackerToken');
- // Simulate victim exists but token doesn't match
- findOneStub.returns(null);
- let errorThrown = false;
- try {
- const hashed = Accounts._hashLoginToken(attackerToken);
- const user = Meteor.users.findOne({
- _id: victimId,
- 'services.resume.loginTokens.hashedToken': hashed,
- });
- if (!user) {
- throw new Meteor.Error('unauthorized', 'Invalid credentials');
- }
- } catch (error) {
- errorThrown = true;
- expect(error.error).to.equal('unauthorized');
- }
- expect(errorThrown).to.equal(true);
- });
- });
- describe('request handler DoS prevention', function() {
- it('enforces timeout on hanging requests', function(done) {
- this.timeout(5000);
-
- const req = createMockReq({ 'x-user-id': 'user1', 'x-auth-token': 'token1' });
- const res = createMockRes();
- // Simulate timeout behavior
- const timeout = setTimeout(() => {
- if (!res.headersSent) {
- res.headersSent = true;
- res.writeHead(408, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, error: 'Request timeout' }));
- }
- }, 100); // Short timeout for test
- // Wait for timeout
- setTimeout(() => {
- expect(res.headersSent).to.equal(true);
- expect(res.writeHead.calledWith(408)).to.equal(true);
- clearTimeout(timeout);
- done();
- }, 150);
- });
- it('limits request body size', function() {
- const req = createMockReq({ 'x-user-id': 'user1', 'x-auth-token': 'token1' });
- let body = '';
- const limit = 50 * 1024 * 1024; // 50MB
- // Simulate exceeding limit
- body = 'a'.repeat(limit + 1);
- expect(body.length).to.be.greaterThan(limit);
- // Handler should destroy connection
- if (body.length > limit) {
- req.connection.destroy();
- }
- expect(req.connection.destroy.calledOnce).to.equal(true);
- });
- });
- });
|