// 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 request = require('supertest'); const jwt = require('jsonwebtoken'); const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, createTestUser, createTestTenant } = require('../setup'); describe('Authentication Integration Tests', () => { let app, models, sequelize, user, tenant; before(async () => { ({ app, models, sequelize } = await setupTestEnvironment()); }); after(async () => { await teardownTestEnvironment(); }); beforeEach(async () => { await cleanDatabase(); tenant = await createTestTenant({ slug: 'test-tenant' }); user = await createTestUser({ username: 'testuser', role: 'admin', tenant_id: tenant.id }); }); describe('Protected Route Access', () => { it('should deny access to protected route without token', async () => { const response = await request(app) .get('/api/users/profile') .expect(401); expect(response.body).to.deep.equal({ success: false, message: 'Access token required' }); }); it('should deny access with malformed authorization header', async () => { const response = await request(app) .get('/api/users/profile') .set('Authorization', 'InvalidToken') .expect(401); expect(response.body).to.deep.equal({ success: false, message: 'Invalid token format' }); }); it('should deny access with expired token', async () => { const expiredToken = jwt.sign( { userId: user.id, username: user.username, role: user.role }, process.env.JWT_SECRET, { expiresIn: '-1h' } ); const response = await request(app) .get('/api/users/profile') .set('Authorization', `Bearer ${expiredToken}`) .expect(401); expect(response.body).to.deep.equal({ success: false, error: 'TOKEN_EXPIRED', message: 'Token expired', redirectToLogin: true }); }); it('should deny access with invalid token signature', async () => { const invalidToken = jwt.sign( { userId: user.id, username: user.username, role: user.role }, 'wrong-secret', { expiresIn: '1h' } ); const response = await request(app) .get('/api/users/profile') .set('Authorization', `Bearer ${invalidToken}`) .expect(401); expect(response.body).to.deep.equal({ success: false, error: 'INVALID_TOKEN', message: 'Invalid token', redirectToLogin: true }); }); it('should deny access for non-existent user', async () => { const fakeToken = jwt.sign( { userId: 'non-existent-id', username: 'fake', role: 'admin' }, process.env.JWT_SECRET, { expiresIn: '1h' } ); const response = await request(app) .get('/api/users/profile') .set('Authorization', `Bearer ${fakeToken}`) .expect(401); expect(response.body).to.deep.equal({ success: false, message: 'User not found' }); }); it('should deny access for inactive user', async () => { // Deactivate the user await user.update({ is_active: false }); const validToken = jwt.sign( { userId: user.id, username: user.username, role: user.role }, process.env.JWT_SECRET, { expiresIn: '1h' } ); const response = await request(app) .get('/api/users/profile') .set('Authorization', `Bearer ${validToken}`) .expect(401); expect(response.body).to.deep.equal({ success: false, message: 'User account is inactive' }); }); it('should allow access with valid token', async () => { const validToken = jwt.sign( { userId: user.id, username: user.username, role: user.role, tenantId: tenant.slug }, process.env.JWT_SECRET, { expiresIn: '1h' } ); const response = await request(app) .get('/api/users/profile') .set('Authorization', `Bearer ${validToken}`) .expect(200); expect(response.body.success).to.be.true; expect(response.body.data).to.have.property('username', user.username); }); }); describe('Role-Based Access Control', () => { let adminUser, regularUser, adminToken, userToken; beforeEach(async () => { adminUser = await createTestUser({ username: 'admin', role: 'admin', tenant_id: tenant.id }); regularUser = await createTestUser({ username: 'user', role: 'user', tenant_id: tenant.id }); adminToken = jwt.sign( { userId: adminUser.id, username: adminUser.username, role: adminUser.role, tenantId: tenant.slug }, process.env.JWT_SECRET, { expiresIn: '1h' } ); userToken = jwt.sign( { userId: regularUser.id, username: regularUser.username, role: regularUser.role, tenantId: tenant.slug }, process.env.JWT_SECRET, { expiresIn: '1h' } ); }); it('should allow admin access to admin-only endpoints', async () => { const response = await request(app) .get('/api/health/metrics') .set('Authorization', `Bearer ${adminToken}`) .expect(200); expect(response.body.success).to.be.true; }); it('should deny regular user access to admin-only endpoints', async () => { const response = await request(app) .get('/api/health/metrics') .set('Authorization', `Bearer ${userToken}`) .expect(403); expect(response.body).to.deep.equal({ success: false, message: 'Insufficient permissions' }); }); }); describe('Token Security Validation', () => { it('should reject token with incorrect algorithm', async () => { // Create token with different algorithm (if supported by library) const maliciousToken = jwt.sign( { userId: user.id, username: user.username, role: 'admin' }, process.env.JWT_SECRET, { algorithm: 'none' } ); const response = await request(app) .get('/api/users/profile') .set('Authorization', `Bearer ${maliciousToken}`) .expect(401); expect(response.body.success).to.be.false; expect(response.body.error).to.equal('INVALID_TOKEN'); }); it('should reject tampered token payload', async () => { const validToken = jwt.sign( { userId: user.id, username: user.username, role: 'user' }, process.env.JWT_SECRET, { expiresIn: '1h' } ); // Tamper with the token by modifying the payload const parts = validToken.split('.'); const tamperedPayload = Buffer.from('{"userId":"' + user.id + '","username":"' + user.username + '","role":"admin"}').toString('base64'); const tamperedToken = parts[0] + '.' + tamperedPayload + '.' + parts[2]; const response = await request(app) .get('/api/users/profile') .set('Authorization', `Bearer ${tamperedToken}`) .expect(401); expect(response.body.success).to.be.false; expect(response.body.error).to.equal('INVALID_TOKEN'); }); }); describe('Tenant Context Validation', () => { let otherTenant, otherUser; beforeEach(async () => { otherTenant = await createTestTenant({ slug: 'other-tenant' }); otherUser = await createTestUser({ username: 'otheruser', role: 'admin', tenant_id: otherTenant.id }); }); it('should set correct tenant context from JWT', async () => { const validToken = jwt.sign( { userId: user.id, username: user.username, role: user.role, tenantId: tenant.slug }, process.env.JWT_SECRET, { expiresIn: '1h' } ); // Make a request that would show tenant context in logs const response = await request(app) .get('/api/devices') .set('Authorization', `Bearer ${validToken}`) .expect(200); expect(response.body.success).to.be.true; }); it('should handle missing tenant context gracefully', async () => { const tokenWithoutTenant = jwt.sign( { userId: user.id, username: user.username, role: user.role }, process.env.JWT_SECRET, { expiresIn: '1h' } ); const response = await request(app) .get('/api/users/profile') .set('Authorization', `Bearer ${tokenWithoutTenant}`) .expect(200); expect(response.body.success).to.be.true; }); }); });