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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | 36x 36x 2x 2x 34x 36x 36x 27x 27x 27x 27x 9x 9x 27x 27x 15x 15x 15x 15x 15x 12x 1x 11x 7x 4x 4x 12x 12x 10x 12x 12x 10x 10x 10x 10x 1x 10x 10x 10x 19x 24x 8x 16x | import { AGENT_DATA_HEADER } from '../../shared/const'
import { IdentificationClient, SendResult } from '../fingerprint/identificationClient'
import { processRuleset } from '../fingerprint/ruleset'
import { hasContentType, isDocumentDestination } from '../utils/headers'
import { injectAgentProcessorScript } from '../scripts'
import { fetchOrigin } from '../utils/origin'
import { TypedEnv } from '../types'
import { getFallbackRuleAction, getRoutePrefix, isMonitorMode } from '../env'
import { setCorsHeadersForInstrumentation } from '../utils/request'
import { copyResponseWithNewHeaders } from '../utils/response'
/**
* Parameters required for handling a protected API call.
*/
export type HandleProtectedApiCallParams = {
/** The incoming HTTP request to be processed */
request: Request
/** Client for sending fingerprinting data to the ingress service */
identificationClient: IdentificationClient
/** The environment for the request*/
env: TypedEnv
}
/**
* Handles a protected API call by validating signals and processing the request.
* For HTML responses, injects the agent processor script into the <head> element to process the agent data.
*/
export async function handleProtectedApiCall({
request,
identificationClient,
env,
}: HandleProtectedApiCallParams): Promise<Response> {
const [response, agentData] = await getResponseForProtectedCall({
request,
identificationClient,
env,
})
/**
* For HTML responses, inject the agent processor script into the <head> element to process the agent data.
* */
if (
agentData &&
hasContentType(response.headers, 'text/html') &&
// This check protects against false-positive HTML requests triggered by a "fetch" call. (e.g. by htmx)
isDocumentDestination(request.headers)
) {
console.info('Injecting agent processor script into HTML response.')
return injectAgentProcessorScript(response, agentData, getRoutePrefix(env))
}
return response
}
/**
* Handles a protected API call by validating signals and processing the request.
*
* This function performs the following operations:
* 1. Validates that the request contains required fingerprinting signals
* 2. Sends the request to both ingress and origin services
* 3. Merges headers from the ingress response into the origin response
* 4. Returns the combined response with updated headers
*
* @param params - Configuration object containing request, ingress client, and error response
*
* @returns the `Response` and optional data that needs to be sent back to the agent. The `Headers`
* on the `Response` are mutable.
*/
async function getResponseForProtectedCall({
request,
identificationClient,
env,
}: HandleProtectedApiCallParams): Promise<[response: Response, agentData: string | null]> {
let ingressResponse: SendResult
let originRequest: Request
let signals: string
let clientCookie: string | undefined
let removeCookies: boolean
try {
const result = await IdentificationClient.parseIncomingRequest(request)
signals = result.signals
originRequest = result.originRequest
clientCookie = result.clientCookie
removeCookies = result.removeCookies
} catch (e) {
console.error('Failed to parse incoming request:', e)
// Importantly, the request, not the originRequest is used here because
// the unmodified request must be sent to the origin if the request
// was not a valid instrumented request and the environment configuration
// indicates the request should be forwarded on error
return [await handleFallbackRule(request, env), null]
}
try {
ingressResponse = await identificationClient.send(originRequest, signals, clientCookie)
} catch (error) {
console.error('Error sending request to ingress service:', error)
const response = await handleFallbackRule(originRequest, env)
// Make a copy of the headers because they are immutable
const originResponseHeaders = new Headers(response.headers)
// The identification could not be completed but the request was a
// valid instrumented request. As a result, the CORS headers
// need to be set appropriately in the response so the browser
// will not fail the request.
setCorsHeadersForInstrumentation(request, originResponseHeaders)
return [copyResponseWithNewHeaders(response, originResponseHeaders), null]
}
let originResponse: Response
if (isMonitorMode(env)) {
originResponse = await fetchOrigin(originRequest)
} else {
if (ingressResponse.ruleAction) {
originResponse = await processRuleset(ingressResponse.ruleAction, originRequest, env)
} else {
console.warn('No ruleset processor found for ingress response, using fallback rule.')
originResponse = await processRuleset(getFallbackRuleAction(env), originRequest, env)
}
}
const originResponseHeaders = new Headers(originResponse.headers)
// For requests whose destination is a document (these are typically triggered by submitting a form or clicking a link)
// it doesn't make sense to set headers from ingress, because the browser will discard them anyway
if (!isDocumentDestination(request.headers)) {
setHeadersFromIngressToOrigin(ingressResponse, originResponseHeaders, removeCookies)
}
setCorsHeadersForInstrumentation(request, originResponseHeaders)
// Re-create the response, because by default its headers are immutable, even if we were to use `originResponse.clone()`
return [copyResponseWithNewHeaders(originResponse, originResponseHeaders), ingressResponse.agentData]
}
/**
* Merges headers from the ingress response into the origin response headers.
*
* This function extracts agent data and set-cookie headers from the ingress response
* and adds them to the origin response headers. The agent data is set as a custom
* header, while cookie headers are appended to preserve existing cookies.
*
* @param ingressResponse - Result from the ingress service containing agent data and cookies
* @param originResponseHeaders - Mutable headers object from the origin response to be modified
* @param includedCrossOriginCredentials - true if the instrumented request is cross-origin and included credentials (i.e., cookies) for identification purposes
* @param removeCookies - true if Set-Cookie header fields need to be removed from the response
*
*/
function setHeadersFromIngressToOrigin(
ingressResponse: SendResult,
originResponseHeaders: Headers,
removeCookies: boolean
) {
const { agentData, setCookieHeaders } = ingressResponse
console.debug('Adding agent data header', agentData)
originResponseHeaders.set(AGENT_DATA_HEADER, agentData)
if (removeCookies) {
// Delete any cookies set by the origin, they would have been ignored
// by the browser if the request was not instrumented.
originResponseHeaders.delete('Set-Cookie')
}
Eif (setCookieHeaders?.length) {
console.debug('Adding set-cookie headers from ingress response', setCookieHeaders)
setCookieHeaders.forEach((cookie) => {
originResponseHeaders.append('Set-Cookie', cookie)
})
}
}
/**
* Handles the fallback rule for the given request based on the mode.
* If `isMonitorMode` is true, the function fetches data from the origin.
* Otherwise, it processes the ruleset using the provided fallback rule.
*
* @param {Request} request - The incoming request object to be processed.
* @param {TypedEnv} env - The environment for the request
* @return {Promise<Response>} A promise that resolves to the response after processing the request.
*/
function handleFallbackRule(request: Request, env: TypedEnv): Promise<Response> {
if (isMonitorMode(env)) {
return fetchOrigin(request)
}
return processRuleset(getFallbackRuleAction(env), request, env)
}
|