"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractPathParamsFromApiGatewayEvent = exports.extractQueryParamsFromApiGatewayEvent = exports.parseApiGatewayEventBody = exports.parseApiGatewayEvent = void 0;
// eslint-disable-next-line import/default
const ramda_1 = __importDefault(require("ramda"));
const authorization_1 = require("../authorization");
const getAuthenticationDetails_1 = require("../authorization/getAuthenticationDetails");
const errors_1 = require("../errors");
const logging_1 = require("../logging");
const types_1 = require("../types");
const utils_1 = require("../utils");
const incomingEvents_1 = require("./incomingEvents");
const verifyAuthIdentity = {
    authorization: '-',
    identity: {
        kind: types_1.AuthIdentityKind.DfnsStaff,
        permissions: [],
        scope: '',
        username: 'dfns.internal',
    }
};
const lambdaAuthorizerPath = ['requestContext', 'authorizer', 'lambda'];
const jwtAuthorizerPath = ['requestContext', 'authorizer', 'jwt'];
const parseApiGatewayEvent = async (event) => {
    const body = (0, exports.parseApiGatewayEventBody)(event);
    const authDetails = await extractAuthDetailsFromApiGatewayEvent(event, body);
    return { authDetails, body };
};
exports.parseApiGatewayEvent = parseApiGatewayEvent;
const parseApiGatewayEventBody = (event) => {
    if (!(0, utils_1.isStringAndNonEmpty)(event.body))
        return event.body;
    const maybeJsonString = event.isBase64Encoded ? Buffer.from(event.body, 'base64').toString('utf8') : event.body;
    if (!(0, utils_1.isJsonString)(maybeJsonString)) {
        throw errors_1.TechnicalError.unableToParseJson(event.body);
    }
    return JSON.parse(maybeJsonString);
};
exports.parseApiGatewayEventBody = parseApiGatewayEventBody;
const extractAuthDetailsFromApiGatewayEvent = async (event, body) => {
    if ((0, incomingEvents_1.getApiGatewayEventSourceApi)(event) === types_1.DfnsApiKind.PrivateApi) {
        return {
            authorization: '',
            identity: {
                kind: types_1.AuthIdentityKind.DfnsService,
                serviceName: extractOriginatingServiceFromApiGatewayEvent(event),
            },
        };
    }
    // Fall back to legacy auth if the auth lambda is not deployed
    if ((0, utils_1.isAuthV2Enabled)()) {
        const authResponse = await (0, getAuthenticationDetails_1.getAuthenticationDetails)(event, body);
        if (!authResponse) {
            return verifyAuthIdentity;
        }
        const authDetails = await extractAuthBlockFromGatewayEvent(authResponse, body);
        if (authDetails) {
            return authDetails;
        }
        const authorizationHeader = event.headers.authorization;
        if (!authorizationHeader || !authResponse.authIdentity) {
            throw errors_1.TechnicalError.noAuthHeaderInEvent();
        }
        return {
            authorization: authorizationHeader,
            identity: authResponse.authIdentity
        };
    }
    const authorizationHeader = event.headers.authorization;
    if (!authorizationHeader) {
        throw errors_1.TechnicalError.noAuthHeaderInEvent();
    }
    const lambdaAuthorizerAuthIdentity = ramda_1.default.path(lambdaAuthorizerPath.concat('authIdentity'), event);
    if (lambdaAuthorizerAuthIdentity) {
        return {
            authorization: authorizationHeader,
            identity: lambdaAuthorizerAuthIdentity,
        };
    }
    const jwtAuthorizerPayload = ramda_1.default.path(jwtAuthorizerPath, event);
    if (jwtAuthorizerPayload) {
        return {
            authorization: authorizationHeader,
            identity: extractStaffUserAuthIdentityFromJwt(event),
        };
    }
    throw errors_1.TechnicalError.cannotExtractAuthIdentityFromGatewayEvent(JSON.stringify(event));
};
const extractAuthBlockFromGatewayEvent = async (authResponse, body) => {
    if (!authResponse.authBlock) {
        return;
    }
    const authDetails = await (0, authorization_1.checkAuthorization)(authResponse.authBlock, body);
    if (!authDetails) {
        throw errors_1.TechnicalError.actionNotAuthorized();
    }
    return authDetails;
};
const extractOriginatingServiceFromApiGatewayEvent = (event) => {
    const originatingService = event.headers['X-DFNS-ORIGINATING-SERVICE'];
    return originatingService || 'Unknown Service';
};
function extractStaffUserAuthIdentityFromJwt(event) {
    const username = ramda_1.default.path(jwtAuthorizerPath.concat('claims', 'https://staff/username'), event);
    const scope = ramda_1.default.path(jwtAuthorizerPath.concat('claims', 'scope'), event);
    const permissions = ramda_1.default.path(jwtAuthorizerPath.concat('claims', 'permissions'), event) || [];
    if (!(0, utils_1.isStringAndNonEmpty)(scope)) {
        throwCannotExtractStaffUserAuthIdentityFromJwt('jwt auth scope', event);
    }
    if (!(0, utils_1.isStringAndNonEmpty)(username)) {
        throwCannotExtractStaffUserAuthIdentityFromJwt('username', event);
    }
    return {
        kind: types_1.AuthIdentityKind.DfnsStaff,
        username,
        permissions,
        scope,
    };
}
function throwCannotExtractStaffUserAuthIdentityFromJwt(propertyName, event) {
    logging_1.logger.error(`Error extracting staff user identity from jwt: No ${propertyName} is set in apiGateway event`, {
        payload: event,
    });
    throw new Error(`Error extracting staff user identity from jwt: No ${propertyName} is set in apiGateway event`);
}
/**
 * This function extracts the query parameters of an incoming Api Gateway event.
 * The behavior changes if the APi GW is v1 or v2.
 * If there are "multi-values" query parameters (eg. ?param=foo&param=bar),
 * it is also added to the resulting object
 */
const extractQueryParamsFromApiGatewayEvent = (event) => {
    if ((0, incomingEvents_1.isApiGatewayV1Event)(event)) {
        const multiValueQueryStringParameters = Object.entries(event.multiValueQueryStringParameters || {}).reduce((acc, [key, val]) => {
            if (val && (val.length || 0) > 1) {
                acc[key] = val;
            }
            return acc;
        }, {});
        return Object.assign(Object.assign({}, event.queryStringParameters), multiValueQueryStringParameters);
    }
    if ((0, incomingEvents_1.isApiGatewayV2Event)(event)) {
        const queryString = event.rawQueryString ? `?${event.rawQueryString}` : '';
        const queryParamsFromRawQueryString = (0, utils_1.extractQueryParamsFromPath)('/' + queryString);
        const queryParamsFromEvent = Object.entries(event.queryStringParameters || {}).reduce((acc, [key, val]) => {
            const valuesSplitted = (val || '').split(',').filter(utils_1.isStringAndNonEmpty);
            if (valuesSplitted.length === 1) {
                acc[key] = valuesSplitted[0];
            }
            else if (valuesSplitted.length > 1) {
                acc[key] = valuesSplitted;
            }
            return acc;
        }, {});
        return Object.assign(Object.assign({}, queryParamsFromRawQueryString), queryParamsFromEvent);
    }
    return {};
};
exports.extractQueryParamsFromApiGatewayEvent = extractQueryParamsFromApiGatewayEvent;
const extractPathParamsFromApiGatewayEvent = (event) => {
    const pathParams = event.pathParameters || {};
    delete pathParams.proxy; // "proxy" is used in aws api gateway to specify "any path", it's not really named specifically with a custom param name in Api GW definition. We decided to remove it from here
    return pathParams;
};
exports.extractPathParamsFromApiGatewayEvent = extractPathParamsFromApiGatewayEvent;
//# sourceMappingURL=parseApiGatewayEvent.js.map