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 { IsBillingEnabled, LetsEncryptAccountKey, LetsEncryptNotificationEmail, } from "../../../Server/EnvironmentConfig";
import AcmeCertificateService from "../../Services/AcmeCertificateService";
import AcmeChallengeService from "../../Services/AcmeChallengeService";
import QueryHelper from "../../Types/Database/QueryHelper";
import logger from "../Logger";
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
import LIMIT_MAX from "../../../Types/Database/LimitMax";
import OneUptimeDate from "../../../Types/Date";
import BadDataException from "../../../Types/Exception/BadDataException";
import Exception from "../../../Types/Exception/Exception";
import ServerException from "../../../Types/Exception/ServerException";
import Text from "../../../Types/Text";
import AcmeCertificate from "../../../Models/DatabaseModels/AcmeCertificate";
import AcmeChallenge from "../../../Models/DatabaseModels/AcmeChallenge";
import acme from "acme-client";
import CaptureSpan from "../Telemetry/CaptureSpan";
export default class GreenlockUtil {
    static async renewAllCertsWhichAreExpiringSoon(data) {
        try {
            logger.debug("Renewing all certificates");
            // get all certificates which are expiring soon
            const certificates = await AcmeCertificateService.findBy({
                query: {
                    expiresAt: QueryHelper.lessThanEqualTo(OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), 40)),
                },
                limit: LIMIT_MAX,
                skip: 0,
                select: {
                    domain: true,
                },
                sort: {
                    expiresAt: SortOrder.Ascending,
                },
                props: {
                    isRoot: true,
                },
            });
            logger.debug(`Found ${certificates.length} certificates which are expiring soon`, { certificateCount: certificates.length });
            // order certificate for each domain
            for (const certificate of certificates) {
                if (!certificate.domain) {
                    continue;
                }
                const certLogAttributes = {
                    domain: certificate.domain,
                };
                logger.debug(`Renewing certificate for domain: ${certificate.domain}`, certLogAttributes);
                try {
                    //validate cname
                    const isValidCname = await data.validateCname(certificate.domain);
                    if (!isValidCname) {
                        logger.debug(`CNAME is not valid for domain: ${certificate.domain}`, certLogAttributes);
                        // if cname is not valid then remove the domain
                        await GreenlockUtil.removeDomain(certificate.domain);
                        await data.notifyDomainRemoved(certificate.domain);
                        logger.error(`Cname is not valid for domain: ${certificate.domain}`, certLogAttributes);
                    }
                    else {
                        logger.debug(`CNAME is valid for domain: ${certificate.domain}`, certLogAttributes);
                        await GreenlockUtil.orderCert({
                            domain: certificate.domain,
                            validateCname: data.validateCname,
                        });
                        logger.debug(`Certificate renewed for domain: ${certificate.domain}`, certLogAttributes);
                    }
                }
                catch (e) {
                    logger.error(`Error renewing certificate for domain: ${certificate.domain}`, certLogAttributes);
                    logger.error(e, certLogAttributes);
                }
            }
        }
        catch (e) {
            logger.error("Error renewing all certificates");
            logger.error(e);
            throw e;
        }
    }
    static async removeDomain(domain) {
        try {
            // remove certificate for this domain.
            await AcmeCertificateService.deleteBy({
                query: {
                    domain: domain,
                },
                limit: 1,
                skip: 0,
                props: {
                    isRoot: true,
                },
            });
        }
        catch (err) {
            logger.error(`Error removing domain: ${domain}`, { domain });
            throw err;
        }
    }
    static async orderCert(data) {
        const orderLogAttributes = {
            domain: data.domain,
        };
        try {
            logger.debug(`GreenlockUtil - Ordering certificate for domain: ${data.domain}`, orderLogAttributes);
            let { domain } = data;
            domain = domain.trim().toLowerCase();
            orderLogAttributes["domain"] = domain;
            const acmeAccountKeyInBase64 = LetsEncryptAccountKey;
            if (!acmeAccountKeyInBase64) {
                throw new ServerException("No lets encrypt account key found in environment variables. Please add one.");
            }
            let acmeAccountKey = Buffer.from(acmeAccountKeyInBase64, "base64").toString();
            acmeAccountKey = Text.replaceAll(acmeAccountKey, "\\n", "\n");
            //validate cname
            logger.debug(`Validating cname for domain: ${domain}`, orderLogAttributes);
            const isValidCname = await data.validateCname(domain);
            if (!isValidCname) {
                logger.debug(`CNAME is not valid for domain: ${domain}`, orderLogAttributes);
                logger.debug(`Removing domain: ${domain}`, orderLogAttributes);
                await GreenlockUtil.removeDomain(domain);
                logger.error(`Cname is not valid for domain: ${domain}`, orderLogAttributes);
                throw new BadDataException("Cname is not valid for domain " + domain);
            }
            logger.debug(`Cname is valid for domain: ${domain}`, orderLogAttributes);
            const client = new acme.Client({
                directoryUrl: acme.directory.letsencrypt.production,
                accountKey: acmeAccountKey,
            });
            const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
                commonName: domain,
            });
            logger.debug(`Ordering certificate for domain: ${domain}`, orderLogAttributes);
            const certificate = await client.auto({
                csr: certificateRequest,
                email: LetsEncryptNotificationEmail.toString(),
                termsOfServiceAgreed: true,
                challengePriority: ["http-01"], // only http-01 challenge is supported by oneuptime
                challengeCreateFn: async (authz, challenge, keyAuthorization) => {
                    // Satisfy challenge here
                    /* http-01 */
                    if (challenge.type === "http-01") {
                        logger.debug(`Creating challenge for domain: ${authz.identifier.value}`, orderLogAttributes);
                        const acmeChallenge = new AcmeChallenge();
                        acmeChallenge.challenge = keyAuthorization;
                        acmeChallenge.token = challenge.token;
                        acmeChallenge.domain = authz.identifier.value;
                        await AcmeChallengeService.create({
                            data: acmeChallenge,
                            props: {
                                isRoot: true,
                            },
                        });
                        logger.debug(`Challenge created for domain: ${authz.identifier.value}`, orderLogAttributes);
                    }
                },
                challengeRemoveFn: async (authz, challenge) => {
                    // Clean up challenge here
                    logger.debug(`Removing challenge for domain: ${authz.identifier.value}`, orderLogAttributes);
                    if (challenge.type === "http-01") {
                        await AcmeChallengeService.deleteBy({
                            query: {
                                domain: authz.identifier.value,
                            },
                            limit: 1,
                            skip: 0,
                            props: {
                                isRoot: true,
                            },
                        });
                    }
                    logger.debug(`Challenge removed for domain: ${authz.identifier.value}`, orderLogAttributes);
                },
            });
            logger.debug(`Certificate ordered for domain: ${domain}`, orderLogAttributes);
            // get expires at date from certificate
            const cert = acme.crypto.readCertificateInfo(certificate);
            const issuedAt = cert.notBefore;
            const expiresAt = cert.notAfter;
            logger.debug(`Certificate expires at: ${expiresAt}`, orderLogAttributes);
            logger.debug(`Certificate issued at: ${issuedAt}`, orderLogAttributes);
            // check if the certificate is already in the database.
            const existingCertificate = await AcmeCertificateService.findOneBy({
                query: {
                    domain: domain,
                },
                select: {
                    _id: true,
                },
                props: {
                    isRoot: true,
                },
            });
            if (existingCertificate) {
                logger.debug(`Updating certificate for domain: ${domain}`, orderLogAttributes);
                // update the certificate
                await AcmeCertificateService.updateBy({
                    query: {
                        domain: domain,
                    },
                    limit: 1,
                    skip: 0,
                    data: {
                        certificate: certificate.toString(),
                        certificateKey: certificateKey.toString(),
                        issuedAt: issuedAt,
                        expiresAt: expiresAt,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                logger.debug(`Certificate updated for domain: ${domain}`, orderLogAttributes);
            }
            else {
                logger.debug(`Creating certificate for domain: ${domain}`, orderLogAttributes);
                // create the certificate
                const acmeCertificate = new AcmeCertificate();
                acmeCertificate.domain = domain;
                acmeCertificate.certificate = certificate.toString();
                acmeCertificate.certificateKey = certificateKey.toString();
                acmeCertificate.issuedAt = issuedAt;
                acmeCertificate.expiresAt = expiresAt;
                await AcmeCertificateService.create({
                    data: acmeCertificate,
                    props: {
                        isRoot: true,
                    },
                });
                logger.debug(`Certificate created for domain: ${domain}`, orderLogAttributes);
            }
        }
        catch (e) {
            logger.error(`Error ordering certificate for domain: ${data.domain}`, orderLogAttributes);
            logger.error(e, orderLogAttributes);
            if (e instanceof Exception) {
                throw e;
            }
            if (IsBillingEnabled) {
                throw new ServerException(`Unable to order certificate for ${data.domain}. Please contact support at support@oneuptime.com for more information.`);
            }
            else {
                throw new ServerException(`Unable to order certificate for ${data.domain}. Please make sure that your server can be accessed publicly over port 80 (HTTP) and port 443 (HTTPS). If the problem persists, please refer to server logs for more information. Please also set up LOG_LEVEL=DEBUG to get more detailed server logs.`);
            }
        }
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], GreenlockUtil, "renewAllCertsWhichAreExpiringSoon", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], GreenlockUtil, "removeDomain", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], GreenlockUtil, "orderCert", null);
//# sourceMappingURL=Greenlock.js.map