All files / proxy/handlers handleIngress.ts

97.43% Statements 38/39
92.3% Branches 12/13
100% Functions 6/6
97.43% Lines 38/39

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110  5x   5x 5x 5x 5x         5x 198x       66x                       66x   121x   86x 86x 6x   86x                                       66x 66x   66x 66x           66x 66x   66x   66x     11x 11x       37x 37x       18x 18x       66x 66x   66x 66x           66x 66x 66x   66x 39x     66x    
import { CloudFrontRequest, CloudFrontResultResponse } from 'aws-lambda'
import { getBehaviorPathNestLevel, getFpIngressBaseHost } from '../utils/customer-variables/selectors'
import { CustomerVariables } from '../utils/customer-variables/customer-variables'
import { addTrafficMonitoring, prepareHeadersForIngressRequest } from '../utils'
import { getValidRegion, isMethodAuthorized } from '../utils/request'
import { extractIngressPath, getIngressAPIHost, getV3AgentPath, INGRESS_CDN_PATH } from '../utils/paths'
import { sendIngressRequest } from '../utils/transport'
import { Region } from '../model'
 
export type RequestType = 'agentV3' | 'ingressV3' | 'v4'
 
export function createIngressHandler(requestType: RequestType) {
  return (
    incomingRequest: CloudFrontRequest,
    customerVariables: CustomerVariables,
    pathMatches: RegExpMatchArray | undefined
  ) => handleIngress(incomingRequest, customerVariables, pathMatches, requestType)
}
 
/**
 * Processes a query string and appends the parameters to the URL's search params,
 * replacing the value of the "region" parameter if found.
 *
 * @param {string} queryString - The query string containing key-value pairs to be processed.
 * @param {URL} url - The URL object to which the search parameters will be appended.
 * @param {Region} region - The region value to insert or replace in the query string.
 */
function setupSearchParams(queryString: string, url: URL, region: Region) {
  decodeURIComponent(queryString)
    .split('&')
    .filter((it) => it.includes('='))
    .forEach((it) => {
      const kv = it.split('=')
      if (kv[0] === 'region') {
        kv[1] = region
      }
      url.searchParams.append(kv[0], kv[1])
    })
}
 
/**
 * Handles an ingress request by preparing the request's path, headers, and forwarding it to the appropriate API destination.
 *
 * @param {CloudFrontRequest} incomingRequest - The incoming request object provided by CloudFront, containing details such as URI, method, and query string.
 * @param {CustomerVariables} customerVariables - Configuration and settings specific to the customer, used to determine handling behavior.
 * @param {RegExpMatchArray | undefined} pathMatches - A regular expression match array for the request path, used to extract specific components of the path.
 * @param {RequestType} requestType - The type of request to handle, which governs behavior such as path handling and endpoint resolution.
 * @return {Promise<CloudFrontResultResponse>} A Promise resolving to the resulting CloudFront response after forwarding the request to the target API endpoint.
 */
async function handleIngress(
  incomingRequest: CloudFrontRequest,
  customerVariables: CustomerVariables,
  pathMatches: RegExpMatchArray | undefined,
  requestType: RequestType
): Promise<CloudFrontResultResponse> {
  // In V4, we need to leverage the new behavior path nest level variable to figure out the path for the ingress request
  const useBehaviorPathNestLevel = requestType === 'v4'
  const behaviorPathNestLevel = useBehaviorPathNestLevel ? await getBehaviorPathNestLevel(customerVariables) : 0
 
  const ingressBaseHost = await getFpIngressBaseHost(customerVariables)
  Iif (!ingressBaseHost) {
    return {
      status: '500',
    }
  }
 
  const requestUrlParams = new URLSearchParams(incomingRequest.querystring)
  const region = getValidRegion(requestUrlParams.get('region'))
 
  let suffix = ''
 
  switch (requestType) {
    // For V3 request, we need to prepend the INGRESS_CDN_PATH to the request path
    case 'agentV3':
      suffix = `${INGRESS_CDN_PATH}/${getV3AgentPath(requestUrlParams)}`
      break
 
    // For V4 request, we just use the path from incoming request and leverage the behavior path nest level
    case 'v4':
      suffix = incomingRequest.uri
      break
 
    default:
      // For the rest, so the "ingressV3" request, we'll extract path using path matches. It's an approach that was used in the old ingress handler.
      if (pathMatches && pathMatches.length >= 1) {
        suffix = pathMatches[1] ?? ''
      }
  }
 
  const requestPathSegments = extractIngressPath(suffix, behaviorPathNestLevel)
  const requestPath = requestPathSegments.join('/')
 
  const isAuthorizedMethodCall = isMethodAuthorized(incomingRequest.method)
  const requestHeaders = await prepareHeadersForIngressRequest(
    incomingRequest,
    customerVariables,
    isAuthorizedMethodCall
  )
 
  const requestUrl = new URL(getIngressAPIHost(region, ingressBaseHost))
  requestUrl.pathname = requestPath
  setupSearchParams(incomingRequest.querystring, requestUrl, region)
 
  if (isAuthorizedMethodCall) {
    addTrafficMonitoring(requestUrl)
  }
 
  return sendIngressRequest(incomingRequest, requestHeaders, requestUrl)
}