WIP - improve login to tenants by domains
This commit is contained in:
@@ -55,10 +55,7 @@ export class AuthController {
|
|||||||
) {
|
) {
|
||||||
const subdomain = req.raw?.subdomain;
|
const subdomain = req.raw?.subdomain;
|
||||||
|
|
||||||
console.log('subdomain:' + subdomain);
|
|
||||||
|
|
||||||
console.log('CENTRAL_SUBDOMAINS:', process.env.CENTRAL_SUBDOMAINS);
|
|
||||||
|
|
||||||
// If it's a central subdomain, tenantId is not required
|
// If it's a central subdomain, tenantId is not required
|
||||||
if (!subdomain || !this.isCentralSubdomain(subdomain)) {
|
if (!subdomain || !this.isCentralSubdomain(subdomain)) {
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
|||||||
import { CentralTenant, CentralDomain, CentralUser } from '../models/central.model';
|
import { CentralTenant, CentralDomain, CentralUser } from '../models/central.model';
|
||||||
import { getCentralKnex, initCentralModels } from './central-database.service';
|
import { getCentralKnex, initCentralModels } from './central-database.service';
|
||||||
import { TenantProvisioningService } from './tenant-provisioning.service';
|
import { TenantProvisioningService } from './tenant-provisioning.service';
|
||||||
|
import { TenantDatabaseService } from './tenant-database.service';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,6 +27,7 @@ import * as bcrypt from 'bcrypt';
|
|||||||
export class CentralAdminController {
|
export class CentralAdminController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly provisioningService: TenantProvisioningService,
|
private readonly provisioningService: TenantProvisioningService,
|
||||||
|
private readonly tenantDbService: TenantDatabaseService,
|
||||||
) {
|
) {
|
||||||
// Initialize central models on controller creation
|
// Initialize central models on controller creation
|
||||||
initCentralModels();
|
initCentralModels();
|
||||||
@@ -173,7 +175,18 @@ export class CentralAdminController {
|
|||||||
@Delete('domains/:id')
|
@Delete('domains/:id')
|
||||||
async deleteDomain(@Req() req: any, @Param('id') id: string) {
|
async deleteDomain(@Req() req: any, @Param('id') id: string) {
|
||||||
this.checkCentralAdmin(req);
|
this.checkCentralAdmin(req);
|
||||||
|
|
||||||
|
// Get domain info before deleting to invalidate cache
|
||||||
|
const domain = await CentralDomain.query().findById(id);
|
||||||
|
|
||||||
|
// Delete the domain
|
||||||
await CentralDomain.query().deleteById(id);
|
await CentralDomain.query().deleteById(id);
|
||||||
|
|
||||||
|
// Invalidate tenant connection cache for this domain
|
||||||
|
if (domain) {
|
||||||
|
this.tenantDbService.removeTenantConnection(domain.domain);
|
||||||
|
}
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,17 +9,68 @@ export class TenantDatabaseService {
|
|||||||
private tenantConnections: Map<string, Knex> = new Map();
|
private tenantConnections: Map<string, Knex> = new Map();
|
||||||
|
|
||||||
async getTenantKnex(tenantIdOrSlug: string): Promise<Knex> {
|
async getTenantKnex(tenantIdOrSlug: string): Promise<Knex> {
|
||||||
|
// Check if we have a cached connection
|
||||||
if (this.tenantConnections.has(tenantIdOrSlug)) {
|
if (this.tenantConnections.has(tenantIdOrSlug)) {
|
||||||
|
// For domain-based lookups, validate the domain still exists before returning cached connection
|
||||||
|
const centralPrisma = getCentralPrisma();
|
||||||
|
|
||||||
|
// Check if this looks like a domain (not a UUID)
|
||||||
|
const isDomain = !tenantIdOrSlug.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
|
||||||
|
|
||||||
|
if (isDomain) {
|
||||||
|
try {
|
||||||
|
const domainRecord = await centralPrisma.domain.findUnique({
|
||||||
|
where: { domain: tenantIdOrSlug },
|
||||||
|
});
|
||||||
|
|
||||||
|
// If domain no longer exists, remove cached connection and continue to error
|
||||||
|
if (!domainRecord) {
|
||||||
|
this.logger.warn(`Domain ${tenantIdOrSlug} no longer exists, removing cached connection`);
|
||||||
|
await this.disconnectTenant(tenantIdOrSlug);
|
||||||
|
throw new Error(`Domain ${tenantIdOrSlug} not found`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// If domain doesn't exist, remove from cache and re-throw
|
||||||
|
if (error.message.includes('not found')) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
// For other errors, log but continue with cached connection
|
||||||
|
this.logger.warn(`Error validating domain ${tenantIdOrSlug}:`, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this.tenantConnections.get(tenantIdOrSlug);
|
return this.tenantConnections.get(tenantIdOrSlug);
|
||||||
}
|
}
|
||||||
|
|
||||||
const centralPrisma = getCentralPrisma();
|
const centralPrisma = getCentralPrisma();
|
||||||
|
|
||||||
// Try to find tenant by ID first, then by slug
|
let tenant = null;
|
||||||
let tenant = await centralPrisma.tenant.findUnique({
|
|
||||||
where: { id: tenantIdOrSlug },
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// First, try to find by domain (most common case - subdomain lookup)
|
||||||
|
try {
|
||||||
|
const domainRecord = await centralPrisma.domain.findUnique({
|
||||||
|
where: { domain: tenantIdOrSlug },
|
||||||
|
include: { tenant: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('here:' + JSON.stringify(domainRecord));
|
||||||
|
|
||||||
|
if (domainRecord) {
|
||||||
|
tenant = domainRecord.tenant;
|
||||||
|
this.logger.log(`Found tenant by domain: ${tenantIdOrSlug} -> ${tenant.name}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.debug(`No domain found for: ${tenantIdOrSlug}, trying ID/slug lookup`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Try to find tenant by ID
|
||||||
|
if (!tenant) {
|
||||||
|
tenant = await centralPrisma.tenant.findUnique({
|
||||||
|
where: { id: tenantIdOrSlug },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Try to find by slug
|
||||||
if (!tenant) {
|
if (!tenant) {
|
||||||
tenant = await centralPrisma.tenant.findUnique({
|
tenant = await centralPrisma.tenant.findUnique({
|
||||||
where: { slug: tenantIdOrSlug },
|
where: { slug: tenantIdOrSlug },
|
||||||
|
|||||||
Reference in New Issue
Block a user