192 lines
5.8 KiB
JavaScript
192 lines
5.8 KiB
JavaScript
const { describe, it, beforeEach, afterEach, before, after } = require('mocha');
|
|
const { expect } = require('chai');
|
|
const sinon = require('sinon');
|
|
const MultiTenantAuth = require('../../middleware/multi-tenant-auth');
|
|
const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, mockRequest, mockResponse, mockNext, createTestUser, createTestTenant, generateTestToken } = require('../setup');
|
|
|
|
describe('Multi-Tenant Authentication Middleware', () => {
|
|
let models, sequelize, multiAuth;
|
|
|
|
before(async () => {
|
|
({ models, sequelize } = await setupTestEnvironment());
|
|
multiAuth = new MultiTenantAuth();
|
|
});
|
|
|
|
after(async () => {
|
|
await teardownTestEnvironment();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await cleanDatabase();
|
|
});
|
|
|
|
describe('determineTenant', () => {
|
|
it('should extract tenant from tenantId in JWT token', async () => {
|
|
const tenant = await createTestTenant({ slug: 'test-tenant' });
|
|
const user = await createTestUser({ tenant_id: tenant.id });
|
|
|
|
const req = mockRequest({
|
|
user: { tenantId: tenant.slug }
|
|
});
|
|
|
|
const result = await multiAuth.determineTenant(req);
|
|
expect(result).to.equal(tenant.slug);
|
|
});
|
|
|
|
it('should extract tenant from subdomain', async () => {
|
|
const req = mockRequest({
|
|
headers: { host: 'tenant1.example.com' }
|
|
});
|
|
|
|
const result = await multiAuth.determineTenant(req);
|
|
expect(result).to.equal('tenant1');
|
|
});
|
|
|
|
it('should extract tenant from complex subdomain', async () => {
|
|
const req = mockRequest({
|
|
headers: { host: 'uamils-ab.dev.uggla.uamils.com' }
|
|
});
|
|
|
|
const result = await multiAuth.determineTenant(req);
|
|
expect(result).to.equal('uamils-ab');
|
|
});
|
|
|
|
it('should extract tenant from domain path', async () => {
|
|
const req = mockRequest({
|
|
headers: { host: 'example.com' },
|
|
url: '/tenant2/api/devices'
|
|
});
|
|
|
|
const result = await multiAuth.determineTenant(req);
|
|
expect(result).to.equal('tenant2');
|
|
});
|
|
|
|
it('should prioritize JWT tenantId over subdomain', async () => {
|
|
const req = mockRequest({
|
|
user: { tenantId: 'jwt-tenant' },
|
|
headers: { host: 'subdomain-tenant.example.com' }
|
|
});
|
|
|
|
const result = await multiAuth.determineTenant(req);
|
|
expect(result).to.equal('jwt-tenant');
|
|
});
|
|
|
|
it('should return null for localhost without tenant info', async () => {
|
|
const req = mockRequest({
|
|
headers: { host: 'localhost:3000' }
|
|
});
|
|
|
|
const result = await multiAuth.determineTenant(req);
|
|
expect(result).to.be.null;
|
|
});
|
|
|
|
it('should handle x-forwarded-host header', async () => {
|
|
const req = mockRequest({
|
|
headers: {
|
|
host: 'localhost:3000',
|
|
'x-forwarded-host': 'tenant3.example.com'
|
|
}
|
|
});
|
|
|
|
const result = await multiAuth.determineTenant(req);
|
|
expect(result).to.equal('tenant3');
|
|
});
|
|
});
|
|
|
|
describe('middleware function', () => {
|
|
it('should pass through when tenant is determined', async () => {
|
|
const tenant = await createTestTenant({ slug: 'valid-tenant' });
|
|
|
|
const req = mockRequest({
|
|
user: { tenantId: tenant.slug }
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await multiAuth.authenticate(req, res, next);
|
|
|
|
expect(req.tenant).to.equal(tenant.slug);
|
|
expect(next.errors).to.have.length(0);
|
|
});
|
|
|
|
it('should reject when tenant cannot be determined', async () => {
|
|
const req = mockRequest({
|
|
headers: { host: 'localhost:3000' }
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await multiAuth.authenticate(req, res, next);
|
|
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.data).to.deep.equal({
|
|
success: false,
|
|
message: 'Unable to determine tenant'
|
|
});
|
|
});
|
|
|
|
it('should reject when tenant does not exist in database', async () => {
|
|
const req = mockRequest({
|
|
user: { tenantId: 'nonexistent-tenant' }
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await multiAuth.authenticate(req, res, next);
|
|
|
|
expect(res.statusCode).to.equal(404);
|
|
expect(res.data).to.deep.equal({
|
|
success: false,
|
|
message: 'Tenant not found'
|
|
});
|
|
});
|
|
|
|
it('should reject when tenant is inactive', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'inactive-tenant',
|
|
is_active: false
|
|
});
|
|
|
|
const req = mockRequest({
|
|
user: { tenantId: tenant.slug }
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await multiAuth.authenticate(req, res, next);
|
|
|
|
expect(res.statusCode).to.equal(403);
|
|
expect(res.data).to.deep.equal({
|
|
success: false,
|
|
message: 'Tenant is not active'
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('validateTenantAccess', () => {
|
|
it('should validate user belongs to tenant', async () => {
|
|
const tenant = await createTestTenant({ slug: 'user-tenant' });
|
|
const user = await createTestUser({ tenant_id: tenant.id });
|
|
|
|
const isValid = await multiAuth.validateTenantAccess(user.id, tenant.slug);
|
|
expect(isValid).to.be.true;
|
|
});
|
|
|
|
it('should reject user from different tenant', async () => {
|
|
const tenant1 = await createTestTenant({ slug: 'tenant1' });
|
|
const tenant2 = await createTestTenant({ slug: 'tenant2' });
|
|
const user = await createTestUser({ tenant_id: tenant1.id });
|
|
|
|
const isValid = await multiAuth.validateTenantAccess(user.id, tenant2.slug);
|
|
expect(isValid).to.be.false;
|
|
});
|
|
|
|
it('should reject nonexistent user', async () => {
|
|
const tenant = await createTestTenant({ slug: 'valid-tenant' });
|
|
|
|
const isValid = await multiAuth.validateTenantAccess(99999, tenant.slug);
|
|
expect(isValid).to.be.false;
|
|
});
|
|
});
|
|
});
|