259 lines
8.0 KiB
TypeScript
259 lines
8.0 KiB
TypeScript
import type { Cloudly } from '../classes.cloudly.js';
|
|
import * as plugins from '../plugins.js';
|
|
import { Domain } from './classes.domain.js';
|
|
|
|
interface IWorkHosterDomain {
|
|
name: string;
|
|
nameservers?: string[];
|
|
capabilities?: {
|
|
canCreateSubdomains: boolean;
|
|
canManageDnsRecords: boolean;
|
|
canIssueCertificates: boolean;
|
|
canHostEmail: boolean;
|
|
};
|
|
}
|
|
|
|
export class DomainManager {
|
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
public cloudlyRef: Cloudly;
|
|
|
|
get db() {
|
|
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
|
}
|
|
|
|
public CDomain = plugins.smartdata.setDefaultManagerForDoc(this, Domain);
|
|
|
|
constructor(cloudlyRef: Cloudly) {
|
|
this.cloudlyRef = cloudlyRef;
|
|
|
|
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
|
|
// Get all domains
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.domain.IRequest_Any_Cloudly_GetDomains>(
|
|
'getDomains',
|
|
async (reqArg) => {
|
|
await plugins.smartguard.passGuardsOrReject(reqArg, [
|
|
this.cloudlyRef.authManager.validIdentityGuard,
|
|
]);
|
|
|
|
const domains = await this.CDomain.getDomains();
|
|
|
|
return {
|
|
domains: await Promise.all(
|
|
domains.map((domain) => domain.createSavableObject())
|
|
),
|
|
};
|
|
}
|
|
)
|
|
);
|
|
|
|
// Get domain by ID
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.domain.IRequest_Any_Cloudly_GetDomainById>(
|
|
'getDomainById',
|
|
async (reqArg) => {
|
|
await plugins.smartguard.passGuardsOrReject(reqArg, [
|
|
this.cloudlyRef.authManager.validIdentityGuard,
|
|
]);
|
|
|
|
const domain = await this.CDomain.getDomainById(reqArg.domainId);
|
|
if (!domain) {
|
|
throw new Error(`Domain with id ${reqArg.domainId} not found`);
|
|
}
|
|
|
|
return {
|
|
domain: await domain.createSavableObject(),
|
|
};
|
|
}
|
|
)
|
|
);
|
|
|
|
// Create domain
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.domain.IRequest_Any_Cloudly_CreateDomain>(
|
|
'createDomain',
|
|
async (reqArg) => {
|
|
await plugins.smartguard.passGuardsOrReject(reqArg, [
|
|
this.cloudlyRef.authManager.validIdentityGuard,
|
|
]);
|
|
|
|
// Check if domain already exists
|
|
const existingDomain = await this.CDomain.getDomainByName(reqArg.domainData.name);
|
|
if (existingDomain) {
|
|
throw new Error(`Domain ${reqArg.domainData.name} already exists`);
|
|
}
|
|
|
|
const domain = await this.CDomain.createDomain(reqArg.domainData);
|
|
|
|
return {
|
|
domain: await domain.createSavableObject(),
|
|
};
|
|
}
|
|
)
|
|
);
|
|
|
|
// Update domain
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.domain.IRequest_Any_Cloudly_UpdateDomain>(
|
|
'updateDomain',
|
|
async (reqArg) => {
|
|
await plugins.smartguard.passGuardsOrReject(reqArg, [
|
|
this.cloudlyRef.authManager.validIdentityGuard,
|
|
]);
|
|
|
|
const domain = await this.CDomain.updateDomain(
|
|
reqArg.domainId,
|
|
reqArg.domainData
|
|
);
|
|
|
|
return {
|
|
domain: await domain.createSavableObject(),
|
|
};
|
|
}
|
|
)
|
|
);
|
|
|
|
// Delete domain
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.domain.IRequest_Any_Cloudly_DeleteDomain>(
|
|
'deleteDomain',
|
|
async (reqArg) => {
|
|
await plugins.smartguard.passGuardsOrReject(reqArg, [
|
|
this.cloudlyRef.authManager.validIdentityGuard,
|
|
]);
|
|
|
|
const success = await this.CDomain.deleteDomain(reqArg.domainId);
|
|
|
|
return {
|
|
success,
|
|
};
|
|
}
|
|
)
|
|
);
|
|
|
|
// Verify domain
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.domain.IRequest_Any_Cloudly_VerifyDomain>(
|
|
'verifyDomain',
|
|
async (reqArg) => {
|
|
await plugins.smartguard.passGuardsOrReject(reqArg, [
|
|
this.cloudlyRef.authManager.validIdentityGuard,
|
|
]);
|
|
|
|
const domain = await this.CDomain.getDomainById(reqArg.domainId);
|
|
if (!domain) {
|
|
throw new Error(`Domain with id ${reqArg.domainId} not found`);
|
|
}
|
|
|
|
const verificationResult = await domain.verifyDomain(reqArg.verificationMethod);
|
|
|
|
return {
|
|
domain: await domain.createSavableObject(),
|
|
verificationResult,
|
|
};
|
|
}
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Initialize the domain manager
|
|
*/
|
|
public async init() {
|
|
await this.syncExternalGatewayDomains().catch((error) => {
|
|
console.log(`External gateway domain sync failed: ${(error as Error).message}`);
|
|
});
|
|
console.log('Domain Manager initialized');
|
|
}
|
|
|
|
public async syncExternalGatewayDomains(): Promise<number> {
|
|
const settings = await this.cloudlyRef.settingsManager.getSettings();
|
|
if (!settings.dcrouterGatewayUrl || !settings.dcrouterGatewayApiToken) {
|
|
return 0;
|
|
}
|
|
|
|
const typedRequest = new plugins.typedrequest.TypedRequest<any>(
|
|
`${settings.dcrouterGatewayUrl.replace(/\/+$/, '')}/typedrequest`,
|
|
'getWorkHosterDomains',
|
|
);
|
|
const response = await typedRequest.fire({
|
|
apiToken: settings.dcrouterGatewayApiToken,
|
|
}) as { domains: IWorkHosterDomain[] };
|
|
|
|
const activeDomainNames = new Set<string>();
|
|
for (const gatewayDomain of response.domains) {
|
|
const domainName = gatewayDomain.name.trim().toLowerCase();
|
|
if (!domainName) continue;
|
|
|
|
activeDomainNames.add(domainName);
|
|
const existingDomain = await this.CDomain.getDomainByName(domainName);
|
|
const tags = Array.from(new Set([...(existingDomain?.data.tags || []), 'dcrouter']));
|
|
const domainData: Partial<plugins.servezoneInterfaces.data.IDomain['data']> = {
|
|
name: domainName,
|
|
status: 'active',
|
|
verificationStatus: 'not_required',
|
|
nameservers: gatewayDomain.nameservers || existingDomain?.data.nameservers || [],
|
|
autoRenew: gatewayDomain.capabilities?.canIssueCertificates !== false,
|
|
activationState: 'available',
|
|
syncSource: 'manual',
|
|
lastSyncAt: Date.now(),
|
|
isExternal: true,
|
|
tags,
|
|
};
|
|
|
|
if (existingDomain) {
|
|
await this.CDomain.updateDomain(existingDomain.id, domainData);
|
|
} else {
|
|
await this.CDomain.createDomain(domainData as plugins.servezoneInterfaces.data.IDomain['data']);
|
|
}
|
|
}
|
|
|
|
const knownDomains = await this.CDomain.getDomains();
|
|
for (const domain of knownDomains) {
|
|
if (domain.data.tags?.includes('dcrouter') && !activeDomainNames.has(domain.data.name)) {
|
|
await this.CDomain.updateDomain(domain.id, {
|
|
activationState: 'ignored',
|
|
lastSyncAt: Date.now(),
|
|
});
|
|
}
|
|
}
|
|
|
|
console.log(`Synced ${activeDomainNames.size} domain(s) from external dcrouter gateway`);
|
|
return activeDomainNames.size;
|
|
}
|
|
|
|
/**
|
|
* Stop the domain manager
|
|
*/
|
|
public async stop() {
|
|
console.log('Domain Manager stopped');
|
|
}
|
|
|
|
/**
|
|
* Get all active domains
|
|
*/
|
|
public async getActiveDomains() {
|
|
const domains = await this.CDomain.getInstances({
|
|
'data.status': 'active',
|
|
});
|
|
return domains;
|
|
}
|
|
|
|
/**
|
|
* Get domains that are expiring soon
|
|
*/
|
|
public async getExpiringDomains(daysThreshold: number = 30) {
|
|
const domains = await this.CDomain.getDomains();
|
|
return domains.filter(domain => domain.isExpiringSoon(daysThreshold));
|
|
}
|
|
|
|
/**
|
|
* Check if a domain name is available (not in our system)
|
|
*/
|
|
public async isDomainAvailable(domainName: string): Promise<boolean> {
|
|
const existingDomain = await this.CDomain.getDomainByName(domainName);
|
|
return !existingDomain;
|
|
}
|
|
}
|