Fix jwt-token

This commit is contained in:
2025-09-16 08:06:50 +02:00
parent b3ada7ccfe
commit f8fcfbb5be
3 changed files with 42 additions and 23 deletions

View File

@@ -30,6 +30,15 @@ class MultiTenantAuth {
this.models = models; this.models = models;
} }
/**
* Check if a string is an IP address
*/
isIPAddress(str) {
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
return ipv4Regex.test(str) || ipv6Regex.test(str);
}
/** /**
* Initialize all authentication providers * Initialize all authentication providers
*/ */
@@ -89,13 +98,17 @@ class MultiTenantAuth {
// Method 5: Subdomain (tenant.yourapp.com) // Method 5: Subdomain (tenant.yourapp.com)
const hostname = req.hostname || req.headers.host || ''; const hostname = req.hostname || req.headers.host || '';
if (hostname && !hostname.startsWith('localhost')) { // Remove port number if present
const hostParts = hostname.split('.'); const hostWithoutPort = hostname.split(':')[0];
// Skip if localhost or IP address
if (hostname && !hostname.startsWith('localhost') && !this.isIPAddress(hostWithoutPort)) {
const hostParts = hostWithoutPort.split('.');
// Only treat as subdomain if there are at least 2 parts (subdomain.domain.com) // Only treat as subdomain if there are at least 2 parts (subdomain.domain.com)
// and the first part is not a common root domain // and the first part is not a common root domain
if (hostParts.length >= 3) { if (hostParts.length >= 3) {
const subdomain = hostParts[0]; const subdomain = hostParts[0];
if (subdomain && subdomain !== 'www' && subdomain !== 'api' && !subdomain.includes(':')) { if (subdomain && subdomain !== 'www' && subdomain !== 'api') {
console.log('🏢 Tenant from subdomain:', subdomain); console.log('🏢 Tenant from subdomain:', subdomain);
return subdomain; return subdomain;
} }

View File

@@ -1,7 +1,7 @@
function validateRequest(schema, target = 'body') { function validateRequest(schema, source = 'body') {
return (req, res, next) => { return (req, res, next) => {
const data = req[target]; const dataToValidate = req[source];
const { error, value } = schema.validate(data, { const { error, value } = schema.validate(dataToValidate, {
abortEarly: false, abortEarly: false,
stripUnknown: true stripUnknown: true
}); });
@@ -13,20 +13,15 @@ function validateRequest(schema, target = 'body') {
value: detail.context.value value: detail.context.value
})); }));
// Create a message that includes the field names for test compatibility
const fieldNames = errorDetails.map(err => err.field).join(', ');
const message = `Validation error: ${fieldNames}`;
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: message, message: 'Validation error',
errors: errorDetails, errors: errorDetails
details: errorDetails // For backward compatibility
}); });
} }
// Replace the target data with validated and sanitized data // Replace the validated data source with validated and sanitized data
req[target] = value; req[source] = value;
next(); next();
}; };
} }

View File

@@ -4,7 +4,6 @@ const sinon = require('sinon');
const request = require('supertest'); const request = require('supertest');
const express = require('express'); const express = require('express');
const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, createTestUser, createTestTenant, generateTestToken } = require('../setup'); const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, createTestUser, createTestTenant, generateTestToken } = require('../setup');
const { createTenantRequest, TEST_TENANTS } = require('../utils/testDomains');
const authRoutes = require('../../routes/auth'); const authRoutes = require('../../routes/auth');
describe('Auth Routes', () => { describe('Auth Routes', () => {
@@ -29,18 +28,16 @@ describe('Auth Routes', () => {
describe('POST /auth/login', () => { describe('POST /auth/login', () => {
it('should login with valid credentials', async () => { it('should login with valid credentials', async () => {
const tenant = await createTestTenant({ slug: TEST_TENANTS.DEFAULT }); const tenant = await createTestTenant({ slug: 'test-tenant' });
console.log('🔧 TEST: Created tenant:', { id: tenant.id, slug: tenant.slug });
const user = await createTestUser({ const user = await createTestUser({
username: 'testuser', username: 'testuser',
password: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password password: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
tenant_id: tenant.id tenant_id: tenant.id
}); });
console.log('🔧 TEST: Created user:', { id: user.id, username: user.username, tenant_id: user.tenant_id });
const response = await createTenantRequest(request, app, TEST_TENANTS.DEFAULT) const response = await request(app)
.post('/auth/login') .post('/auth/login')
.set('Host', 'test-tenant.example.com')
.send({ .send({
username: 'testuser', username: 'testuser',
password: 'password' password: 'password'
@@ -55,6 +52,7 @@ describe('Auth Routes', () => {
it('should reject invalid username', async () => { it('should reject invalid username', async () => {
const response = await request(app) const response = await request(app)
.post('/auth/login') .post('/auth/login')
.set('Host', 'test-tenant.example.com')
.send({ .send({
username: 'nonexistent', username: 'nonexistent',
password: 'password' password: 'password'
@@ -66,13 +64,16 @@ describe('Auth Routes', () => {
}); });
it('should reject invalid password', async () => { it('should reject invalid password', async () => {
const tenant = await createTestTenant({ slug: 'test-tenant' });
const user = await createTestUser({ const user = await createTestUser({
username: 'testuser', username: 'testuser',
password: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi' password: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
tenant_id: tenant.id
}); });
const response = await request(app) const response = await request(app)
.post('/auth/login') .post('/auth/login')
.set('Host', 'test-tenant.example.com')
.send({ .send({
username: 'testuser', username: 'testuser',
password: 'wrongpassword' password: 'wrongpassword'
@@ -83,14 +84,17 @@ describe('Auth Routes', () => {
}); });
it('should reject inactive user', async () => { it('should reject inactive user', async () => {
const tenant = await createTestTenant({ slug: 'test-tenant' });
const user = await createTestUser({ const user = await createTestUser({
username: 'inactive', username: 'inactive',
password: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', password: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
is_active: false is_active: false,
tenant_id: tenant.id
}); });
const response = await request(app) const response = await request(app)
.post('/auth/login') .post('/auth/login')
.set('Host', 'test-tenant.example.com')
.send({ .send({
username: 'inactive', username: 'inactive',
password: 'password' password: 'password'
@@ -111,6 +115,7 @@ describe('Auth Routes', () => {
const response = await request(app) const response = await request(app)
.post('/auth/login') .post('/auth/login')
.set('Host', 'test-tenant.example.com')
.send({ .send({
username: 'testuser', username: 'testuser',
password: 'password' password: 'password'
@@ -123,8 +128,11 @@ describe('Auth Routes', () => {
}); });
it('should validate required fields', async () => { it('should validate required fields', async () => {
const tenant = await createTestTenant({ slug: 'test-tenant' });
const response = await request(app) const response = await request(app)
.post('/auth/login') .post('/auth/login')
.set('Host', 'test-tenant.example.com')
.send({ .send({
username: 'testuser' username: 'testuser'
// missing password // missing password
@@ -135,12 +143,15 @@ describe('Auth Routes', () => {
}); });
it('should handle database errors gracefully', async () => { it('should handle database errors gracefully', async () => {
const tenant = await createTestTenant({ slug: 'test-tenant' });
// Mock database error // Mock database error
const originalFindOne = models.User.findOne; const originalFindOne = models.User.findOne;
models.User.findOne = sinon.stub().rejects(new Error('Database error')); models.User.findOne = sinon.stub().rejects(new Error('Database error'));
const response = await request(app) const response = await request(app)
.post('/auth/login') .post('/auth/login')
.set('Host', 'test-tenant.example.com')
.send({ .send({
username: 'testuser', username: 'testuser',
password: 'password' password: 'password'