405 lines
12 KiB
JavaScript
405 lines
12 KiB
JavaScript
const { describe, it, beforeEach, afterEach, before, after } = require('mocha');
|
|
const { expect } = require('chai');
|
|
const sinon = require('sinon');
|
|
const request = require('supertest');
|
|
const express = require('express');
|
|
const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, createTestUser, createTestTenant, generateTestToken } = require('../setup');
|
|
const authRoutes = require('../../routes/auth');
|
|
|
|
describe('Auth Routes', () => {
|
|
let app, models, sequelize;
|
|
|
|
before(async () => {
|
|
({ models, sequelize } = await setupTestEnvironment());
|
|
|
|
// Setup express app for testing
|
|
app = express();
|
|
app.use(express.json());
|
|
app.use('/auth', authRoutes);
|
|
});
|
|
|
|
after(async () => {
|
|
await teardownTestEnvironment();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await cleanDatabase();
|
|
});
|
|
|
|
describe('POST /auth/login', () => {
|
|
it('should login with valid credentials', async () => {
|
|
const tenant = await createTestTenant({ slug: 'test-tenant' });
|
|
const user = await createTestUser({
|
|
username: 'testuser',
|
|
password_hash: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
|
tenant_id: tenant.id
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/auth/login')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.send({
|
|
username: 'testuser',
|
|
password: 'password'
|
|
});
|
|
|
|
expect(response.status).to.equal(200);
|
|
expect(response.body.success).to.be.true;
|
|
expect(response.body.data.token).to.exist;
|
|
expect(response.body.data.user.username).to.equal('testuser');
|
|
});
|
|
|
|
it('should reject invalid username', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/login')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.send({
|
|
username: 'nonexistent',
|
|
password: 'password'
|
|
});
|
|
|
|
expect(response.status).to.equal(401);
|
|
expect(response.body.success).to.be.false;
|
|
expect(response.body.message).to.equal('Invalid credentials');
|
|
});
|
|
|
|
it('should reject invalid password', async () => {
|
|
const tenant = await createTestTenant({ slug: 'test-tenant' });
|
|
const user = await createTestUser({
|
|
username: 'testuser',
|
|
password_hash: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
|
|
tenant_id: tenant.id
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/auth/login')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.send({
|
|
username: 'testuser',
|
|
password: 'wrongpassword'
|
|
});
|
|
|
|
expect(response.status).to.equal(401);
|
|
expect(response.body.success).to.be.false;
|
|
});
|
|
|
|
it('should reject inactive user', async () => {
|
|
const tenant = await createTestTenant({ slug: 'test-tenant' });
|
|
const user = await createTestUser({
|
|
username: 'inactive',
|
|
password_hash: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
|
|
is_active: false,
|
|
tenant_id: tenant.id
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/auth/login')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.send({
|
|
username: 'inactive',
|
|
password: 'password'
|
|
});
|
|
|
|
expect(response.status).to.equal(401);
|
|
expect(response.body.success).to.be.false;
|
|
expect(response.body.message).to.equal('Account is inactive');
|
|
});
|
|
|
|
it('should include tenant information in JWT token', async () => {
|
|
const tenant = await createTestTenant({ slug: 'test-tenant' });
|
|
const user = await createTestUser({
|
|
username: 'testuser',
|
|
password_hash: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
|
|
tenant_id: tenant.id
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/auth/login')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.send({
|
|
username: 'testuser',
|
|
password: 'password'
|
|
});
|
|
|
|
expect(response.status).to.equal(200);
|
|
const jwt = require('jsonwebtoken');
|
|
const decoded = jwt.verify(response.body.data.token, process.env.JWT_SECRET || 'test-secret');
|
|
expect(decoded.tenantId).to.equal(tenant.slug);
|
|
});
|
|
|
|
it('should validate required fields', async () => {
|
|
const tenant = await createTestTenant({ slug: 'test-tenant' });
|
|
|
|
const response = await request(app)
|
|
.post('/auth/login')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.send({
|
|
username: 'testuser'
|
|
// missing password
|
|
});
|
|
|
|
expect(response.status).to.equal(400);
|
|
expect(response.body.success).to.be.false;
|
|
});
|
|
|
|
it('should handle database errors gracefully', async () => {
|
|
const tenant = await createTestTenant({ slug: 'test-tenant' });
|
|
|
|
// Mock database error
|
|
const originalFindOne = models.User.findOne;
|
|
models.User.findOne = sinon.stub().rejects(new Error('Database error'));
|
|
|
|
const response = await request(app)
|
|
.post('/auth/login')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.send({
|
|
username: 'testuser',
|
|
password: 'password'
|
|
});
|
|
|
|
expect(response.status).to.equal(500);
|
|
expect(response.body.success).to.be.false;
|
|
|
|
// Restore original method
|
|
models.User.findOne = originalFindOne;
|
|
});
|
|
});
|
|
|
|
describe('POST /auth/register', () => {
|
|
it('should register new user when registration allowed', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
allow_registration: true
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/auth/register')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.send({
|
|
username: 'newuser',
|
|
email: 'new@example.com',
|
|
password: 'Password123',
|
|
first_name: 'New',
|
|
last_name: 'User'
|
|
});
|
|
|
|
expect(response.status).to.equal(201);
|
|
expect(response.body.success).to.be.true;
|
|
expect(response.body.data.user.username).to.equal('newuser');
|
|
});
|
|
|
|
it('should reject registration when not allowed', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
allow_registration: false
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/auth/register')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.send({
|
|
username: 'newuser',
|
|
email: 'new@example.com',
|
|
password: 'password123'
|
|
});
|
|
|
|
expect(response.status).to.equal(403);
|
|
expect(response.body.success).to.be.false;
|
|
expect(response.body.message).to.include('Registration not allowed');
|
|
});
|
|
|
|
it('should reject duplicate username', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
allow_registration: true
|
|
});
|
|
|
|
await createTestUser({
|
|
username: 'existing',
|
|
tenant_id: tenant.id
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/auth/register')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.send({
|
|
username: 'existing',
|
|
email: 'new@example.com',
|
|
password: 'password123'
|
|
});
|
|
|
|
expect(response.status).to.equal(400);
|
|
expect(response.body.success).to.be.false;
|
|
expect(response.body.message).to.include('already exists');
|
|
});
|
|
|
|
it('should reject duplicate email', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
allow_registration: true
|
|
});
|
|
|
|
await createTestUser({
|
|
username: 'existing',
|
|
email: 'existing@example.com',
|
|
tenant_id: tenant.id
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/auth/register')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.send({
|
|
username: 'newuser',
|
|
email: 'existing@example.com',
|
|
password: 'password123'
|
|
});
|
|
|
|
expect(response.status).to.equal(400);
|
|
expect(response.body.success).to.be.false;
|
|
});
|
|
|
|
it('should validate password strength', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
allow_registration: true
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/auth/register')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.send({
|
|
username: 'newuser',
|
|
email: 'new@example.com',
|
|
password: '123' // weak password
|
|
});
|
|
|
|
expect(response.status).to.equal(400);
|
|
expect(response.body.success).to.be.false;
|
|
expect(response.body.message).to.include('password');
|
|
});
|
|
|
|
it('should validate email format', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
allow_registration: true
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/auth/register')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.send({
|
|
username: 'newuser',
|
|
email: 'invalid-email',
|
|
password: 'password123'
|
|
});
|
|
|
|
expect(response.status).to.equal(400);
|
|
expect(response.body.success).to.be.false;
|
|
});
|
|
|
|
it('should enforce IP restrictions during registration', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
allow_registration: true,
|
|
ip_restrictions_enabled: true,
|
|
allowed_ips: '192.168.1.1'
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/auth/register')
|
|
.set('Host', 'test-tenant.example.com')
|
|
.set('X-Forwarded-For', '192.168.2.1') // Not allowed IP
|
|
.send({
|
|
username: 'newuser',
|
|
email: 'new@example.com',
|
|
password: 'password123'
|
|
});
|
|
|
|
expect(response.status).to.equal(403);
|
|
expect(response.body.success).to.be.false;
|
|
expect(response.body.message).to.include('IP address not allowed');
|
|
});
|
|
});
|
|
|
|
describe('POST /auth/refresh', () => {
|
|
it('should refresh valid token', async () => {
|
|
const user = await createTestUser();
|
|
const token = generateTestToken(user);
|
|
|
|
const response = await request(app)
|
|
.post('/auth/refresh')
|
|
.set('Authorization', `Bearer ${token}`);
|
|
|
|
expect(response.status).to.equal(200);
|
|
expect(response.body.success).to.be.true;
|
|
expect(response.body.data.token).to.exist;
|
|
expect(response.body.data.token).to.not.equal(token); // New token
|
|
});
|
|
|
|
it('should reject refresh without token', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/refresh');
|
|
|
|
expect(response.status).to.equal(401);
|
|
expect(response.body.success).to.be.false;
|
|
});
|
|
|
|
it('should reject refresh with invalid token', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/refresh')
|
|
.set('Authorization', 'Bearer invalid.token');
|
|
|
|
expect(response.status).to.equal(401);
|
|
expect(response.body.success).to.be.false;
|
|
});
|
|
});
|
|
|
|
describe('POST /auth/logout', () => {
|
|
it('should logout successfully', async () => {
|
|
const user = await createTestUser();
|
|
const token = generateTestToken(user);
|
|
|
|
const response = await request(app)
|
|
.post('/auth/logout')
|
|
.set('Authorization', `Bearer ${token}`);
|
|
|
|
expect(response.status).to.equal(200);
|
|
expect(response.body.success).to.be.true;
|
|
expect(response.body.message).to.equal('Logged out successfully');
|
|
});
|
|
|
|
it('should logout without token', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/logout');
|
|
|
|
expect(response.status).to.equal(200);
|
|
expect(response.body.success).to.be.true;
|
|
});
|
|
});
|
|
|
|
describe('GET /auth/me', () => {
|
|
it('should return current user info', async () => {
|
|
const tenant = await createTestTenant();
|
|
const user = await createTestUser({ tenant_id: tenant.id });
|
|
const token = generateTestToken(user, tenant);
|
|
|
|
const response = await request(app)
|
|
.get('/auth/me')
|
|
.set('Authorization', `Bearer ${token}`);
|
|
|
|
expect(response.status).to.equal(200);
|
|
expect(response.body.success).to.be.true;
|
|
expect(response.body.data.username).to.equal(user.username);
|
|
expect(response.body.data.email).to.equal(user.email);
|
|
});
|
|
|
|
it('should require authentication', async () => {
|
|
const response = await request(app)
|
|
.get('/auth/me');
|
|
|
|
expect(response.status).to.equal(401);
|
|
expect(response.body.success).to.be.false;
|
|
});
|
|
});
|
|
});
|