"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthorizerFactory = exports.K_WARN_NO_AUTHORIZATION_PROTOCOL = void 0;
const express_jwt_1 = require("express-jwt");
const typescript_optional_1 = require("typescript-optional");
const cactus_common_1 = require("@hyperledger/cactus-common");
const cactus_core_api_1 = require("@hyperledger/cactus-core-api");
const cactus_core_1 = require("@hyperledger/cactus-core");
const authorization_protocol_1 = require("../config/authorization-protocol");
const is_express_jwt_options_type_guard_1 = require("./is-express-jwt-options-type-guard");
exports.K_WARN_NO_AUTHORIZATION_PROTOCOL = "The API server configuration specified no authorization protocol. " +
    "All endpoints are exposed and unprotected. Do not use this in production!";
class AuthorizerFactory {
    get isInitialized() {
        return this.initialized;
    }
    get className() {
        return AuthorizerFactory.CLASS_NAME;
    }
    constructor(opts) {
        this.opts = opts;
        this._unprotectedEndpoints = [];
        this.initialized = false;
        const fnTag = `${this.className}#constructor()`;
        cactus_common_1.Checks.truthy(opts, `${fnTag} arg options`);
        cactus_common_1.Checks.truthy(opts.pluginRegistry, `${fnTag} options.pluginRegistry`);
        cactus_common_1.Checks.truthy(opts.pluginRegistry instanceof cactus_core_1.PluginRegistry, `${fnTag} opts.pluginRegistry instanceof PluginRegistry`);
        this.pluginRegistry = opts.pluginRegistry;
        this.plugins = this.pluginRegistry
            .getPlugins()
            .filter((p) => (0, cactus_core_api_1.isIPluginWebService)(p));
        const level = this.opts.logLevel || "INFO";
        const label = this.className;
        this.log = cactus_common_1.LoggerProvider.getOrCreate({ level, label });
        this.log.debug(`Instantiated ${this.className} OK`);
    }
    async createExpressJwtMiddleware(authzConf) {
        const fnTag = `${this.className}#createExpressJwtMiddleware()`;
        const { log } = this;
        const { E_BAD_EXPRESS_JWT_OPTIONS } = AuthorizerFactory;
        const { expressJwtOptions, socketIoPath } = authzConf;
        if (!(0, is_express_jwt_options_type_guard_1.isExpressJwtOptions)(expressJwtOptions)) {
            throw new Error(`${fnTag}: ${E_BAD_EXPRESS_JWT_OPTIONS}`);
        }
        const options = Object.assign({ audience: "org.hyperledger.cactus" }, expressJwtOptions);
        const unprotectedEndpoints = this.unprotectedEndpoints.map((e) => {
            const verbLowerCase = e.getVerbLowerCase();
            // including both cases of the HTTP verbs because upgrading the
            // express-unless dependency (through the upgrade of express-jwt)
            // broke the test cases that were verifying if the exemptions are
            // enforced correctly.
            // This test breakage was traced back to express-unless not realizing
            // that there is a match of verbs if they are differently cased.
            const methods = [verbLowerCase.toUpperCase(), verbLowerCase];
            // type pathFilter = string | RegExp | { url: string | RegExp, methods?: string[], method?: string | string[] };
            return { url: e.getPath(), methods };
        });
        if (socketIoPath) {
            log.info("SocketIO path configuration detected: %o", socketIoPath);
            // const exemption = { url: new RegExp(`${socketIoPath}.*`), method: "get" };
            // unprotectedEndpoints.push(exemption as any);
            log.info("Exempted SocketIO path from express-jwt authorization. Using @thream/socketio-jwt instead)");
        }
        return (0, express_jwt_1.expressjwt)(options).unless({ path: unprotectedEndpoints });
    }
    /**
     * Creates an ExpressJS middleware (a request handler) that can be hooked
     * up to the Express web application with authorization as per the API
     * server configuration.
     * It is assumed by this method that the plugin registry has already been
     * initialized and now is containing the list of plugins that can be filtered
     * down to the list of plugins that also expose web services (without which
     * the authorization will not work properly)
     */
    async createMiddleware(authorizationProtocol, authzConf) {
        const fnTag = `${this.className}#createAuthzMiddleware()`;
        const { log } = this;
        switch (authorizationProtocol) {
            case authorization_protocol_1.AuthorizationProtocol.NONE: {
                log.warn(exports.K_WARN_NO_AUTHORIZATION_PROTOCOL);
                return typescript_optional_1.Optional.empty();
            }
            case authorization_protocol_1.AuthorizationProtocol.JSON_WEB_TOKEN: {
                const handler = await this.createExpressJwtMiddleware(authzConf);
                return typescript_optional_1.Optional.ofNonNull(handler);
            }
            default: {
                const acceptedCsv = Object.values(authorization_protocol_1.AuthorizationProtocol).join(",");
                const msg = `${fnTag} Unknown authz protocol: ${authorizationProtocol} ` +
                    `Accepted values: ${acceptedCsv}`;
                throw new Error(msg);
            }
        }
    }
    async initOnce() {
        if (this.isInitialized) {
            return;
        }
        const { log } = this;
        log.debug(`Initializing ${this.className}...`);
        const endpoints = await this.getEndpoints();
        this._unprotectedEndpoints = await this.findUnprotectedEndpoints(endpoints);
        log.debug(`Initialized ${this.className} OK`);
        this.initialized = true;
    }
    async getEndpoints() {
        const { plugins } = this;
        const promises = plugins.map((p) => p.getOrCreateWebServices());
        const endpoints = await Promise.all(promises);
        return endpoints.flat();
    }
    get unprotectedEndpoints() {
        const fnTag = `${this.className}#unprotectedEndpoints()`;
        if (this.isInitialized) {
            return this._unprotectedEndpoints;
        }
        else {
            throw new Error(`${fnTag} Need to call #initOnce() at least once.`);
        }
    }
    async findUnprotectedEndpoints(endpoints) {
        const fnTag = `${this.className}#findUnprotectedEndpoints()`;
        cactus_common_1.Checks.truthy(Array.isArray(endpoints), `${fnTag} isArray(endpoints)`);
        const unprotectedEndpoints = [];
        for (const ep of endpoints) {
            const authzOptionsProvider = await ep.getAuthorizationOptionsProvider();
            const { isProtected: isProtected } = await authzOptionsProvider.get();
            if (!isProtected) {
                unprotectedEndpoints.push(ep);
            }
        }
        return unprotectedEndpoints;
    }
}
exports.AuthorizerFactory = AuthorizerFactory;
AuthorizerFactory.CLASS_NAME = "AuthorizerFactory";
AuthorizerFactory.E_BAD_EXPRESS_JWT_OPTIONS = `Need valid express-jwt middleware configuration object. ` +
    `See the documentation of the express-jwt ` +
    `npm package for further details about what might be missing!`;
//# sourceMappingURL=data:application/json;base64, 