"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isDebugModeMiddlewareMultiSigLow = exports.isDebugModeMiddlewareMultiSigMedium = exports.isDebugModeMiddlewareMultiSigHigh = exports.isDebugModeMiddlewareMultiSig = exports.isDebugModeMiddlewareHigh = exports.isDebugModeMiddlewareMedium = exports.isDebugModeMiddlewareLow = exports.isDebugModeMiddleware = exports.stripQueryParams = void 0;
const debug_1 = require("../debug");
const Context = __importStar(require("../p2p/Context"));
const crypto = __importStar(require("@shardeum-foundation/lib-crypto-utils"));
const shardus_types_1 = require("../shardus/shardus-types");
const server_1 = __importDefault(require("../config/server"));
const logger_1 = require("../logger");
const nestedCounters_1 = require("../utils/nestedCounters");
const lib_types_1 = require("@shardeum-foundation/lib-types");
const CycleChain = __importStar(require("../p2p/CycleChain"));
const Self_1 = require("../p2p/Self");
const P2PTypes_1 = require("@shardeum-foundation/lib-types/build/src/p2p/P2PTypes");
const Sync_1 = require("../p2p/Sync");
const utils_1 = require("../utils");
const MAX_COUNTER_BUFFER_MILLISECONDS = 10000; // <- Nonce are essentially just timestamp. This number dictate how much time range we will tolerance.
let lastCounter = Date.now(); // <- when node is first load onto mem this used to be 0, but that would cause a replay attack. So now it is set to the current time with ntp offset accounted
let multiSigLstCounter = Date.now();
// This function is used to check if the request is authorized to access the debug endpoint
async function handleDebugAuth(_req, res, next, authLevel) {
    let unauthorizedDBG = '';
    try {
        let statusHist = (0, Self_1.getStatusHistoryCopy)();
        let statusNow = statusHist[statusHist.length - 1].moduleStatus || undefined;
        let weAreActive = statusNow === P2PTypes_1.NodeStatus.ACTIVE;
        let latestCycle = CycleChain.newest;
        if (!weAreActive) {
            const activeNodes = await (0, Self_1.contactArchiver)('dbgMiddleware');
            latestCycle = await (0, Sync_1.getNewestCycle)(activeNodes);
        }
        if (!latestCycle) {
            res.status(500).json({ error: "Node can't gather latest Cycle to perform signature verification" });
        }
        //auth with a signature
        if (_req.query.sig != null && _req.query.sig_counter != null) {
            const nodes = String(_req.query.nodePubkeys).split(',');
            const ourPubkey = Context.crypto.getPublicKey().slice(0, 4);
            let intentedForOurNode = false;
            nodes.forEach((id) => {
                if (ourPubkey === id) {
                    intentedForOurNode = true;
                }
            });
            if (!intentedForOurNode) {
                unauthorizedDBG = `ourPubkey: ${ourPubkey} not found in ${_req.query.nodePubkeys}`;
                return res.status(401).json({
                    status: 401,
                    message: 'Unauthorized! 1',
                    dbg: unauthorizedDBG,
                });
            }
            // trim sig and sig_counter from the originalUrl
            // when the debug call is signed it include the hash of baseurl and counter to prevent replay at later time and replay at different node or the same endpoint different query params
            let payload = {
                route: stripQueryParams(_req.originalUrl, ['sig', 'sig_counter', 'nodePubkeys']),
                count: _req.query.sig_counter,
                nodes: _req.query.nodePubkeys,
                networkId: latestCycle.networkId,
                cycleCounter: latestCycle.counter,
            };
            const hash = crypto.hash(lib_types_1.Utils.safeStringify(payload));
            const devPublicKeys = (0, debug_1.getDevPublicKeys)(); // This should return list of public keys
            const requestSig = _req.query.sig;
            // Check if signature is valid for any of the public keys
            for (const ownerPk in devPublicKeys) {
                const sign = { owner: ownerPk, sig: requestSig };
                const hashIncluded = {
                    route: payload.route,
                    count: payload.count,
                    nodes: payload.nodes,
                    networkId: payload.networkId,
                    cycleCounter: payload.cycleCounter,
                    requestHash: hash,
                    sign,
                };
                //reguire a larger counter than before. This prevents replay attacks
                const currentCounter = parseInt(payload.count);
                const currentTime = Date.now();
                if (currentCounter > lastCounter && currentCounter <= currentTime + MAX_COUNTER_BUFFER_MILLISECONDS) {
                    let verified = Context.crypto.verify(hashIncluded, hashIncluded.sign.owner);
                    if (verified === true) {
                        const authorized = (0, debug_1.ensureKeySecurity)(ownerPk, authLevel);
                        if (authorized) {
                            lastCounter = currentCounter;
                            next();
                            return;
                        }
                        else {
                            /* prettier-ignore */ if (logger_1.logFlags.verbose)
                                console.log('Authorization failed for security level', authLevel);
                            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('security', 'Authorization failed for security level: ', authLevel);
                            return res.status(403).json({
                                status: 403,
                                message: 'FORBIDDEN!',
                            });
                        }
                    }
                    else {
                        /* prettier-ignore */ if (logger_1.logFlags.verbose)
                            console.log('Signature is not correct');
                        unauthorizedDBG = `Signature is not correct: ${hashIncluded.sign.sig}`;
                    }
                }
                else {
                    if (logger_1.logFlags.verbose) {
                        const parsedCounter = parseInt(hashIncluded.count);
                        if (Number.isNaN(parsedCounter)) {
                            console.log('Counter is not a number');
                        }
                        else {
                            console.log('Counter is not larger than last counter', parsedCounter, lastCounter);
                        }
                    }
                    const parsedCounter = parseInt(hashIncluded.count);
                    if (Number.isNaN(parsedCounter)) {
                        unauthorizedDBG = `Counter is not a number: ${hashIncluded.count}`;
                    }
                    else {
                        unauthorizedDBG = `Counter is not larger than last counter: ${parsedCounter} <= ${lastCounter}`;
                    }
                }
            }
        }
        else {
            unauthorizedDBG = `sig or counter missing. sig: ${_req.query.sig} sig_counter: ${_req.query.sig_counter}`;
        }
    }
    catch (error) {
        /* prettier-ignore */ if (logger_1.logFlags.verbose)
            console.log('Error in handleDebugAuth:', error);
        nestedCounters_1.nestedCountersInstance.countEvent('security', 'debug unauthorized failure - exception caught');
        unauthorizedDBG = `Exception caught: ${(0, utils_1.formatErrorMessage)(error)}`;
    }
    return res.status(401).json({
        status: 401,
        message: 'Unauthorized! 2',
        dbg: unauthorizedDBG,
    });
}
async function handleDebugMultiSigAuth(_req, res, next, authLevel) {
    nestedCounters_1.nestedCountersInstance.countEvent('middleware', 'debug_multi_sig_auth');
    try {
        let statusHist = (0, Self_1.getStatusHistoryCopy)();
        let statusNow = statusHist[statusHist.length - 1].moduleStatus || undefined;
        let weAreActive = statusNow === P2PTypes_1.NodeStatus.ACTIVE;
        let latestCycle = CycleChain.newest;
        if (!weAreActive) {
            const activeNodes = await (0, Self_1.contactArchiver)('dbgMiddleware');
            latestCycle = await (0, Sync_1.getNewestCycle)(activeNodes);
        }
        if (!latestCycle) {
            res.status(500).json({ error: "Node can't gather latest Cycle to perform signature verification" });
        }
        //auth with a signature
        if (_req.query.sig != null && _req.query.sig_counter != null) {
            const devPublicKeys = (0, debug_1.getMultisigPublicKeys)();
            let parsedSignatures = lib_types_1.Utils.safeJsonParse(_req.query.sig);
            if (!parsedSignatures || Array.isArray(parsedSignatures) === false) {
                return res.status(400).json({
                    status: 400,
                    message: 'Bad Request!',
                });
            }
            const nodes = String(_req.query.nodePubkeys).split(',');
            const ourPubkey = Context.crypto.getPublicKey().slice(0, 4);
            let intentedForOurNode = false;
            nodes.forEach((id) => {
                if (ourPubkey === id) {
                    intentedForOurNode = true;
                }
            });
            if (!intentedForOurNode) {
                return res.status(401).json({
                    status: 401,
                    message: 'Unauthorized!',
                });
            }
            // Check if parsed signatures exceed the number of developer public keys
            if (parsedSignatures.length > devPublicKeys.length) {
                return res.status(400).json({
                    status: 400,
                    message: 'Bad Request! Too many signatures.',
                });
            }
            // Remove duplicates from parsedSignatures
            parsedSignatures = Array.from(new Set(parsedSignatures));
            const minApprovals = Math.max(3, server_1.default.debug.minMultiSigRequiredForEndpoints);
            if (parsedSignatures.length < minApprovals) {
                return res.status(400).json({
                    status: 400,
                    message: 'Bad Request! Not enough signatures.',
                });
            }
            // when the debug call is signed it include the counter to prevent replay at later time and replay at different node or the same endpoint different query params
            const payload = {
                route: stripQueryParams(_req.originalUrl, ['sig', 'sig_counter', 'nodePubkeys']),
                nodes: _req.query.nodePubkeys,
                count: _req.query.sig_counter,
                networkId: latestCycle.networkId,
            };
            // Require a larger counter than before. This prevents replay attacks
            if (parseInt(_req.query.sig_counter) > multiSigLstCounter && parsedSignatures.length >= minApprovals) {
                const signaturesValid = Context.stateManager.app.verifyMultiSigs(payload, parsedSignatures, devPublicKeys, minApprovals, authLevel);
                // If all signatures are valid, proceed with the next middleware
                if (signaturesValid) {
                    multiSigLstCounter = parseInt(_req.query.sig_counter);
                    next();
                    return;
                }
                else {
                    return res.status(401).json({
                        status: 401,
                        message: 'Unauthorized! Invalid signatures.',
                    });
                }
            }
            else {
                console.log('Counter is not larger than last counter', _req.query.sig_counter, multiSigLstCounter);
            }
        }
    }
    catch (error) {
        if (logger_1.logFlags.verbose && logger_1.logFlags.console)
            console.log('Error in handleDebugMultiSigAuth:', error);
        console.log(error);
    }
    nestedCounters_1.nestedCountersInstance.countEvent('middleware', 'debug_multi_sig_auth failure');
    return res.status(403).json({
        status: 403,
        message: 'FORBIDDEN. Endpoint is only available in debug mode in addtion to signature verification.',
    });
}
function stripQueryParams(url, params) {
    // Split the URL into the base and the query string
    let [base, ...tail] = url.split('?');
    let queryString = tail.join('?');
    // If there's no query string, return the base URL
    if (!queryString)
        return url;
    // Split the query string into individual key-value pairs
    let queryParams = queryString.split('&');
    // Filter out the parameters that are not in the params array
    queryParams = queryParams.filter((param) => {
        let [key, value] = param.split('=');
        return !params.includes(key);
    });
    // Join the filtered parameters back into a query string
    queryString = queryParams.join('&');
    // If there are no parameters left, return the base URL
    if (queryString === '')
        return base;
    // Otherwise, return the base URL with the filtered query string
    return `${base}?${queryString}`;
}
exports.stripQueryParams = stripQueryParams;
//Secury Levels: Unauthorized = 0, Low=1, Medium=2, High=3
// Alias for isDebugModeMiddlewareHigh
const isDebugModeMiddleware = (_req, res, next) => {
    (0, exports.isDebugModeMiddlewareHigh)(_req, res, next);
};
exports.isDebugModeMiddleware = isDebugModeMiddleware;
// Middleware for low security level
const isDebugModeMiddlewareLow = (_req, res, next) => {
    // if (unsafeUnlock) {
    //   next()
    //   return
    // }
    const isDebug = (0, debug_1.isDebugMode)();
    if (!isDebug) {
        handleDebugAuth(_req, res, next, shardus_types_1.DevSecurityLevel.Low);
    }
    else
        next();
};
exports.isDebugModeMiddlewareLow = isDebugModeMiddlewareLow;
// Middleware for medium security level
const isDebugModeMiddlewareMedium = (_req, res, next) => {
    // if (unsafeUnlock) {
    //   next()
    //   return
    // }
    const isDebug = (0, debug_1.isDebugMode)();
    if (!isDebug) {
        handleDebugAuth(_req, res, next, shardus_types_1.DevSecurityLevel.Medium);
    }
    else
        next();
};
exports.isDebugModeMiddlewareMedium = isDebugModeMiddlewareMedium;
// Middleware for high security level
const isDebugModeMiddlewareHigh = (_req, res, next) => {
    // if (unsafeUnlock) {
    //   next()
    //   return
    // }
    const isDebug = (0, debug_1.isDebugMode)();
    if (!isDebug) {
        handleDebugAuth(_req, res, next, shardus_types_1.DevSecurityLevel.High);
    }
    else
        next();
};
exports.isDebugModeMiddlewareHigh = isDebugModeMiddlewareHigh;
const isDebugModeMiddlewareMultiSig = (_req, res, next) => {
    const isDebug = (0, debug_1.isDebugMode)();
    if (!isDebug) {
        handleDebugMultiSigAuth(_req, res, next, shardus_types_1.DevSecurityLevel.High);
    }
    else
        next();
};
exports.isDebugModeMiddlewareMultiSig = isDebugModeMiddlewareMultiSig;
const isDebugModeMiddlewareMultiSigHigh = (_req, res, next) => {
    const isDebug = (0, debug_1.isDebugMode)();
    if (!isDebug) {
        handleDebugMultiSigAuth(_req, res, next, shardus_types_1.DevSecurityLevel.High);
    }
    else
        next();
};
exports.isDebugModeMiddlewareMultiSigHigh = isDebugModeMiddlewareMultiSigHigh;
const isDebugModeMiddlewareMultiSigMedium = (_req, res, next) => {
    const isDebug = (0, debug_1.isDebugMode)();
    if (!isDebug) {
        handleDebugMultiSigAuth(_req, res, next, shardus_types_1.DevSecurityLevel.Medium);
    }
    else
        next();
};
exports.isDebugModeMiddlewareMultiSigMedium = isDebugModeMiddlewareMultiSigMedium;
const isDebugModeMiddlewareMultiSigLow = (_req, res, next) => {
    const isDebug = (0, debug_1.isDebugMode)();
    if (!isDebug) {
        handleDebugMultiSigAuth(_req, res, next, shardus_types_1.DevSecurityLevel.Low);
    }
    else
        next();
};
exports.isDebugModeMiddlewareMultiSigLow = isDebugModeMiddlewareMultiSigLow;
//# sourceMappingURL=debugMiddleware.js.map