WIP - manage tenant users from central
This commit is contained in:
@@ -8,83 +8,116 @@ export class TenantDatabaseService {
|
||||
private readonly logger = new Logger(TenantDatabaseService.name);
|
||||
private tenantConnections: Map<string, Knex> = new Map();
|
||||
|
||||
async getTenantKnex(tenantIdOrSlug: string): Promise<Knex> {
|
||||
/**
|
||||
* Get tenant database connection by domain (for subdomain-based authentication)
|
||||
* This is used when users log in via tenant subdomains
|
||||
*/
|
||||
async getTenantKnexByDomain(domain: string): Promise<Knex> {
|
||||
const cacheKey = `domain:${domain}`;
|
||||
|
||||
// Check if we have a cached connection
|
||||
if (this.tenantConnections.has(tenantIdOrSlug)) {
|
||||
// For domain-based lookups, validate the domain still exists before returning cached connection
|
||||
if (this.tenantConnections.has(cacheKey)) {
|
||||
// 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);
|
||||
try {
|
||||
const domainRecord = await centralPrisma.domain.findUnique({
|
||||
where: { domain },
|
||||
});
|
||||
|
||||
// If domain no longer exists, remove cached connection
|
||||
if (!domainRecord) {
|
||||
this.logger.warn(`Domain ${domain} no longer exists, removing cached connection`);
|
||||
await this.disconnectTenant(cacheKey);
|
||||
throw new Error(`Domain ${domain} 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 ${domain}:`, error.message);
|
||||
}
|
||||
|
||||
return this.tenantConnections.get(tenantIdOrSlug);
|
||||
return this.tenantConnections.get(cacheKey);
|
||||
}
|
||||
|
||||
const centralPrisma = getCentralPrisma();
|
||||
|
||||
let tenant = null;
|
||||
|
||||
// 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));
|
||||
// Find tenant by domain
|
||||
const domainRecord = await centralPrisma.domain.findUnique({
|
||||
where: { domain },
|
||||
include: { tenant: true },
|
||||
});
|
||||
|
||||
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) {
|
||||
tenant = await centralPrisma.tenant.findUnique({
|
||||
where: { slug: tenantIdOrSlug },
|
||||
});
|
||||
if (!domainRecord) {
|
||||
throw new Error(`Domain ${domain} not found`);
|
||||
}
|
||||
|
||||
const tenant = domainRecord.tenant;
|
||||
this.logger.log(`Found tenant by domain: ${domain} -> ${tenant.name}`);
|
||||
|
||||
if (tenant.status !== 'active') {
|
||||
throw new Error(`Tenant ${tenant.name} is not active`);
|
||||
}
|
||||
|
||||
// Create connection and cache it
|
||||
const tenantKnex = await this.createTenantConnection(tenant);
|
||||
this.tenantConnections.set(cacheKey, tenantKnex);
|
||||
|
||||
return tenantKnex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tenant database connection by tenant ID (for central admin operations)
|
||||
* This is used when central admin needs to access tenant databases
|
||||
*/
|
||||
async getTenantKnexById(tenantId: string): Promise<Knex> {
|
||||
const cacheKey = `id:${tenantId}`;
|
||||
|
||||
// Check if we have a cached connection (no validation needed for ID-based lookups)
|
||||
if (this.tenantConnections.has(cacheKey)) {
|
||||
return this.tenantConnections.get(cacheKey);
|
||||
}
|
||||
|
||||
const centralPrisma = getCentralPrisma();
|
||||
|
||||
// Find tenant by ID
|
||||
const tenant = await centralPrisma.tenant.findUnique({
|
||||
where: { id: tenantId },
|
||||
});
|
||||
|
||||
if (!tenant) {
|
||||
throw new Error(`Tenant ${tenantIdOrSlug} not found`);
|
||||
throw new Error(`Tenant ${tenantId} not found`);
|
||||
}
|
||||
|
||||
if (tenant.status !== 'active') {
|
||||
throw new Error(`Tenant ${tenantIdOrSlug} is not active`);
|
||||
throw new Error(`Tenant ${tenant.name} is not active`);
|
||||
}
|
||||
|
||||
this.logger.log(`Connecting to tenant database by ID: ${tenant.name}`);
|
||||
|
||||
// Create connection and cache it
|
||||
const tenantKnex = await this.createTenantConnection(tenant);
|
||||
this.tenantConnections.set(cacheKey, tenantKnex);
|
||||
|
||||
return tenantKnex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy method - delegates to domain-based lookup
|
||||
* @deprecated Use getTenantKnexByDomain or getTenantKnexById instead
|
||||
*/
|
||||
async getTenantKnex(tenantIdOrSlug: string): Promise<Knex> {
|
||||
// Assume it's a domain if it contains a dot
|
||||
return this.getTenantKnexByDomain(tenantIdOrSlug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Knex connection to a tenant database
|
||||
*/
|
||||
private async createTenantConnection(tenant: any): Promise<Knex> {
|
||||
// Decrypt password
|
||||
const decryptedPassword = this.decryptPassword(tenant.dbPassword);
|
||||
|
||||
@@ -115,7 +148,6 @@ export class TenantDatabaseService {
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.tenantConnections.set(tenantIdOrSlug, tenantKnex);
|
||||
return tenantKnex;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user