var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import GreenlockUtil from "../Utils/Greenlock/Greenlock";
import logger from "../Utils/Logger";
import DatabaseService from "./DatabaseService";
import DomainService from "./DomainService";
import URL from "../../Types/API/URL";
import LIMIT_MAX from "../../Types/Database/LimitMax";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import API from "../../Utils/API";
import DashboardDomain from "../../Models/DatabaseModels/DashboardDomain";
import AcmeCertificateService from "./AcmeCertificateService";
import Telemetry from "../Utils/Telemetry";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import { DashboardCNameRecord } from "../EnvironmentConfig";
import Domain from "../Types/Domain";
export class Service extends DatabaseService {
    constructor() {
        super(DashboardDomain);
    }
    async onBeforeCreate(createBy) {
        var _a, _b, _c, _d;
        const domain = await DomainService.findOneBy({
            query: {
                _id: ((_a = createBy.data.domainId) === null || _a === void 0 ? void 0 : _a.toString()) || ((_b = createBy.data.domain) === null || _b === void 0 ? void 0 : _b._id) || "",
            },
            select: { domain: true, isVerified: true },
            props: {
                isRoot: true,
            },
        });
        if (!(domain === null || domain === void 0 ? void 0 : domain.isVerified)) {
            throw new BadDataException("This domain is not verified. Please verify it by going to Settings > Domains");
        }
        let normalizedSubdomain = ((_c = createBy.data.subdomain) === null || _c === void 0 ? void 0 : _c.trim().toLowerCase()) || "";
        if (normalizedSubdomain === "@") {
            normalizedSubdomain = "";
        }
        createBy.data.subdomain = normalizedSubdomain;
        if (domain) {
            const baseDomain = ((_d = domain.domain) === null || _d === void 0 ? void 0 : _d.toString().toLowerCase().trim()) || "";
            if (!baseDomain) {
                throw new BadDataException("Please select a valid domain.");
            }
            createBy.data.fullDomain = normalizedSubdomain
                ? `${normalizedSubdomain}.${baseDomain}`
                : baseDomain;
        }
        createBy.data.cnameVerificationToken = ObjectID.generate().toString();
        if (createBy.data.isCustomCertificate) {
            if (!createBy.data.customCertificate ||
                !createBy.data.customCertificateKey) {
                throw new BadDataException("Custom certificate or private key is missing");
            }
        }
        return { createBy, carryForward: null };
    }
    async onBeforeDelete(deleteBy) {
        const domains = await this.findBy({
            query: Object.assign({}, deleteBy.query),
            skip: 0,
            limit: LIMIT_MAX,
            select: { fullDomain: true },
            props: {
                isRoot: true,
            },
        });
        return { deleteBy, carryForward: domains };
    }
    async onDeleteSuccess(onDelete, _itemIdsBeforeDelete) {
        for (const domain of onDelete.carryForward) {
            await this.removeDomainFromGreenlock(domain.fullDomain);
        }
        return onDelete;
    }
    async removeDomainFromGreenlock(domain) {
        await GreenlockUtil.removeDomain(domain);
    }
    async orderCert(dashboardDomain) {
        var _a;
        return Telemetry.startActiveSpan({
            name: "DashboardDomainService.orderCert",
            options: {
                attributes: {
                    fullDomain: dashboardDomain.fullDomain,
                    _id: (_a = dashboardDomain.id) === null || _a === void 0 ? void 0 : _a.toString(),
                },
            },
            fn: async (span) => {
                try {
                    if (!dashboardDomain.fullDomain) {
                        const fetchedDashboardDomain = await this.findOneBy({
                            query: {
                                _id: dashboardDomain.id.toString(),
                            },
                            select: {
                                _id: true,
                                fullDomain: true,
                            },
                            props: {
                                isRoot: true,
                            },
                        });
                        if (!fetchedDashboardDomain) {
                            throw new BadDataException("DomainModel not found");
                        }
                        dashboardDomain = fetchedDashboardDomain;
                    }
                    if (!dashboardDomain.fullDomain) {
                        throw new BadDataException("Unable to order certificate because domain is null");
                    }
                    logger.debug("Ordering SSL for domain: " + dashboardDomain.fullDomain, { fullDomain: dashboardDomain.fullDomain });
                    await GreenlockUtil.orderCert({
                        domain: dashboardDomain.fullDomain,
                        validateCname: async (fullDomain) => {
                            return await this.isCnameValid(fullDomain);
                        },
                    });
                    logger.debug("SSL ordered for domain: " + dashboardDomain.fullDomain, { fullDomain: dashboardDomain.fullDomain });
                    await this.updateOneById({
                        id: dashboardDomain.id,
                        data: {
                            isSslOrdered: true,
                        },
                        props: {
                            isRoot: true,
                        },
                    });
                    Telemetry.endSpan(span);
                }
                catch (err) {
                    Telemetry.recordExceptionMarkSpanAsErrorAndEndSpan({
                        span,
                        exception: err,
                    });
                    throw err;
                }
            },
        });
    }
    async updateSslProvisioningStatusForAllDomains() {
        const domains = await this.findBy({
            query: {
                isSslOrdered: true,
                isCustomCertificate: false,
            },
            select: {
                _id: true,
            },
            limit: LIMIT_MAX,
            skip: 0,
            props: {
                isRoot: true,
            },
        });
        for (const domain of domains) {
            await this.updateSslProvisioningStatus(domain);
        }
    }
    async isSSLProvisioned(fulldomain, token) {
        try {
            const result = await API.get({
                url: URL.fromString("https://" +
                    fulldomain +
                    "/dashboard-api/cname-verification/" +
                    token),
            });
            if (result.isFailure()) {
                return false;
            }
            return true;
        }
        catch (err) {
            logger.error(err, { fullDomain: fulldomain });
            return false;
        }
    }
    async updateCnameStatusForDashboardDomain(data) {
        if (!data.cnameStatus) {
            await this.updateOneBy({
                query: {
                    fullDomain: data.domain,
                },
                data: {
                    isCnameVerified: false,
                    isSslOrdered: false,
                    isSslProvisioned: false,
                },
                props: {
                    isRoot: true,
                },
            });
        }
        else {
            await this.updateOneBy({
                query: {
                    fullDomain: data.domain,
                },
                data: {
                    isCnameVerified: true,
                },
                props: {
                    isRoot: true,
                },
            });
        }
    }
    async isCnameValid(fullDomain) {
        try {
            logger.debug("Checking for CNAME " + fullDomain, {
                fullDomain,
            });
            const dashboardDomain = await this.findOneBy({
                query: {
                    fullDomain: fullDomain,
                },
                select: {
                    _id: true,
                    cnameVerificationToken: true,
                },
                props: {
                    isRoot: true,
                },
            });
            if (!dashboardDomain) {
                return false;
            }
            const token = dashboardDomain.cnameVerificationToken;
            logger.debug("Checking for CNAME " + fullDomain + " with token " + token, { fullDomain });
            try {
                const result = await API.get({
                    url: URL.fromString("http://" +
                        fullDomain +
                        "/dashboard-api/cname-verification/" +
                        token),
                });
                logger.debug("CNAME verification result", {
                    fullDomain,
                });
                logger.debug(result, { fullDomain });
                if (result.isSuccess()) {
                    await this.updateCnameStatusForDashboardDomain({
                        domain: fullDomain,
                        cnameStatus: true,
                    });
                    return true;
                }
            }
            catch (err) {
                logger.debug("Failed checking for CNAME " + fullDomain, {
                    fullDomain,
                });
                logger.debug(err, { fullDomain });
            }
            try {
                const resultHttps = await API.get({
                    url: URL.fromString("https://" +
                        fullDomain +
                        "/dashboard-api/cname-verification/" +
                        token),
                });
                logger.debug("CNAME verification result for https", {
                    fullDomain,
                });
                logger.debug(resultHttps, { fullDomain });
                if (resultHttps.isSuccess()) {
                    await this.updateCnameStatusForDashboardDomain({
                        domain: fullDomain,
                        cnameStatus: true,
                    });
                    return true;
                }
            }
            catch (err) {
                logger.debug("Failed checking for CNAME " + fullDomain, {
                    fullDomain,
                });
                logger.debug(err, { fullDomain });
            }
            try {
                if (DashboardCNameRecord) {
                    const cnameRecords = await Domain.getCnameRecords({
                        domain: fullDomain,
                    });
                    let cnameRecord = undefined;
                    if (cnameRecords.length > 0) {
                        cnameRecord = cnameRecords[0];
                    }
                    if (!cnameRecord) {
                        logger.debug(`No CNAME record found for ${fullDomain}. Expected record: ${DashboardCNameRecord}`, { fullDomain });
                        await this.updateCnameStatusForDashboardDomain({
                            domain: fullDomain,
                            cnameStatus: false,
                        });
                        return false;
                    }
                    if (cnameRecord &&
                        cnameRecord.trim().toLocaleLowerCase() ===
                            DashboardCNameRecord.trim().toLocaleLowerCase()) {
                        logger.debug(`CNAME record for ${fullDomain} matches the expected record: ${DashboardCNameRecord}`, { fullDomain });
                        await this.updateCnameStatusForDashboardDomain({
                            domain: fullDomain,
                            cnameStatus: true,
                        });
                        return true;
                    }
                    logger.debug(`CNAME record for ${fullDomain} is ${cnameRecord} and it does not match the expected record: ${DashboardCNameRecord}`, { fullDomain });
                }
            }
            catch (err) {
                logger.debug("Failed checking for CNAME " + fullDomain, {
                    fullDomain,
                });
                logger.debug(err, { fullDomain });
            }
            await this.updateCnameStatusForDashboardDomain({
                domain: fullDomain,
                cnameStatus: false,
            });
            return false;
        }
        catch (err) {
            logger.debug("Failed checking for CNAME " + fullDomain);
            logger.debug(err);
            await this.updateCnameStatusForDashboardDomain({
                domain: fullDomain,
                cnameStatus: false,
            });
            return false;
        }
    }
    async updateSslProvisioningStatus(domain) {
        var _a;
        if (!domain.id) {
            throw new BadDataException("DomainModel ID is required");
        }
        const dashboardDomain = await this.findOneBy({
            query: {
                _id: (_a = domain.id) === null || _a === void 0 ? void 0 : _a.toString(),
            },
            select: {
                _id: true,
                fullDomain: true,
                cnameVerificationToken: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!dashboardDomain) {
            throw new BadDataException("DomainModel not found");
        }
        logger.debug(`DashboardCerts:RemoveCerts - Checking CNAME ${dashboardDomain.fullDomain}`, { fullDomain: dashboardDomain.fullDomain });
        const isValid = await this.isSSLProvisioned(dashboardDomain.fullDomain, dashboardDomain.cnameVerificationToken);
        if (!isValid) {
            const isCnameValid = await this.isCnameValid(dashboardDomain.fullDomain);
            await this.updateOneById({
                id: dashboardDomain.id,
                data: {
                    isSslProvisioned: false,
                },
                props: {
                    isRoot: true,
                },
            });
            if (isCnameValid) {
                try {
                    await this.orderCert(dashboardDomain);
                }
                catch (err) {
                    logger.error("Cannot order cert for domain: " + dashboardDomain.fullDomain, { fullDomain: dashboardDomain.fullDomain });
                    logger.error(err, {
                        fullDomain: dashboardDomain.fullDomain,
                    });
                }
            }
        }
        else {
            await this.updateOneById({
                id: dashboardDomain.id,
                data: {
                    isSslProvisioned: true,
                },
                props: {
                    isRoot: true,
                },
            });
        }
    }
    async orderSSLForDomainsWhichAreNotOrderedYet() {
        return Telemetry.startActiveSpan({
            name: "DashboardDomainService.orderSSLForDomainsWhichAreNotOrderedYet",
            options: { attributes: {} },
            fn: async (span) => {
                try {
                    const domains = await this.findBy({
                        query: {
                            isSslOrdered: false,
                            isCustomCertificate: false,
                        },
                        select: {
                            _id: true,
                            fullDomain: true,
                        },
                        limit: LIMIT_MAX,
                        skip: 0,
                        props: {
                            isRoot: true,
                        },
                    });
                    for (const domain of domains) {
                        try {
                            logger.debug("Ordering SSL for domain: " + domain.fullDomain, {
                                fullDomain: domain.fullDomain,
                            });
                            await this.orderCert(domain);
                        }
                        catch (e) {
                            logger.error(e, {
                                fullDomain: domain.fullDomain,
                            });
                        }
                    }
                    Telemetry.endSpan(span);
                }
                catch (err) {
                    Telemetry.recordExceptionMarkSpanAsErrorAndEndSpan({
                        span,
                        exception: err,
                    });
                    throw err;
                }
            },
        });
    }
    async verifyCnameWhoseCnameisNotVerified() {
        const domains = await this.findBy({
            query: {
                isCnameVerified: false,
            },
            select: {
                _id: true,
                fullDomain: true,
            },
            limit: LIMIT_MAX,
            skip: 0,
            props: {
                isRoot: true,
            },
        });
        for (const domain of domains) {
            try {
                await this.isCnameValid(domain.fullDomain);
            }
            catch (e) {
                logger.error(e, { fullDomain: domain.fullDomain });
            }
        }
    }
    async renewCertsWhichAreExpiringSoon() {
        await GreenlockUtil.renewAllCertsWhichAreExpiringSoon({
            validateCname: async (fullDomain) => {
                return await this.isCnameValid(fullDomain);
            },
            notifyDomainRemoved: async (domain) => {
                await this.updateOneBy({
                    query: {
                        fullDomain: domain,
                    },
                    data: {
                        isSslOrdered: false,
                        isSslProvisioned: false,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                logger.debug(`DomainModel removed from greenlock: ${domain}`, {
                    fullDomain: domain,
                });
            },
        });
    }
    async checkOrderStatus() {
        const domains = await this.findBy({
            query: {
                isSslOrdered: true,
                isCustomCertificate: false,
            },
            select: {
                _id: true,
                fullDomain: true,
                cnameVerificationToken: true,
            },
            limit: LIMIT_MAX,
            skip: 0,
            props: {
                isRoot: true,
            },
        });
        for (const domain of domains) {
            if (!domain.fullDomain) {
                continue;
            }
            const acmeCert = await AcmeCertificateService.findOneBy({
                query: {
                    domain: domain.fullDomain,
                },
                select: {
                    _id: true,
                },
                props: {
                    isRoot: true,
                },
            });
            if (!acmeCert) {
                try {
                    await this.orderCert(domain);
                }
                catch (err) {
                    logger.error("Cannot order cert for domain: " + domain.fullDomain, {
                        fullDomain: domain.fullDomain,
                    });
                    logger.error(err, { fullDomain: domain.fullDomain });
                }
            }
        }
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onBeforeCreate", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onBeforeDelete", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Array]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onDeleteSuccess", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], Service.prototype, "removeDomainFromGreenlock", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [DashboardDomain]),
    __metadata("design:returntype", Promise)
], Service.prototype, "orderCert", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], Service.prototype, "updateSslProvisioningStatusForAllDomains", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "updateCnameStatusForDashboardDomain", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], Service.prototype, "isCnameValid", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [DashboardDomain]),
    __metadata("design:returntype", Promise)
], Service.prototype, "updateSslProvisioningStatus", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], Service.prototype, "orderSSLForDomainsWhichAreNotOrderedYet", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], Service.prototype, "verifyCnameWhoseCnameisNotVerified", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], Service.prototype, "renewCertsWhichAreExpiringSoon", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], Service.prototype, "checkOrderStatus", null);
export default new Service();
//# sourceMappingURL=DashboardDomainService.js.map