"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigService = void 0;
const fs_1 = require("fs");
const convict_1 = __importDefault(require("convict"));
const convict_format_with_validator_1 = require("convict-format-with-validator");
const uuid_1 = require("uuid");
const jose_1 = require("jose");
const cactus_common_1 = require("@hyperledger/cactus-common");
const cactus_core_api_1 = require("@hyperledger/cactus-core-api");
const convict_plugin_array_format_1 = require("./convict-plugin-array-format");
const self_signed_pki_generator_1 = require("./self-signed-pki-generator");
const authorization_protocol_1 = require("./authorization-protocol");
convict_1.default.addFormat(convict_plugin_array_format_1.FORMAT_PLUGIN_ARRAY);
convict_1.default.addFormat(convict_format_with_validator_1.ipaddress);
class ConfigService {
    static getHelpText() {
        const schema = ConfigService.getConfigSchema();
        const argsHelpText = Object.keys(schema)
            .map((optionName) => `  ${optionName}:` +
            `\n\t\tDescription: ${schema[optionName].doc}` +
            `\n\t\tDefault: ${schema[optionName].default ||
                "Mandatory parameter without a default value."}` +
            `\n\t\tEnv: ${schema[optionName].env}` +
            `\n\t\tCLI: --${schema[optionName].arg}`)
            .join("\n");
        return this.HELP_TEXT_HEADER.concat(argsHelpText).concat("\n\n");
    }
    static getConfigSchema() {
        return {
            crpcHost: {
                doc: "The host to bind the CRPC fastify instance to. Secure default is: 127.0.0.1. Use 0.0.0.0 to bind for any host.",
                format: "ipaddress",
                env: "CRPC_HOST",
                arg: "crpc-host",
                default: "127.0.0.1",
            },
            crpcPort: {
                doc: "The HTTP port to bind the CRPC fastify server endpoints to.",
                format: "port",
                env: "CRPC_PORT",
                arg: "crpc-port",
                default: 6000,
            },
            pluginManagerOptionsJson: {
                doc: "Can be used to override npm registry and authentication details for example. See https://www.npmjs.com/package/live-plugin-manager#pluginmanagerconstructoroptions-partialpluginmanageroptions for further details.",
                format: "*",
                default: "{}",
                env: "PLUGIN_MANAGER_OPTIONS_JSON",
                arg: "plugin-manager-options-json",
            },
            authorizationProtocol: {
                doc: "The name of the authorization protocol to use. Accepted values" +
                    `are: ${Object.values(authorization_protocol_1.AuthorizationProtocol).join(",")}`,
                format: (value) => {
                    ConfigService.formatNonBlankString(value);
                    const accepted = Object.values(authorization_protocol_1.AuthorizationProtocol);
                    const acceptedCsv = accepted.join(",");
                    if (!accepted.includes(value)) {
                        const m = `Accepted auth protocols: ${acceptedCsv} Got: ${value}`;
                        throw new Error(m);
                    }
                },
                default: null,
                env: "AUTHORIZATION_PROTOCOL",
                arg: "authorization-protocol",
            },
            authorizationConfigJson: {
                doc: "The JSON string to deserialize when configuring authorization.",
                default: null,
                format: Object,
                env: "AUTHORIZATION_CONFIG_JSON",
                arg: "authorization-config-json",
            },
            plugins: {
                doc: "A collection of plugins to load at runtime.",
                format: "plugin-array",
                default: [],
                env: "PLUGINS",
                arg: "plugins",
                pluginSchema: {
                    action: {
                        format: (value) => {
                            ConfigService.formatNonBlankString(value);
                            const accepted = Object.values(cactus_core_api_1.PluginImportAction);
                            const acceptedCsv = accepted.join(",");
                            if (!accepted.includes(value)) {
                                const m = `Accepted plugin import actions: ${acceptedCsv} Got: ${value}`;
                                throw new Error(m);
                            }
                        },
                        default: cactus_core_api_1.PluginImportAction.Install,
                    },
                    options: {
                        format: Object,
                        default: {},
                    },
                    packageName: "*",
                    type: {
                        format: (value) => {
                            ConfigService.formatNonBlankString(value);
                            const accepted = Object.values(cactus_core_api_1.PluginImportType);
                            const acceptedCsv = accepted.join(",");
                            if (!accepted.includes(value)) {
                                const m = `Accepted plugin import types: ${acceptedCsv} Got: ${value}`;
                                throw new Error(m);
                            }
                        },
                        default: cactus_core_api_1.PluginImportType.Local,
                    },
                },
            },
            configFile: {
                doc: "The path to a config file that holds the configuration itself which will be parsed and validated.",
                format: "*",
                default: "",
                env: "CONFIG_FILE",
                arg: "config-file",
            },
            logLevel: {
                doc: "The level at which loggers should be configured. Supported values include the following: " +
                    "error, warn, info, debug, trace",
                format: ConfigService.formatNonBlankString,
                default: "warn",
                env: "LOG_LEVEL",
                arg: "log-level",
            },
            minNodeVersion: {
                doc: "Determines the lower bound of NodeJS version that the API " +
                    "server will be willing to start on. Defaults to v12 because v10 " +
                    "does not support TLS v1.3. If you must run on Node 10, just set " +
                    "this configuration parameter to 10.0.0 for example.",
                format: ConfigService.formatNonBlankString,
                default: "12.0.0",
                env: "MIN_NODE_VERSION",
                arg: "min-node-version",
            },
            tlsDefaultMaxVersion: {
                doc: "Sets the DEFAULT_MAX_VERSION property of the built-in tls module of NodeJS. " +
                    "Only makes a difference on NOdeJS 10 and older where TLS v1.3 is turned off by default. " +
                    "Newer NodeJS versions ship with TLS v1.3 enabled.",
                format: (version) => {
                    ConfigService.formatNonBlankString(version);
                    const versions = ["TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1"];
                    if (!versions.includes(version)) {
                        const msg = `OK TLS versions ${versions.join(",")} Got: ${version}`;
                        throw new Error(msg);
                    }
                },
                default: "TLSv1.3",
                env: "TLS_DEFAULT_MAX_VERSION",
                arg: "tls-default-max-version",
            },
            cockpitEnabled: {
                doc: "Enable Cockpit server.",
                format: Boolean,
                env: "COCKPIT_ENABLED",
                arg: "cockpit-enabled",
                default: false,
            },
            cockpitHost: {
                doc: "The host to bind the Cockpit webserver to. Secure default is: 127.0.0.1. Use 0.0.0.0 to bind for any host.",
                format: "ipaddress",
                default: "127.0.0.1",
                env: "COCKPIT_HOST",
                arg: "cockpit-host",
            },
            cockpitPort: {
                doc: "The HTTP port to bind the Cockpit webserver to.",
                format: "port",
                env: "COCKPIT_PORT",
                arg: "cockpit-port",
                default: 3000,
            },
            cockpitWwwRoot: {
                doc: "The file-system path pointing to the static files of web application served as the cockpit by the API server.",
                format: "*",
                env: "COCKPIT_WWW_ROOT",
                arg: "cockpit-www-root",
                default: "packages/cactus-cmd-api-server/node_modules/@hyperledger/cactus-cockpit/www/",
            },
            cockpitCorsDomainCsv: {
                doc: "The Comma seperated list of domains to allow Cross Origin Resource Sharing from when " +
                    "serving static file requests. The wildcard (*) character is supported to allow CORS for any and all domains",
                format: "*",
                env: "COCKPIT_CORS_DOMAIN_CSV",
                arg: "cockpit-cors-domain-csv",
                default: "",
            },
            cockpitTlsEnabled: {
                doc: "Enable TLS termination on the server. Useful if you do not have/want to " +
                    "have a reverse proxy or load balancer doing the SSL/TLS termination in your environment.",
                format: Boolean,
                env: "COCKPIT_TLS_ENABLED",
                arg: "cockpit-tls-enabled",
                default: true,
            },
            cockpitApiProxyRejectUnauthorized: {
                doc: "When false: accept self signed certificates while proxying from the cockpit host " +
                    "to the API host. Acceptable for development environments, never use it in production.",
                format: Boolean,
                env: "COCKPIT_API_PROXY_REJECT_UNAUTHORIZED",
                arg: "cockpit-api-proxy-reject-unauthorized",
                default: true,
            },
            cockpitMtlsEnabled: {
                doc: "Enable mTLS so that only clients presenting valid TLS certificate of " +
                    "their own will be able to connect to the cockpit",
                format: Boolean,
                env: "COCKPIT_MTLS_ENABLED",
                arg: "cockpit-mtls-enabled",
                default: true,
            },
            cockpitTlsCertPem: {
                doc: "Either the file path to the cert file or the contents of it. Value is checked for existence on the file " +
                    "system as a path. If the latter comes back negative then value is assumed to be the actual pem string.",
                format: ConfigService.filePathOrFileContents,
                env: "COCKPIT_TLS_CERT_PEM",
                arg: "cockpit-tls-cert-pem",
                default: null,
            },
            cockpitTlsKeyPem: {
                sensitive: true,
                doc: "Either the file path to the key file or the contents of it. Value is checked for existence on the file " +
                    "system as a path. If the latter comes back negative then value is assumed to be the actual pem string.",
                format: ConfigService.filePathOrFileContents,
                env: "COCKPIT_TLS_KEY_PEM",
                arg: "cockpit-tls-key-pem",
                default: null,
            },
            cockpitTlsClientCaPem: {
                doc: "Either the client cert file pat or the contents of it. Value is checked for existence on the file " +
                    "system as a path. If the latter comes back negative then value is assumed to be the actual pem string.",
                format: ConfigService.filePathOrFileContents,
                env: "COCKPIT_TLS_CLIENT_CA_PEM",
                arg: "cockpit-tls-client-ca-pem",
                default: null,
            },
            apiHost: {
                doc: "The host to bind the API to. Secure default is: 127.0.0.1. Use 0.0.0.0 to bind for any host.",
                format: "ipaddress",
                env: "API_HOST",
                arg: "api-host",
                default: "127.0.0.1",
            },
            apiPort: {
                doc: "The HTTP port to bind the API server endpoints to.",
                format: "port",
                env: "API_PORT",
                arg: "api-port",
                default: 4000,
            },
            apiCorsDomainCsv: {
                doc: "The Comma seperated list of domains to allow Cross Origin Resource Sharing from when " +
                    "serving API requests. The wildcard (*) character is supported to allow CORS for any and all domains, " +
                    "however using it is not recommended unless you are developing or demonstrating something with Cactus.",
                format: "*",
                env: "API_CORS_DOMAIN_CSV",
                arg: "api-cors-domain-csv",
                default: "",
            },
            apiTlsEnabled: {
                doc: "Enable TLS termination on the server. Useful if you do not have/want to " +
                    "have a reverse proxy or load balancer doing the SSL/TLS termination in your environment.",
                format: Boolean,
                env: "API_TLS_ENABLED",
                arg: "api-tls-enabled",
                default: true,
            },
            apiMtlsEnabled: {
                doc: "Enable mTLS so that only clients presenting valid TLS certificate of " +
                    "their own will be able to connect to the web APIs",
                format: Boolean,
                env: "API_MTLS_ENABLED",
                arg: "api-mtls-enabled",
                default: true,
            },
            apiTlsCertPem: {
                doc: "Either the file path to the cert file or the contents of it. Value is checked for existence on the file " +
                    "system as a path. If the latter comes back negative then value is assumed to be the actual pem string.",
                format: ConfigService.filePathOrFileContents,
                env: "API_TLS_CERT_PEM",
                arg: "api-tls-cert-pem",
                default: null,
            },
            apiTlsClientCaPem: {
                doc: "Either the client cert file pat or the contents of it. Value is checked for existence on the file " +
                    "system as a path. If the latter comes back negative then value is assumed to be the actual pem string.",
                format: ConfigService.filePathOrFileContents,
                env: "API_TLS_CLIENT_CA_PEM",
                arg: "api-tls-client-ca-pem",
                default: null,
            },
            apiTlsKeyPem: {
                sensitive: true,
                doc: "Either the file path to the key file or the contents of it. Value is checked for existence on the file " +
                    "system as a path. If the latter comes back negative then value is assumed to be the actual pem string.",
                format: ConfigService.filePathOrFileContents,
                env: "API_TLS_KEY_PEM",
                arg: "api-tls-key-pem",
                default: null,
            },
            grpcPort: {
                doc: "The gRPC port to serve web services on.",
                format: "port",
                env: "GRPC_PORT",
                arg: "grpc-port",
                default: 5000,
            },
            grpcMtlsEnabled: {
                doc: "Enable TLS termination on the grpc server. Useful if you do not have/want to " +
                    "have a reverse proxy or load balancer doing the SSL/TLS termination in your environment.",
                format: Boolean,
                env: "GRPC_TLS_ENABLED",
                arg: "grpc-tls-enabled",
                default: true,
            },
            enableShutdownHook: {
                doc: "It will cause the API server to listen to OS process signals and will attempt " +
                    "to gracefully shut itself down in response to these when the flag is enabled " +
                    "(which is the default behavior). You will want to turn this off if you are embedding " +
                    "the API server in your own application and would like to stop the API server from " +
                    "meddling in the OS process signal handling when you take care of it yourself in your own code.",
                env: "ENABLE_SHUTDOWN_HOOK",
                arg: "enable-shutdown-hook",
                format: Boolean,
                default: true,
            },
        };
    }
    static formatNonBlankString(value) {
        if (typeof value === "undefined" || value === null || value === "") {
            throw new Error("must be a non-empty string");
        }
    }
    static filePathOrFileContents(value) {
        ConfigService.formatNonBlankString(value);
        if ((0, fs_1.existsSync)(value)) {
            return (0, fs_1.readFileSync)(value);
        }
        else {
            return value;
        }
    }
    /**
     * Remaps the example config returned by `newExampleConfig()` into a similar object whose keys are the designated
     * environment variable names. As an example it returns something like this:
     *
     * ```json
     * {
     *   "HTTP_PORT": "3000"
     * }
     * ```
     *
     * Where the output of `newExampleConfig()` would be something like this (example)
     *
     * ```json
     * {
     *   "httpPort": "3000"
     * }
     * ```
     */
    async newExampleConfigEnv(cactusApiServerOptions) {
        cactusApiServerOptions =
            cactusApiServerOptions || (await this.newExampleConfig());
        const configSchema = ConfigService.getConfigSchema();
        return Object.entries(cactusApiServerOptions).reduce((acc, [key, value]) => {
            const schemaObj = configSchema[key];
            acc[schemaObj.env] = value;
            return acc;
        }, {});
    }
    async newExampleConfigConvict(cactusApiServerOptions, overrides) {
        cactusApiServerOptions =
            cactusApiServerOptions || (await this.newExampleConfig());
        const env = await this.newExampleConfigEnv(cactusApiServerOptions);
        const conf = overrides ? this.create({ env }) : this.getOrCreate({ env });
        return conf;
    }
    async newExampleConfig() {
        const schema = ConfigService.getConfigSchema();
        const apiTlsEnabled = schema.apiTlsEnabled.default;
        const apiHost = schema.apiHost.default;
        const apiPort = schema.apiPort.default;
        const apiProtocol = apiTlsEnabled ? "https:" : "http";
        const apiBaseUrl = `${apiProtocol}//${apiHost}:${apiPort}`;
        const grpcPort = schema.grpcPort.default;
        const grpcMtlsEnabled = schema.grpcMtlsEnabled.default;
        const enableShutdownHook = schema.enableShutdownHook.default;
        const keyPair = await (0, jose_1.generateKeyPair)("ES256K");
        const keyPairPem = await (0, jose_1.exportPKCS8)(keyPair.privateKey);
        const publicKeyPem = await (0, jose_1.exportSPKI)(keyPair.publicKey);
        const memberId1 = "Cactus_Example_Consortium_Member_1";
        const nodeId1 = "Cactus_Example_Consortium_Node_1";
        const consortiumDatabase = {
            cactusNode: [
                {
                    consortiumId: "Cactus_Example_Consortium",
                    id: nodeId1,
                    ledgerIds: [],
                    memberId: memberId1,
                    pluginInstanceIds: [],
                    nodeApiHost: apiBaseUrl,
                    publicKeyPem: publicKeyPem,
                },
            ],
            consortiumMember: [
                {
                    id: memberId1,
                    name: "Example Cactus Consortium Member 1",
                    nodeIds: [nodeId1],
                },
            ],
            ledger: [],
            pluginInstance: [],
            consortium: [
                {
                    name: "Example Cactus Consortium",
                    id: (0, uuid_1.v4)(),
                    mainApiHost: apiBaseUrl,
                    memberIds: [memberId1],
                },
            ],
        };
        const cockpitTlsEnabled = schema.cockpitTlsEnabled.default;
        const cockpitHost = schema.cockpitHost.default;
        const cockpitPort = schema.cockpitPort.default;
        const pkiGenerator = new self_signed_pki_generator_1.SelfSignedPkiGenerator();
        const pkiServer = pkiGenerator.create("127.0.0.1");
        const plugins = [
            {
                packageName: "@hyperledger/cactus-plugin-keychain-memory",
                type: cactus_core_api_1.PluginImportType.Local,
                action: cactus_core_api_1.PluginImportAction.Install,
                options: {
                    instanceId: (0, uuid_1.v4)(),
                    keychainId: (0, uuid_1.v4)(),
                },
            },
            {
                packageName: "@hyperledger/cactus-plugin-consortium-manual",
                type: cactus_core_api_1.PluginImportType.Local,
                action: cactus_core_api_1.PluginImportAction.Install,
                options: {
                    instanceId: (0, uuid_1.v4)(),
                    keyPairPem,
                    consortiumDatabase,
                },
            },
        ];
        const jwtSecret = (0, uuid_1.v4)();
        const expressJwtOptions = {
            secret: jwtSecret,
            algorithms: ["RS256"],
            audience: "org.hyperledger.cactus.jwt.audience",
            issuer: "org.hyperledger.cactus.jwt.issuer",
        };
        const authorizationConfigJson = {
            socketIoPath: cactus_core_api_1.Constants.SocketIoConnectionPathV1,
            unprotectedEndpointExemptions: [],
            socketIoJwtOptions: {
                secret: jwtSecret,
            },
            expressJwtOptions,
        };
        return {
            crpcHost: schema.crpcHost.default,
            crpcPort: schema.crpcPort.default,
            pluginManagerOptionsJson: "{}",
            authorizationProtocol: authorization_protocol_1.AuthorizationProtocol.JSON_WEB_TOKEN,
            authorizationConfigJson,
            configFile: ".config.json",
            logLevel: "debug",
            minNodeVersion: schema.minNodeVersion.default,
            tlsDefaultMaxVersion: "TLSv1.3",
            apiHost,
            apiPort,
            apiCorsDomainCsv: schema.apiCorsDomainCsv.default,
            apiMtlsEnabled: false,
            cockpitApiProxyRejectUnauthorized: true,
            apiTlsEnabled,
            apiTlsCertPem: pkiServer.certificatePem,
            apiTlsKeyPem: pkiServer.privateKeyPem,
            apiTlsClientCaPem: "-", // API mTLS is off so this will not crash the server
            grpcPort,
            grpcMtlsEnabled,
            cockpitEnabled: schema.cockpitEnabled.default,
            cockpitHost,
            cockpitPort,
            cockpitWwwRoot: schema.cockpitWwwRoot.default,
            cockpitCorsDomainCsv: schema.cockpitCorsDomainCsv.default,
            cockpitMtlsEnabled: false,
            cockpitTlsEnabled,
            cockpitTlsCertPem: pkiServer.certificatePem,
            cockpitTlsKeyPem: pkiServer.privateKeyPem,
            cockpitTlsClientCaPem: "-", // Cockpit mTLS is off so this will not crash the server
            plugins,
            enableShutdownHook,
        };
    }
    async getOrCreate(options) {
        if (!ConfigService.config) {
            this.create(options);
        }
        return ConfigService.config;
    }
    create(options) {
        const schema = ConfigService.getConfigSchema();
        ConfigService.config = convict_1.default(schema, options);
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        if (ConfigService.config.get("configFile")) {
            const configFilePath = ConfigService.config.get("configFile");
            ConfigService.config.loadFile(configFilePath);
        }
        ConfigService.config.validate();
        // this validation fails with supply-chain default configuration
        // and it will be removed
        // this.validateKeyPairMatch();
        const level = ConfigService.config.get("logLevel");
        const logger = cactus_common_1.LoggerProvider.getOrCreate({
            label: "config-service",
            level,
        });
        logger.info("Configuration validation OK.");
        return ConfigService.config;
    }
}
exports.ConfigService = ConfigService;
ConfigService.HELP_TEXT_HEADER = `Order of precedent for parameters in descdending order: CLI, Environment variables, Configuration file.\n` +
    `Passing "help" as the first argument prints this message and also dumps the effective configuration.\n\n` +
    `Configuration Parameters\n` +
    `========================\n\n`;
//# sourceMappingURL=data:application/json;base64, 