108 lines
No EOL
2.8 KiB
JavaScript
108 lines
No EOL
2.8 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert';
|
|
import bcrypt from 'bcryptjs';
|
|
import jwt from 'jsonwebtoken';
|
|
import { pool } from '../db/connection.js';
|
|
|
|
// Mock the database pool
|
|
const mockPool = {
|
|
query: (sql, params) => {
|
|
if (sql.includes('INSERT INTO users')) {
|
|
return [{ insertId: 1 }];
|
|
}
|
|
if (sql.includes('SELECT id, email, password_hash FROM users')) {
|
|
// Simulate a valid user with hashed password
|
|
const hashedPassword = '$2a$12$O5y0793j8K4h6g7f9e8d7c6b5a4z3y2x1w0v9u8t7s6r5q4p3o2n1m';
|
|
return [[{ id: 1, email: 'test@example.com', password_hash: hashedPassword }]];
|
|
}
|
|
return [];
|
|
}
|
|
};
|
|
|
|
// Replace the actual pool with mock
|
|
const originalPool = pool;
|
|
pool.query = mockPool.query;
|
|
|
|
// Mock bcrypt.compare to always return true for valid password
|
|
const originalBcryptCompare = bcrypt.compare;
|
|
bcrypt.compare = async (password, hash) => {
|
|
if (password === 'validpassword') {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// Mock jwt.sign to return a fixed token
|
|
const originalJwtSign = jwt.sign;
|
|
jwt.sign = () => 'mock-jwt-token';
|
|
|
|
test('login should implement rate limiting for failed attempts', async () => {
|
|
const req = {
|
|
body: {
|
|
email: 'test@example.com',
|
|
password: 'wrongpassword'
|
|
}
|
|
};
|
|
|
|
const res = {
|
|
status: (code) => {
|
|
res.statusCode = code;
|
|
return res;
|
|
},
|
|
json: (data) => {
|
|
res.data = data;
|
|
}
|
|
};
|
|
|
|
// First 4 failed attempts should not be rate limited
|
|
for (let i = 0; i < 4; i++) {
|
|
await require('../routes/auth').default.post('/login', req, res);
|
|
assert.strictEqual(res.statusCode, 401);
|
|
}
|
|
|
|
// 5th attempt should be rate limited
|
|
await require('../routes/auth').default.post('/login', req, res);
|
|
assert.strictEqual(res.statusCode, 429);
|
|
assert.ok(res.data.error.includes('Too many login attempts'));
|
|
});
|
|
|
|
test('login should reset rate limit after successful authentication', async () => {
|
|
const req = {
|
|
body: {
|
|
email: 'test@example.com',
|
|
password: 'validpassword'
|
|
}
|
|
};
|
|
|
|
const res = {
|
|
status: (code) => {
|
|
res.statusCode = code;
|
|
return res;
|
|
},
|
|
json: (data) => {
|
|
res.data = data;
|
|
}
|
|
};
|
|
|
|
// Successful login should reset the rate limit
|
|
await require('../routes/auth').default.post('/login', req, res);
|
|
assert.strictEqual(res.statusCode, 200);
|
|
|
|
// Now try to fail again - should not be rate limited immediately
|
|
const failedReq = {
|
|
body: {
|
|
email: 'test@example.com',
|
|
password: 'wrongpassword'
|
|
}
|
|
};
|
|
|
|
await require('../routes/auth').default.post('/login', failedReq, res);
|
|
assert.strictEqual(res.statusCode, 401);
|
|
});
|
|
|
|
// Restore the original functions
|
|
bcrypt.compare = originalBcryptCompare;
|
|
jwt.sign = originalJwtSign;
|
|
pool.query = originalPool;
|
|
|
|
export default {}; |