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 | 28x 28x 2x 2x 26x 28x 28x 22x 22x 6x 6x 22x 22x 12x 12x 10x 10x 6x 4x 4x 10x 10x 8x 10x 8x 8x 8x 8x 8x 8x 15x 18x 6x 12x | import { AGENT_DATA_HEADER } from '../../shared/const'
import { IdentificationClient, SendResult } from '../fingerprint/identificationClient'
import { processRuleset, RuleActionUnion } from '../fingerprint/ruleset'
import { hasContentType, isDocumentDestination } from '../utils/headers'
import { injectAgentProcessorScript } from '../scripts'
import { fetchOrigin } from '../utils/origin'
/**
* 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
/** Fallback rule if identification client rule evaluation fails */
fallbackRule: RuleActionUnion
/** Route prefix for the worker requests */
routePrefix: string
/** Flag that determines whether worker is in Monitor Mode */
isMonitorMode: boolean
}
/**
* 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,
fallbackRule,
routePrefix,
isMonitorMode,
}: HandleProtectedApiCallParams): Promise<Response> {
const [response, agentData] = await getResponseForProtectedCall({
request,
identificationClient,
fallbackRule,
isMonitorMode,
})
/**
* 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, routePrefix)
}
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
*/
async function getResponseForProtectedCall({
request,
identificationClient,
fallbackRule,
isMonitorMode,
}: Omit<HandleProtectedApiCallParams, 'routePrefix'>): Promise<[response: Response, agentData: string | null]> {
let ingressResponse: SendResult
let originRequest: Request
let signals: string
try {
const result = await IdentificationClient.parseIncomingRequest(request)
signals = result.signals
originRequest = result.request
} catch (e) {
console.error('Failed to parse incoming request:', e)
return [await handleFallbackRule(request, fallbackRule, isMonitorMode), null]
}
try {
ingressResponse = await identificationClient.send(originRequest, signals)
} catch (error) {
console.error('Error sending request to ingress service:', error)
return [await handleFallbackRule(originRequest, fallbackRule, isMonitorMode), null]
}
let originResponse: Response
Iif (isMonitorMode) {
originResponse = await fetchOrigin(originRequest)
} else {
if (ingressResponse.ruleAction) {
originResponse = await processRuleset(ingressResponse.ruleAction, originRequest)
} else {
console.warn('No ruleset processor found for ingress response, using fallback rule.')
originResponse = await processRuleset(fallbackRule, originRequest)
}
}
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)
}
// Re-create the response, because by default its headers are immutable, even if we were to use `originResponse.clone()`
return [
new Response(originResponse.body, {
status: originResponse.status,
headers: originResponseHeaders,
statusText: originResponse.statusText,
cf: originResponse.cf,
}),
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
*
*/
function setHeadersFromIngressToOrigin(ingressResponse: SendResult, originResponseHeaders: Headers) {
const { agentData, setCookieHeaders } = ingressResponse
console.debug('Adding agent data header', agentData)
originResponseHeaders.set(AGENT_DATA_HEADER, agentData)
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 {RuleActionUnion} fallbackRule - The fallback rule to be applied to the request.
* @param {boolean} [isMonitorMode] - Indicates whether the function operates in monitor mode.
* @return {Promise<Response>} A promise that resolves to the response after processing the request.
*/
function handleFallbackRule(
request: Request,
fallbackRule: RuleActionUnion,
isMonitorMode: boolean
): Promise<Response> {
if (isMonitorMode) {
return fetchOrigin(request)
}
return processRuleset(fallbackRule, request)
}
|