"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateHmacFromRequestFactory = exports.getCurrentTimeInSec = exports.validateHmacString = exports.validateHmac = exports.generateLocalHmac = void 0;
const tslib_1 = require("tslib");
const logger_1 = require("../logger");
const types_1 = require("../types");
const http_1 = require("../../runtime/http");
const crypto_1 = require("../../runtime/crypto");
const types_2 = require("../../runtime/crypto/types");
const ShopifyErrors = tslib_1.__importStar(require("../error"));
const safe_compare_1 = require("../auth/oauth/safe-compare");
const processed_query_1 = tslib_1.__importDefault(require("./processed-query"));
const types_3 = require("./types");
const HMAC_TIMESTAMP_PERMITTED_CLOCK_TOLERANCE_SEC = 90;
function stringifyQueryForAdmin(query) {
    const processedQuery = new processed_query_1.default();
    Object.keys(query)
        .sort((val1, val2) => val1.localeCompare(val2))
        .forEach((key) => processedQuery.put(key, query[key]));
    return processedQuery.stringify(true);
}
function stringifyQueryForAppProxy(query) {
    return Object.entries(query)
        .sort(([val1], [val2]) => val1.localeCompare(val2))
        .reduce((acc, [key, value]) => {
        return `${acc}${key}=${Array.isArray(value) ? value.join(',') : value}`;
    }, '');
}
function generateLocalHmac(config) {
    return async (params, signator = 'admin') => {
        const { hmac, signature, ...query } = params;
        const queryString = signator === 'admin'
            ? stringifyQueryForAdmin(query)
            : stringifyQueryForAppProxy(query);
        return (0, crypto_1.createSHA256HMAC)(config.apiSecretKey, queryString, types_2.HashFormat.Hex);
    };
}
exports.generateLocalHmac = generateLocalHmac;
function validateHmac(config) {
    return async (query, { signator } = { signator: 'admin' }) => {
        if (signator === 'admin' && !query.hmac) {
            throw new ShopifyErrors.InvalidHmacError('Query does not contain an HMAC value.');
        }
        if (signator === 'appProxy' && !query.signature) {
            throw new ShopifyErrors.InvalidHmacError('Query does not contain a signature value.');
        }
        validateHmacTimestamp(query);
        const hmac = signator === 'appProxy' ? query.signature : query.hmac;
        const localHmac = await generateLocalHmac(config)(query, signator);
        return (0, safe_compare_1.safeCompare)(hmac, localHmac);
    };
}
exports.validateHmac = validateHmac;
async function validateHmacString(config, data, hmac, format) {
    const localHmac = await (0, crypto_1.createSHA256HMAC)(config.apiSecretKey, data, format);
    return (0, safe_compare_1.safeCompare)(hmac, localHmac);
}
exports.validateHmacString = validateHmacString;
function getCurrentTimeInSec() {
    return Math.trunc(Date.now() / 1000);
}
exports.getCurrentTimeInSec = getCurrentTimeInSec;
function validateHmacFromRequestFactory(config) {
    return async function validateHmacFromRequest({ type, rawBody, ...adapterArgs }) {
        const request = await (0, http_1.abstractConvertRequest)(adapterArgs);
        if (!rawBody.length) {
            return fail(types_3.ValidationErrorReason.MissingBody, type, config);
        }
        const hmac = (0, http_1.getHeader)(request.headers, types_1.ShopifyHeader.Hmac);
        if (!hmac) {
            return fail(types_3.ValidationErrorReason.MissingHmac, type, config);
        }
        const validHmac = await validateHmacString(config, rawBody, hmac, types_2.HashFormat.Base64);
        if (!validHmac) {
            return fail(types_3.ValidationErrorReason.InvalidHmac, type, config);
        }
        return succeed(type, config);
    };
}
exports.validateHmacFromRequestFactory = validateHmacFromRequestFactory;
function validateHmacTimestamp(query) {
    if (Math.abs(getCurrentTimeInSec() - Number(query.timestamp)) >
        HMAC_TIMESTAMP_PERMITTED_CLOCK_TOLERANCE_SEC) {
        throw new ShopifyErrors.InvalidHmacError('HMAC timestamp is outside of the tolerance range');
    }
}
async function fail(reason, type, config) {
    const log = (0, logger_1.logger)(config);
    await log.debug(`${type} request is not valid`, { reason });
    return {
        valid: false,
        reason,
    };
}
async function succeed(type, config) {
    const log = (0, logger_1.logger)(config);
    await log.debug(`${type} request is valid`);
    return {
        valid: true,
    };
}
//# sourceMappingURL=hmac-validator.js.map