285 lines
8.8 KiB
JavaScript
285 lines
8.8 KiB
JavaScript
// 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: 'No authentication token provided.'
|
|
});
|
|
});
|
|
|
|
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 authentication token. Please log in again.'
|
|
});
|
|
});
|
|
|
|
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: 'Your session has expired. Please log in again.',
|
|
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 authentication token. Please log in again.',
|
|
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 account not found. Please contact support.'
|
|
});
|
|
});
|
|
|
|
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: 'Your account has been deactivated. Please contact support.'
|
|
});
|
|
});
|
|
|
|
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: 'You do not have permission to perform this action.'
|
|
});
|
|
});
|
|
});
|
|
|
|
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;
|
|
});
|
|
});
|
|
}); |