// CRITICAL: Set environment variables FIRST process.env.NODE_ENV = 'test'; process.env.JWT_SECRET = 'test-jwt-secret-key-for-testing-only'; const { describe, it, beforeEach, afterEach, before, after } = require('mocha'); const { expect } = require('chai'); const sinon = require('sinon'); const jwt = require('jsonwebtoken'); const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, mockRequest, mockResponse, mockNext, createTestUser, createTestTenant } = require('../setup'); const { authenticateToken, requireRole, setModels } = require('../../middleware/auth'); describe('Authentication Middleware', () => { let models, sequelize; before(async () => { ({ models, sequelize } = await setupTestEnvironment()); setModels(models); // Set models for the auth middleware }); after(async () => { await teardownTestEnvironment(); }); beforeEach(async () => { await cleanDatabase(); }); describe('authenticateToken', () => { it('should reject request without Authorization header', async () => { const req = mockRequest(); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(res.statusCode).to.equal(401); expect(res.data).to.deep.equal({ success: false, message: 'No authentication token provided.' }); expect(next.errors).to.have.length(0); }); it('should reject request with invalid token format', async () => { const req = mockRequest({ headers: { authorization: 'InvalidToken' } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(res.statusCode).to.equal(401); expect(res.data).to.deep.equal({ success: false, message: 'Invalid authentication token. Please log in again.' }); }); it('should reject request with invalid JWT token', async () => { const req = mockRequest({ headers: { authorization: 'Bearer invalid.jwt.token' } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(res.statusCode).to.equal(401); expect(res.data.success).to.be.false; expect(res.data.message).to.equal('Invalid authentication token. Please log in again.'); }); it('should reject request with expired JWT token', async () => { const expiredToken = jwt.sign( { userId: 1, username: 'test' }, process.env.JWT_SECRET || 'test-secret', { expiresIn: '-1h' } ); const req = mockRequest({ headers: { authorization: `Bearer ${expiredToken}` } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(res.statusCode).to.equal(401); expect(res.data.success).to.be.false; expect(res.data.message).to.equal('Your session has expired. Please log in again.'); }); it('should accept valid JWT token and set user data', async () => { const user = await createTestUser({ username: 'testuser', role: 'admin' }); const token = jwt.sign( { userId: user.id, username: user.username, role: user.role, email: user.email }, process.env.JWT_SECRET || 'test-secret', { expiresIn: '1h' } ); const req = mockRequest({ headers: { authorization: `Bearer ${token}` } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(req.user).to.exist; expect(req.user.userId).to.equal(user.id); expect(req.user.username).to.equal(user.username); expect(req.user.role).to.equal(user.role); expect(next.errors).to.have.length(0); }); it('should handle token with tenantId', async () => { const tenant = await createTestTenant({ slug: 'test-tenant' }); const user = await createTestUser({ username: 'testuser', tenant_id: tenant.id }); const token = jwt.sign( { userId: user.id, username: user.username, role: user.role, tenantId: tenant.slug }, process.env.JWT_SECRET || 'test-secret', { expiresIn: '1h' } ); const req = mockRequest({ headers: { authorization: `Bearer ${token}` } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(req.user.tenantId).to.equal(tenant.slug); expect(next.errors).to.have.length(0); }); it('should reject user not found in database', async () => { const token = jwt.sign( { userId: 99999, username: 'nonexistent' }, process.env.JWT_SECRET || 'test-secret', { expiresIn: '1h' } ); const req = mockRequest({ headers: { authorization: `Bearer ${token}` } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(res.statusCode).to.equal(401); expect(res.data.success).to.be.false; expect(res.data.message).to.equal('User account not found. Please contact support.'); }); it('should reject inactive user', async () => { const user = await createTestUser({ username: 'inactive', is_active: false }); const token = jwt.sign( { userId: user.id, username: user.username }, process.env.JWT_SECRET || 'test-secret', { expiresIn: '1h' } ); const req = mockRequest({ headers: { authorization: `Bearer ${token}` } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(res.statusCode).to.equal(401); expect(res.data.success).to.be.false; expect(res.data.message).to.equal('Your account has been deactivated. Please contact support.'); }); it('should handle malformed JWT token', async () => { const req = mockRequest({ headers: { authorization: 'Bearer malformed.jwt' } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(res.statusCode).to.equal(401); expect(res.data.success).to.be.false; expect(res.data.error).to.equal('INVALID_TOKEN'); expect(res.data.redirectToLogin).to.be.true; }); it('should handle JWT with invalid signature', async () => { const invalidToken = jwt.sign( { userId: 1, username: 'test' }, 'wrong-secret', { expiresIn: '1h' } ); const req = mockRequest({ headers: { authorization: `Bearer ${invalidToken}` } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(res.statusCode).to.equal(401); expect(res.data.success).to.be.false; expect(res.data.error).to.equal('INVALID_TOKEN'); expect(res.data.redirectToLogin).to.be.true; }); it('should handle JWT not before error', async () => { const futureToken = jwt.sign( { userId: 1, username: 'test', nbf: Math.floor(Date.now() / 1000) + 3600 }, process.env.JWT_SECRET || 'test-secret', { expiresIn: '1h' } ); const req = mockRequest({ headers: { authorization: `Bearer ${futureToken}` } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(res.statusCode).to.equal(401); expect(res.data.success).to.be.false; expect(res.data.error).to.equal('TOKEN_NOT_ACTIVE'); expect(res.data.redirectToLogin).to.be.true; }); it('should handle missing JWT_SECRET environment variable', async () => { const originalSecret = process.env.JWT_SECRET; delete process.env.JWT_SECRET; const token = jwt.sign( { userId: 1, username: 'test' }, 'any-secret', { expiresIn: '1h' } ); const req = mockRequest({ headers: { authorization: `Bearer ${token}` } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(res.statusCode).to.equal(401); expect(res.data.success).to.be.false; expect(res.data.error).to.equal('AUTHENTICATION_FAILED'); expect(res.data.redirectToLogin).to.be.true; // Restore original secret process.env.JWT_SECRET = originalSecret; }); it('should include error details for monitoring', async () => { const originalEnv = process.env.NODE_ENV; process.env.NODE_ENV = 'production'; const consoleSpy = sinon.spy(console, 'error'); const req = mockRequest({ headers: { authorization: 'Bearer invalid.jwt.token', 'user-agent': 'Test Agent', }, ip: '127.0.0.1', path: '/api/test', connection: { remoteAddress: '127.0.0.1' } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(consoleSpy.calledOnce).to.be.true; const logCall = consoleSpy.firstCall.args; expect(logCall[0]).to.equal('🔐 Authentication error:'); expect(logCall[1]).to.have.property('error'); expect(logCall[1]).to.have.property('userAgent', 'Test Agent'); expect(logCall[1]).to.have.property('ip', '127.0.0.1'); expect(logCall[1]).to.have.property('path', '/api/test'); consoleSpy.restore(); process.env.NODE_ENV = originalEnv; }); }); describe('Enhanced Error Response Format', () => { it('should return consistent error format for expired tokens', async () => { const expiredToken = jwt.sign( { userId: 1, username: 'test' }, process.env.JWT_SECRET || 'test-secret', { expiresIn: '-1h' } ); const req = mockRequest({ headers: { authorization: `Bearer ${expiredToken}` } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(res.statusCode).to.equal(401); expect(res.data).to.deep.equal({ success: false, error: 'TOKEN_EXPIRED', message: 'Your session has expired. Please log in again.', redirectToLogin: true }); }); it('should return consistent error format for invalid tokens', async () => { const req = mockRequest({ headers: { authorization: 'Bearer invalid.jwt.token' } }); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(res.statusCode).to.equal(401); expect(res.data).to.deep.equal({ success: false, error: 'INVALID_TOKEN', message: 'Invalid authentication token. Please log in again.', redirectToLogin: true }); }); it('should return consistent error format for missing tokens', async () => { const req = mockRequest(); const res = mockResponse(); const next = mockNext(); await authenticateToken(req, res, next); expect(res.statusCode).to.equal(401); expect(res.data).to.deep.equal({ success: false, error: 'NO_TOKEN', message: 'No authentication token provided.', redirectToLogin: true }); }); }); });