135 lines
4.8 KiB
TypeScript
135 lines
4.8 KiB
TypeScript
import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
|
|
import { FastifyRequest, FastifyReply } from 'fastify';
|
|
import { TenantDatabaseService } from './tenant-database.service';
|
|
|
|
@Injectable()
|
|
export class TenantMiddleware implements NestMiddleware {
|
|
private readonly logger = new Logger(TenantMiddleware.name);
|
|
|
|
constructor(private readonly tenantDbService: TenantDatabaseService) {}
|
|
|
|
async use(
|
|
req: FastifyRequest['raw'],
|
|
res: FastifyReply['raw'],
|
|
next: () => void,
|
|
) {
|
|
try {
|
|
// Priority 1: Check x-tenant-subdomain header from Nitro BFF proxy
|
|
// This is the primary method when using the BFF architecture
|
|
let subdomain = req.headers['x-tenant-subdomain'] as string | null;
|
|
let tenantId = req.headers['x-tenant-id'] as string;
|
|
|
|
if (subdomain) {
|
|
this.logger.log(`Using x-tenant-subdomain header: ${subdomain}`);
|
|
}
|
|
|
|
// Priority 2: Fall back to extracting subdomain from Origin/Host headers
|
|
// This supports direct backend access for development/testing
|
|
if (!subdomain && !tenantId) {
|
|
const host = req.headers.host || '';
|
|
const hostname = host.split(':')[0];
|
|
const origin = req.headers.origin as string;
|
|
const referer = req.headers.referer as string;
|
|
|
|
let parts = hostname.split('.');
|
|
|
|
this.logger.log(`Host header: ${host}, hostname: ${hostname}, origin: ${origin}, referer: ${referer}`);
|
|
|
|
// Try to extract subdomain from Origin header first (for API calls from frontend)
|
|
if (origin) {
|
|
try {
|
|
const originUrl = new URL(origin);
|
|
const originHost = originUrl.hostname;
|
|
parts = originHost.split('.');
|
|
this.logger.log(`Using Origin header hostname: ${originHost}, parts: ${JSON.stringify(parts)}`);
|
|
} catch (error) {
|
|
this.logger.warn(`Failed to parse origin: ${origin}`);
|
|
}
|
|
} else if (referer) {
|
|
// Fallback to Referer if no Origin
|
|
try {
|
|
const refererUrl = new URL(referer);
|
|
const refererHost = refererUrl.hostname;
|
|
parts = refererHost.split('.');
|
|
this.logger.log(`Using Referer header hostname: ${refererHost}, parts: ${JSON.stringify(parts)}`);
|
|
} catch (error) {
|
|
this.logger.warn(`Failed to parse referer: ${referer}`);
|
|
}
|
|
}
|
|
|
|
// Extract subdomain (e.g., "tenant1" from "tenant1.routebox.co")
|
|
if (parts.length >= 3) {
|
|
subdomain = parts[0];
|
|
if (subdomain === 'www') {
|
|
subdomain = null;
|
|
}
|
|
} else if (parts.length === 2 && parts[1] === 'localhost') {
|
|
subdomain = parts[0];
|
|
}
|
|
}
|
|
|
|
this.logger.log(`Extracted subdomain: ${subdomain}, x-tenant-id: ${tenantId}`);
|
|
|
|
// Always attach subdomain to request if present
|
|
if (subdomain) {
|
|
(req as any).subdomain = subdomain;
|
|
}
|
|
|
|
// If x-tenant-id is explicitly provided, use it directly but still keep subdomain
|
|
if (tenantId) {
|
|
this.logger.log(`Using explicit x-tenant-id: ${tenantId}`);
|
|
(req as any).tenantId = tenantId;
|
|
next();
|
|
return;
|
|
}
|
|
|
|
// Always attach subdomain to request if present
|
|
if (subdomain) {
|
|
(req as any).subdomain = subdomain;
|
|
}
|
|
|
|
// Check if this is a central subdomain
|
|
const centralSubdomains = (process.env.CENTRAL_SUBDOMAINS || 'central,admin').split(',');
|
|
const isCentral = subdomain && centralSubdomains.includes(subdomain);
|
|
|
|
// If it's a central subdomain, skip tenant resolution
|
|
if (isCentral) {
|
|
this.logger.log(`Central subdomain detected: ${subdomain}, skipping tenant resolution`);
|
|
(req as any).subdomain = subdomain;
|
|
next();
|
|
return;
|
|
}
|
|
|
|
// Get tenant by subdomain if available
|
|
if (subdomain) {
|
|
try {
|
|
const tenant = await this.tenantDbService.getTenantByDomain(subdomain);
|
|
if (tenant) {
|
|
tenantId = tenant.id;
|
|
this.logger.log(
|
|
`Tenant identified: ${tenant.name} (${tenant.id}) from subdomain: ${subdomain}`,
|
|
);
|
|
}
|
|
} catch (error) {
|
|
this.logger.warn(`No tenant found for subdomain: ${subdomain}`, error.message);
|
|
// Fall back to using subdomain as tenantId directly if domain lookup fails
|
|
tenantId = subdomain;
|
|
this.logger.log(`Using subdomain as tenantId fallback: ${tenantId}`);
|
|
}
|
|
}
|
|
|
|
if (tenantId) {
|
|
// Attach tenant info to request object
|
|
(req as any).tenantId = tenantId;
|
|
} else {
|
|
this.logger.warn(`No tenant identified from host: ${subdomain}`);
|
|
}
|
|
|
|
next();
|
|
} catch (error) {
|
|
this.logger.error('Error in tenant middleware', error);
|
|
next();
|
|
}
|
|
}
|
|
}
|