@arcjet/analyze
Version:
Arcjet local analysis engine
198 lines (195 loc) • 6.24 kB
JavaScript
import { initializeWasm } from '@arcjet/analyze-wasm';
const FREE_EMAIL_PROVIDERS = [
"gmail.com",
"yahoo.com",
"hotmail.com",
"aol.com",
"hotmail.co.uk",
];
function noOpSensitiveInfoDetect() {
return [];
}
function noOpBotsDetect() {
return [];
}
function createCoreImports(detect) {
if (typeof detect !== "function") {
detect = noOpSensitiveInfoDetect;
}
return {
"arcjet:js-req/bot-identifier": {
detect: noOpBotsDetect,
},
"arcjet:js-req/email-validator-overrides": {
isFreeEmail(domain) {
if (FREE_EMAIL_PROVIDERS.includes(domain)) {
return "yes";
}
return "unknown";
},
isDisposableEmail() {
return "unknown";
},
hasMxRecords() {
return "unknown";
},
hasGravatar() {
return "unknown";
},
},
"arcjet:js-req/filter-overrides": {
ipLookup() {
return undefined;
},
},
// TODO(@wooorm-arcjet): figure out a test case for this with the default `detect`.
"arcjet:js-req/sensitive-information-identifier": {
detect,
},
// TODO(@wooorm-arcjet): figure out a test case for this that calls `verify`.
"arcjet:js-req/verify-bot": {
verify() {
return "unverifiable";
},
},
};
}
/**
* Generate a fingerprint.
*
* Fingerprints can be used to identify the client across multiple requests.
*
* This considers different things on the `request` based on the passed
* `context.characteristics`.
*
* See [*Fingerprints* on
* `docs.arcjet.com`](https://docs.arcjet.com/fingerprints/) for more info.
*
* @param context
* Context.
* @param request
* Request.
* @returns
* Promise for a SHA-256 fingerprint.
*/
async function generateFingerprint(context, request) {
const { log } = context;
const coreImports = createCoreImports();
const analyze = await initializeWasm(coreImports);
if (typeof analyze !== "undefined") {
return analyze.generateFingerprint(JSON.stringify(request), context.characteristics);
// Ignore the `else` branch as we test in places that have WebAssembly.
/* node:coverage ignore next 4 */
}
log.debug("WebAssembly is not supported in this runtime");
return "";
}
/**
* Check whether an email is valid.
*
* @param context
* Context.
* @param value
* Value.
* @param options
* Configuration.
* @returns
* Promise for a result.
*/
async function isValidEmail(context, value, options) {
const { log } = context;
const coreImports = createCoreImports();
const analyze = await initializeWasm(coreImports);
if (typeof analyze !== "undefined") {
return analyze.isValidEmail(value, options);
// Ignore the `else` branch as we test in places that have WebAssembly.
/* node:coverage ignore next 4 */
}
log.debug("WebAssembly is not supported in this runtime");
return { blocked: [], validity: "valid" };
}
/**
* Detect whether a request is by a bot.
*
* @param context
* Context.
* @param request
* Request.
* @param options
* Configuration.
* @returns
* Promise for a result.
*/
async function detectBot(context, request, options) {
const { log } = context;
const coreImports = createCoreImports();
const analyze = await initializeWasm(coreImports);
if (typeof analyze !== "undefined") {
return analyze.detectBot(JSON.stringify(request), options);
// Ignore the `else` branch as we test in places that have WebAssembly.
/* node:coverage ignore next 4 */
}
log.debug("WebAssembly is not supported in this runtime");
return { allowed: [], denied: [], spoofed: false, verified: false };
}
/**
* Detect sensitive info in a value.
*
* @param context
* Context.
* @param value
* Value.
* @param entities
* Strategy to use for detecting sensitive info;
* either by denying everything and allowing certain tags or by allowing
* everything and denying certain tags.
* @param contextWindowSize
* Number of tokens to pass to `detect`.
* @param detect
* Function to detect sensitive info (optional).
* @returns
* Promise for a result.
*/
async function detectSensitiveInfo(context, value, entities, contextWindowSize, detect) {
const { log } = context;
const coreImports = createCoreImports(detect);
const analyze = await initializeWasm(coreImports);
if (typeof analyze !== "undefined") {
const skipCustomDetect = typeof detect !== "function";
return analyze.detectSensitiveInfo(value, {
entities,
contextWindowSize,
skipCustomDetect,
});
// Ignore the `else` branch as we test in places that have WebAssembly.
/* node:coverage ignore next 4 */
}
log.debug("WebAssembly is not supported in this runtime");
throw new Error("SENSITIVE_INFO rule failed to run because Wasm is not supported in this environment.");
}
/**
* Check if a filter matches a request.
*
* @param context
* Arcjet context.
* @param request
* Request.
* @param expressions
* Filter expressions.
* @returns
* Promise to whether the filter matches the request.
*/
async function matchFilters(context, request, expressions, allowIfMatch) {
const coreImports = createCoreImports();
const analyze = await initializeWasm(coreImports);
if (typeof analyze !== "undefined") {
return analyze.matchFilters(JSON.stringify(request),
// @ts-expect-error: WebAssembly does not support readonly values.
expressions, allowIfMatch);
// Ignore the `else` branch as we test in places that have WebAssembly.
/* node:coverage ignore next 4 */
}
context.log.debug("WebAssembly is not supported in this runtime");
throw new Error("FILTER rule failed to run because Wasm is not supported in this environment.");
}
export { detectBot, detectSensitiveInfo, generateFingerprint, isValidEmail, matchFilters };