All files / worker urlMatching.ts

100% Statements 22/22
100% Branches 8/8
100% Functions 7/7
100% Lines 22/22

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                                                93x   93x   93x     279x                               98x   60x                   103x                   93x 93x   93x 80x 80x     13x                   32x 32x 19x   22x 22x     19x 19x 13x       19x    
import { scripts } from './scripts'
import { TypedEnv } from './types'
import { getIdentificationPageUrls, getProtectedApis, getRoutePrefix } from './env'
import { Script } from '../shared/scripts'
import { findMatchingRoute, parseRoutes } from '@fingerprintjs/url-matcher'
import { getCrossOriginUrl } from './utils/request'
 
export type UrlType =
  | {
      type: 'identification'
    }
  | {
      type: 'protection'
      options: boolean
    }
  | {
      type: 'script'
      script: Script
    }
  | {
      type: 'browserCache'
    }
 
export function matchUrl(url: URL, method: string, env: TypedEnv): UrlType | undefined {
  console.debug('Matching url', url.toString())
 
  const routePrefix = getRoutePrefix(env)
 
  const routes = parseRoutes<UrlType>(
    [
      ...scripts.map((script) => {
        return {
          url: new URL(`/${routePrefix}/${script}`, url.origin).toString(),
          metadata: {
            type: 'script' as const,
            script,
          },
        }
      }),
      {
        url: new URL(`/${routePrefix}/*`, url.origin).toString(),
        metadata: {
          type: 'browserCache' as const,
        },
      },
 
      ...getProtectedApis(env)
        .filter((protectedApi) => protectedApi.method === method || method === 'OPTIONS')
        .map((protectedApi) => {
          return {
            url: protectedApi.url,
            metadata: {
              type: 'protection' as const,
              options: method === 'OPTIONS',
            },
          }
        }),
 
      ...getIdentificationPageUrls(env).map((identificationPageUrl) => {
        return {
          url: identificationPageUrl,
          metadata: {
            type: 'identification' as const,
          },
        }
      }),
    ],
    { sortBySpecificity: true }
  )
  console.debug('Created routes', routes)
  const matchedRoute = findMatchingRoute(url, routes)
 
  if (matchedRoute) {
    console.debug('Matched route', matchedRoute)
    return matchedRoute.metadata
  }
 
  return undefined
}
 
/**
 * Checks if the request is a cross-origin request and the origin of the request
 * matches the origin of an identification page (i.e., it is an allowed origin)
 *
 * @returns the allowed origin (protocol://host[:port]); null otherwise, including when the request is a same-origin request
 */
export function getAllowedOrigin(request: Request, typedEnv: TypedEnv): string | null {
  const crossOriginUrl = getCrossOriginUrl(request)
  if (crossOriginUrl) {
    const identificationPageRoutes = parseRoutes(getIdentificationPageUrls(typedEnv)).map((route) => {
      // Convert the identification page URL into a URL that is equivalent to the page's origin
      route.path = '/'
      return route
    })
 
    const matchedIdentificationPage = findMatchingRoute(crossOriginUrl, identificationPageRoutes)
    if (matchedIdentificationPage) {
      return crossOriginUrl.origin
    }
  }
 
  return null
}