Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

arcjet

Package Overview
Dependencies
Maintainers
2
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

arcjet - npm Package Compare versions

Comparing version 1.0.0-alpha.7 to 1.0.0-alpha.8

69

index.d.ts

@@ -9,2 +9,5 @@ import { ArcjetContext, ArcjetBotType, ArcjetEmailType, ArcjetMode, ArcjetStack, ArcjetDecision, ArcjetRule, ArcjetRequestDetails } from "@arcjet/protocol";

Intersection & Union : never;
type IsNever<T> = [T] extends [never] ? true : false;
type LiteralCheck<T, LiteralType extends null | undefined | string | number | boolean | symbol | bigint> = IsNever<T> extends false ? [T] extends [LiteralType] ? [LiteralType] extends [T] ? false : true : false : false;
type IsStringLiteral<T> = LiteralCheck<T, string>;
export interface RemoteClient {

@@ -46,10 +49,24 @@ decide(context: ArcjetContext, details: Partial<ArcjetRequestDetails>, rules: ArcjetRule[]): Promise<ArcjetDecision>;

}
export type RateLimitOptions = {
type TokenBucketRateLimitOptions<Characteristics extends readonly string[]> = {
mode?: ArcjetMode;
match?: string;
characteristics?: string[];
window: string;
characteristics?: Characteristics;
refillRate: number;
interval: string | number;
capacity: number;
};
type FixedWindowRateLimitOptions<Characteristics extends readonly string[]> = {
mode?: ArcjetMode;
match?: string;
characteristics?: Characteristics;
window: string | number;
max: number;
timeout: string;
};
type SlidingWindowRateLimitOptions<Characteristics extends readonly string[]> = {
mode?: ArcjetMode;
match?: string;
characteristics?: Characteristics;
interval: string | number;
max: number;
};
/**

@@ -124,8 +141,26 @@ * Bot detection is disabled by default. The `bots` configuration block allows

};
export type Primitive<Props extends PlainObject = {}> = ArcjetRule<Props>[];
export type Product<Props extends PlainObject = {}> = ArcjetRule<Props>[];
type PropsForCharacteristic<T> = IsStringLiteral<T> extends true ? T extends "ip.src" | "http.host" | "http.method" | "http.request.uri.path" | `http.request.headers["${string}"]` | `http.request.cookie["${string}"]` | `http.request.uri.args["${string}"]` ? {} : T extends string ? Record<T, string | number | boolean> : never : {};
type PropsForRule<R> = R extends ArcjetRule<infer Props> ? Props : {};
export type ExtraProps<Rules> = Rules extends [] ? {} : Rules extends ArcjetRule[][] ? UnionToIntersection<PropsForRule<Rules[number][number]>> : Rules extends ArcjetRule[] ? UnionToIntersection<PropsForRule<Rules[number]>> : never;
export type ArcjetRequest<Props extends PlainObject> = Simplify<Partial<ArcjetRequestDetails & Props>>;
export type Primitive<Props extends PlainObject = {}> = ArcjetRule<Props>[];
export type Product<Props extends PlainObject = {}> = ArcjetRule<Props>[];
export declare function rateLimit(options?: RateLimitOptions, ...additionalOptions: RateLimitOptions[]): Primitive;
/**
* @property {string} ip - The IP address of the client.
* @property {string} method - The HTTP method of the request.
* @property {string} protocol - The protocol of the request.
* @property {string} host - The host of the request.
* @property {string} path - The path of the request.
* @property {Headers} headers - The headers of the request.
* @property {string} cookies - The string representing semicolon-separated Cookies for a request.
* @property {string} query - The `?`-prefixed string representing the Query for a request. Commonly referred to as a "querystring".
* @property {string} email - An email address related to the request.
* @property ...extra - Extra data that might be useful for Arcjet. For example, requested tokens are specified as the `requested` property.
*/
export type ArcjetRequest<Props extends PlainObject> = Simplify<Partial<ArcjetRequestDetails> & Props>;
export declare function tokenBucket<const Characteristics extends readonly string[] = []>(options?: TokenBucketRateLimitOptions<Characteristics>, ...additionalOptions: TokenBucketRateLimitOptions<Characteristics>[]): Primitive<Simplify<UnionToIntersection<{
requested: number;
} | PropsForCharacteristic<Characteristics[number]>>>>;
export declare function fixedWindow<const Characteristics extends readonly string[] = []>(options?: FixedWindowRateLimitOptions<Characteristics>, ...additionalOptions: FixedWindowRateLimitOptions<Characteristics>[]): Primitive<Simplify<UnionToIntersection<PropsForCharacteristic<Characteristics[number]>>>>;
export declare function rateLimit<const Characteristics extends readonly string[] = []>(options?: FixedWindowRateLimitOptions<Characteristics>, ...additionalOptions: FixedWindowRateLimitOptions<Characteristics>[]): Primitive<Simplify<UnionToIntersection<PropsForCharacteristic<Characteristics[number]>>>>;
export declare function slidingWindow<const Characteristics extends readonly string[] = []>(options?: SlidingWindowRateLimitOptions<Characteristics>, ...additionalOptions: SlidingWindowRateLimitOptions<Characteristics>[]): Primitive<Simplify<UnionToIntersection<PropsForCharacteristic<Characteristics[number]>>>>;
export declare function validateEmail(options?: EmailOptions, ...additionalOptions: EmailOptions[]): Primitive<{

@@ -135,10 +170,10 @@ email: string;

export declare function detectBot(options?: BotOptions, ...additionalOptions: BotOptions[]): Primitive;
export type ProtectSignupOptions = {
rateLimit?: RateLimitOptions | RateLimitOptions[];
export type ProtectSignupOptions<Characteristics extends string[]> = {
rateLimit?: SlidingWindowRateLimitOptions<Characteristics> | SlidingWindowRateLimitOptions<Characteristics>[];
bots?: BotOptions | BotOptions[];
email?: EmailOptions | EmailOptions[];
};
export declare function protectSignup(options?: ProtectSignupOptions): Product<{
export declare function protectSignup<const Characteristics extends string[] = []>(options?: ProtectSignupOptions<Characteristics>): Product<Simplify<UnionToIntersection<{
email: string;
}>;
} | PropsForCharacteristic<Characteristics[number]>>>>;
export interface ArcjetOptions<Rules extends [...(Primitive | Product)[]]> {

@@ -169,11 +204,3 @@ /**

*
* @param {ArcjetRequest} request - The details about the request that Arcjet needs to make a decision.
* @param {string} request.ip - The IP address of the client.
* @param {string} request.method - The HTTP method of the request.
* @param {string} request.protocol - The protocol of the request.
* @param {string} request.host - The host of the request.
* @param {string} request.path - The path of the request.
* @param {Headers} request.headers - The headers of the request.
* @param request.extra - Extra data to send to the Arcjet API.
*
* @param {ArcjetRequest} request - Details about the {@link ArcjetRequest} that Arcjet needs to make a decision.
* @returns An {@link ArcjetDecision} indicating Arcjet's decision about the request.

@@ -180,0 +207,0 @@ */

@@ -6,2 +6,3 @@ import { ArcjetErrorDecision, ArcjetErrorReason, ArcjetRuleResult, ArcjetReason, ArcjetDenyDecision, ArcjetEmailReason, ArcjetBotType, ArcjetBotReason } from '@arcjet/protocol';

import * as analyze from '@arcjet/analyze';
import * as duration from '@arcjet/duration';
import { Logger } from '@arcjet/logger';

@@ -17,2 +18,5 @@

}
function nowInSeconds() {
return Math.floor(Date.now() / 1000);
}
class Cache {

@@ -36,8 +40,8 @@ expires;

}
set(key, value, ttl) {
this.expires.set(key, Date.now() + ttl);
set(key, value, expiresAt) {
this.expires.set(key, expiresAt);
this.data.set(key, value);
}
ttl(key) {
const now = Date.now();
const now = nowInSeconds();
const expiresAt = this.expires.get(key) ?? now;

@@ -60,7 +64,62 @@ return expiresAt - now;

}
const baseUrlAllowed = [
"https://decide.arcjet.com",
"https://decide.arcjettest.com",
"https://decide.arcjet.orb.local:4082",
];
function defaultBaseUrl() {
return process.env["ARCJET_BASE_URL"]
? process.env["ARCJET_BASE_URL"] // If ARCJET_BASE_URL is set, use it
: "https://decide.arcjet.com"; // Otherwise use the default
// TODO(#90): Remove this production conditional before 1.0.0
if (process.env["NODE_ENV"] === "production") {
// Use ARCJET_BASE_URL if it is set and belongs to our allowlist; otherwise
// use the hardcoded default.
if (typeof process.env["ARCJET_BASE_URL"] === "string" &&
baseUrlAllowed.includes(process.env["ARCJET_BASE_URL"])) {
return process.env["ARCJET_BASE_URL"];
}
else {
return "https://decide.arcjet.com";
}
}
else {
return process.env["ARCJET_BASE_URL"]
? process.env["ARCJET_BASE_URL"]
: "https://decide.arcjet.com";
}
}
const knownFields = [
"ip",
"method",
"protocol",
"host",
"path",
"headers",
"body",
"email",
"cookies",
"query",
];
function isUnknownRequestProperty(key) {
return !knownFields.includes(key);
}
function toString(value) {
if (typeof value === "string") {
return value;
}
if (typeof value === "number") {
return `${value}`;
}
if (typeof value === "boolean") {
return value ? "true" : "false";
}
return "<unsupported type>";
}
function extraProps(details) {
const extra = new Map();
for (const [key, value] of Object.entries(details)) {
if (isUnknownRequestProperty(key)) {
extra.set(key, toString(value));
}
}
return Object.fromEntries(extra.entries());
}
function createRemoteClient(options) {

@@ -79,3 +138,3 @@ // TODO(#207): Remove this when we can default the transport

const sdkStack = ArcjetStackToProtocol(options?.sdkStack ?? "NODEJS");
const sdkVersion = "1.0.0-alpha.7";
const sdkVersion = "1.0.0-alpha.8";
const client = createPromiseClient(DecideService, options.transport);

@@ -96,5 +155,7 @@ return Object.freeze({

headers: Object.fromEntries(details.headers.entries()),
cookies: details.cookies,
query: details.query,
// TODO(#208): Re-add body
// body: details.body,
extra: details.extra,
extra: extraProps(details),
email: typeof details.email === "string" ? details.email : undefined,

@@ -137,3 +198,3 @@ },

// body: details.body,
extra: details.extra,
extra: extraProps(details),
email: typeof details.email === "string" ? details.email : undefined,

@@ -254,5 +315,64 @@ },

}
function tokenBucket(options, ...additionalOptions) {
const rules = [];
if (typeof options === "undefined") {
return rules;
}
for (const opt of [options, ...additionalOptions]) {
const mode = opt.mode === "LIVE" ? "LIVE" : "DRY_RUN";
const match = opt.match;
const characteristics = Array.isArray(opt.characteristics)
? opt.characteristics
: undefined;
const refillRate = opt.refillRate;
const interval = duration.parse(opt.interval);
const capacity = opt.capacity;
rules.push({
type: "RATE_LIMIT",
priority: Priority.RateLimit,
mode,
match,
characteristics,
algorithm: "TOKEN_BUCKET",
refillRate,
interval,
capacity,
});
}
return rules;
}
function fixedWindow(options, ...additionalOptions) {
const rules = [];
if (typeof options === "undefined") {
return rules;
}
for (const opt of [options, ...additionalOptions]) {
const mode = opt.mode === "LIVE" ? "LIVE" : "DRY_RUN";
const match = opt.match;
const characteristics = Array.isArray(opt.characteristics)
? opt.characteristics
: undefined;
const max = opt.max;
const window = duration.parse(opt.window);
rules.push({
type: "RATE_LIMIT",
priority: Priority.RateLimit,
mode,
match,
characteristics,
algorithm: "FIXED_WINDOW",
max,
window,
});
}
return rules;
}
// This is currently kept for backwards compatibility but should be removed in
// favor of the fixedWindow primitive.
function rateLimit(options, ...additionalOptions) {
// TODO(#195): We should also have a local rate limit using an in-memory data
// structure if the environment supports it
return fixedWindow(options, ...additionalOptions);
}
function slidingWindow(options, ...additionalOptions) {
const rules = [];

@@ -264,2 +384,8 @@ if (typeof options === "undefined") {

const mode = opt.mode === "LIVE" ? "LIVE" : "DRY_RUN";
const match = opt.match;
const characteristics = Array.isArray(opt.characteristics)
? opt.characteristics
: undefined;
const max = opt.max;
const interval = duration.parse(opt.interval);
rules.push({

@@ -269,7 +395,7 @@ type: "RATE_LIMIT",

mode,
match: opt.match,
characteristics: opt.characteristics,
window: opt.window,
max: opt.max,
timeout: opt.timeout,
match,
characteristics,
algorithm: "SLIDING_WINDOW",
max,
interval,
});

@@ -331,3 +457,3 @@ }

const mode = opt.mode === "LIVE" ? "LIVE" : "DRY_RUN";
// TODO: Filter invalid email types (or error??)
// TODO: Filter invalid bot types (or error??)
const block = Array.isArray(opt.block)

@@ -370,3 +496,3 @@ ? opt.block

return new ArcjetRuleResult({
ttl: 60000,
ttl: 60,
state: "RUN",

@@ -384,3 +510,3 @@ conclusion: "DENY",

return new ArcjetRuleResult({
ttl: 60000,
ttl: 60,
state: "RUN",

@@ -402,6 +528,6 @@ conclusion: "ALLOW",

if (Array.isArray(options?.rateLimit)) {
rateLimitRules = rateLimit(...options.rateLimit);
rateLimitRules = slidingWindow(...options.rateLimit);
}
else {
rateLimitRules = rateLimit(options?.rateLimit);
rateLimitRules = slidingWindow(options?.rateLimit);
}

@@ -546,2 +672,3 @@ let botRules = [];

runtime: runtime(),
ttl: results[idx].ttl,
conclusion: results[idx].conclusion,

@@ -576,3 +703,3 @@ reason: results[idx].reason,

if (results[idx].ttl > 0) {
log.debug("Caching decision for %d milliseconds", decision.ttl, {
log.debug("Caching decision for %d seconds", decision.ttl, {
fingerprint,

@@ -582,3 +709,3 @@ conclusion: decision.conclusion,

});
blockCache.set(fingerprint, decision.reason, decision.ttl);
blockCache.set(fingerprint, decision.reason, nowInSeconds() + decision.ttl);
}

@@ -601,4 +728,4 @@ return decision;

if (decision.isDenied() && decision.ttl > 0) {
log.debug("decide: Caching block locally for %d milliseconds", decision.ttl);
blockCache.set(fingerprint, decision.reason, decision.ttl);
log.debug("decide: Caching block locally for %d seconds", decision.ttl);
blockCache.set(fingerprint, decision.reason, nowInSeconds() + decision.ttl);
}

@@ -624,2 +751,2 @@ return decision;

export { ArcjetHeaders, Runtime, createRemoteClient, arcjet as default, defaultBaseUrl, detectBot, protectSignup, rateLimit, validateEmail };
export { ArcjetHeaders, Runtime, createRemoteClient, arcjet as default, defaultBaseUrl, detectBot, fixedWindow, protectSignup, rateLimit, slidingWindow, tokenBucket, validateEmail };

@@ -16,3 +16,2 @@ import {

ArcjetErrorDecision,
ArcjetRateLimitRule,
ArcjetBotRule,

@@ -22,2 +21,5 @@ ArcjetRule,

ArcjetRequestDetails,
ArcjetTokenBucketRateLimitRule,
ArcjetFixedWindowRateLimitRule,
ArcjetSlidingWindowRateLimitRule,
} from "@arcjet/protocol";

@@ -42,2 +44,3 @@ import {

import * as analyze from "@arcjet/analyze";
import * as duration from "@arcjet/duration";
import { Logger } from "@arcjet/logger";

@@ -57,2 +60,6 @@

function nowInSeconds(): number {
return Math.floor(Date.now() / 1000);
}
class Cache<T> {

@@ -78,4 +85,4 @@ expires: Map<string, number>;

set(key: string, value: T, ttl: number) {
this.expires.set(key, Date.now() + ttl);
set(key: string, value: T, expiresAt: number) {
this.expires.set(key, expiresAt);
this.data.set(key, value);

@@ -85,3 +92,3 @@ }

ttl(key: string): number {
const now = Date.now();
const now = nowInSeconds();
const expiresAt = this.expires.get(key) ?? now;

@@ -117,2 +124,6 @@ return expiresAt - now;

// https://github.com/sindresorhus/type-fest/blob/017bf38ebb52df37c297324d97bcc693ec22e920/source/union-to-intersection.d.ts
// IsNever:
// https://github.com/sindresorhus/type-fest/blob/e02f228f6391bb2b26c32a55dfe1e3aa2386d515/source/primitive.d.ts
// LiteralCheck & IsStringLiteral:
// https://github.com/sindresorhus/type-fest/blob/e02f228f6391bb2b26c32a55dfe1e3aa2386d515/source/is-literal.d.ts
//

@@ -156,2 +167,21 @@ // Licensed: MIT License Copyright (c) Sindre Sorhus <sindresorhus@gmail.com>

: never;
type IsNever<T> = [T] extends [never] ? true : false;
type LiteralCheck<
T,
LiteralType extends
| null
| undefined
| string
| number
| boolean
| symbol
| bigint,
> = IsNever<T> extends false // Must be wider than `never`
? [T] extends [LiteralType] // Must be narrower than `LiteralType`
? [LiteralType] extends [T] // Cannot be wider than `LiteralType`
? false
: true
: false
: false;
type IsStringLiteral<T> = LiteralCheck<T, string>;

@@ -182,8 +212,71 @@ export interface RemoteClient {

const baseUrlAllowed = [
"https://decide.arcjet.com",
"https://decide.arcjettest.com",
"https://decide.arcjet.orb.local:4082",
];
export function defaultBaseUrl() {
return process.env["ARCJET_BASE_URL"]
? process.env["ARCJET_BASE_URL"] // If ARCJET_BASE_URL is set, use it
: "https://decide.arcjet.com"; // Otherwise use the default
// TODO(#90): Remove this production conditional before 1.0.0
if (process.env["NODE_ENV"] === "production") {
// Use ARCJET_BASE_URL if it is set and belongs to our allowlist; otherwise
// use the hardcoded default.
if (
typeof process.env["ARCJET_BASE_URL"] === "string" &&
baseUrlAllowed.includes(process.env["ARCJET_BASE_URL"])
) {
return process.env["ARCJET_BASE_URL"];
} else {
return "https://decide.arcjet.com";
}
} else {
return process.env["ARCJET_BASE_URL"]
? process.env["ARCJET_BASE_URL"]
: "https://decide.arcjet.com";
}
}
const knownFields = [
"ip",
"method",
"protocol",
"host",
"path",
"headers",
"body",
"email",
"cookies",
"query",
];
function isUnknownRequestProperty(key: string) {
return !knownFields.includes(key);
}
function toString(value: unknown) {
if (typeof value === "string") {
return value;
}
if (typeof value === "number") {
return `${value}`;
}
if (typeof value === "boolean") {
return value ? "true" : "false";
}
return "<unsupported type>";
}
function extraProps(details: ArcjetRequestDetails): Record<string, string> {
const extra: Map<string, string> = new Map();
for (const [key, value] of Object.entries(details)) {
if (isUnknownRequestProperty(key)) {
extra.set(key, toString(value));
}
}
return Object.fromEntries(extra.entries());
}
export function createRemoteClient(

@@ -230,5 +323,7 @@ options?: RemoteClientOptions,

headers: Object.fromEntries(details.headers.entries()),
cookies: details.cookies,
query: details.query,
// TODO(#208): Re-add body
// body: details.body,
extra: details.extra,
extra: extraProps(details),
email: typeof details.email === "string" ? details.email : undefined,

@@ -282,3 +377,3 @@ },

// body: details.body,
extra: details.extra,
extra: extraProps(details),
email: typeof details.email === "string" ? details.email : undefined,

@@ -365,11 +460,28 @@ },

export type RateLimitOptions = {
type TokenBucketRateLimitOptions<Characteristics extends readonly string[]> = {
mode?: ArcjetMode;
match?: string;
characteristics?: string[];
window: string;
characteristics?: Characteristics;
refillRate: number;
interval: string | number;
capacity: number;
};
type FixedWindowRateLimitOptions<Characteristics extends readonly string[]> = {
mode?: ArcjetMode;
match?: string;
characteristics?: Characteristics;
window: string | number;
max: number;
timeout: string;
};
type SlidingWindowRateLimitOptions<Characteristics extends readonly string[]> =
{
mode?: ArcjetMode;
match?: string;
characteristics?: Characteristics;
interval: string | number;
max: number;
};
/**

@@ -477,2 +589,27 @@ * Bot detection is disabled by default. The `bots` configuration block allows

// Primitives and Products external names for Rules even though they are defined
// the same.
// See ExtraProps below for further explanation on why we define them like this.
export type Primitive<Props extends PlainObject = {}> = ArcjetRule<Props>[];
export type Product<Props extends PlainObject = {}> = ArcjetRule<Props>[];
// User-defined characteristics alter the required props of an ArcjetRequest
// Note: If a user doesn't provide the object literal to our primitives
// directly, we fallback to no required props. They can opt-in by adding the
// `as const` suffix to the characteristics array.
type PropsForCharacteristic<T> = IsStringLiteral<T> extends true
? T extends
| "ip.src"
| "http.host"
| "http.method"
| "http.request.uri.path"
| `http.request.headers["${string}"]`
| `http.request.cookie["${string}"]`
| `http.request.uri.args["${string}"]`
? {}
: T extends string
? Record<T, string | number | boolean>
: never
: {};
// Rules can specify they require specific props on an ArcjetRequest
type PropsForRule<R> = R extends ArcjetRule<infer Props> ? Props : {};

@@ -491,11 +628,18 @@ // We theoretically support an arbitrary amount of rule flattening,

/**
* @property {string} ip - The IP address of the client.
* @property {string} method - The HTTP method of the request.
* @property {string} protocol - The protocol of the request.
* @property {string} host - The host of the request.
* @property {string} path - The path of the request.
* @property {Headers} headers - The headers of the request.
* @property {string} cookies - The string representing semicolon-separated Cookies for a request.
* @property {string} query - The `?`-prefixed string representing the Query for a request. Commonly referred to as a "querystring".
* @property {string} email - An email address related to the request.
* @property ...extra - Extra data that might be useful for Arcjet. For example, requested tokens are specified as the `requested` property.
*/
export type ArcjetRequest<Props extends PlainObject> = Simplify<
Partial<ArcjetRequestDetails & Props>
Partial<ArcjetRequestDetails> & Props
>;
// Primitives and Products are the external names for Rules even though they are defined the same
// See ArcjetRequest above for the explanation on why we define them like this.
export type Primitive<Props extends PlainObject = {}> = ArcjetRule<Props>[];
export type Product<Props extends PlainObject = {}> = ArcjetRule<Props>[];
function isLocalRule<Props extends PlainObject>(

@@ -512,10 +656,108 @@ rule: ArcjetRule<Props>,

export function rateLimit(
options?: RateLimitOptions,
...additionalOptions: RateLimitOptions[]
): Primitive {
export function tokenBucket<
const Characteristics extends readonly string[] = [],
>(
options?: TokenBucketRateLimitOptions<Characteristics>,
...additionalOptions: TokenBucketRateLimitOptions<Characteristics>[]
): Primitive<
Simplify<
UnionToIntersection<
{ requested: number } | PropsForCharacteristic<Characteristics[number]>
>
>
> {
const rules: ArcjetTokenBucketRateLimitRule<{ requested: number }>[] = [];
if (typeof options === "undefined") {
return rules;
}
for (const opt of [options, ...additionalOptions]) {
const mode = opt.mode === "LIVE" ? "LIVE" : "DRY_RUN";
const match = opt.match;
const characteristics = Array.isArray(opt.characteristics)
? opt.characteristics
: undefined;
const refillRate = opt.refillRate;
const interval = duration.parse(opt.interval);
const capacity = opt.capacity;
rules.push({
type: "RATE_LIMIT",
priority: Priority.RateLimit,
mode,
match,
characteristics,
algorithm: "TOKEN_BUCKET",
refillRate,
interval,
capacity,
});
}
return rules;
}
export function fixedWindow<
const Characteristics extends readonly string[] = [],
>(
options?: FixedWindowRateLimitOptions<Characteristics>,
...additionalOptions: FixedWindowRateLimitOptions<Characteristics>[]
): Primitive<
Simplify<UnionToIntersection<PropsForCharacteristic<Characteristics[number]>>>
> {
const rules: ArcjetFixedWindowRateLimitRule<{}>[] = [];
if (typeof options === "undefined") {
return rules;
}
for (const opt of [options, ...additionalOptions]) {
const mode = opt.mode === "LIVE" ? "LIVE" : "DRY_RUN";
const match = opt.match;
const characteristics = Array.isArray(opt.characteristics)
? opt.characteristics
: undefined;
const max = opt.max;
const window = duration.parse(opt.window);
rules.push({
type: "RATE_LIMIT",
priority: Priority.RateLimit,
mode,
match,
characteristics,
algorithm: "FIXED_WINDOW",
max,
window,
});
}
return rules;
}
// This is currently kept for backwards compatibility but should be removed in
// favor of the fixedWindow primitive.
export function rateLimit<const Characteristics extends readonly string[] = []>(
options?: FixedWindowRateLimitOptions<Characteristics>,
...additionalOptions: FixedWindowRateLimitOptions<Characteristics>[]
): Primitive<
Simplify<UnionToIntersection<PropsForCharacteristic<Characteristics[number]>>>
> {
// TODO(#195): We should also have a local rate limit using an in-memory data
// structure if the environment supports it
return fixedWindow(options, ...additionalOptions);
}
const rules: ArcjetRateLimitRule<{}>[] = [];
export function slidingWindow<
const Characteristics extends readonly string[] = [],
>(
options?: SlidingWindowRateLimitOptions<Characteristics>,
...additionalOptions: SlidingWindowRateLimitOptions<Characteristics>[]
): Primitive<
Simplify<UnionToIntersection<PropsForCharacteristic<Characteristics[number]>>>
> {
const rules: ArcjetSlidingWindowRateLimitRule<{}>[] = [];

@@ -528,3 +770,10 @@ if (typeof options === "undefined") {

const mode = opt.mode === "LIVE" ? "LIVE" : "DRY_RUN";
const match = opt.match;
const characteristics = Array.isArray(opt.characteristics)
? opt.characteristics
: undefined;
const max = opt.max;
const interval = duration.parse(opt.interval);
rules.push({

@@ -534,7 +783,7 @@ type: "RATE_LIMIT",

mode,
match: opt.match,
characteristics: opt.characteristics,
window: opt.window,
max: opt.max,
timeout: opt.timeout,
match,
characteristics,
algorithm: "SLIDING_WINDOW",
max,
interval,
});

@@ -620,3 +869,3 @@ }

const mode = opt.mode === "LIVE" ? "LIVE" : "DRY_RUN";
// TODO: Filter invalid email types (or error??)
// TODO: Filter invalid bot types (or error??)
const block = Array.isArray(opt.block)

@@ -684,3 +933,3 @@ ? opt.block

return new ArcjetRuleResult({
ttl: 60000,
ttl: 60,
state: "RUN",

@@ -697,3 +946,3 @@ conclusion: "DENY",

return new ArcjetRuleResult({
ttl: 60000,
ttl: 60,
state: "RUN",

@@ -714,4 +963,6 @@ conclusion: "ALLOW",

export type ProtectSignupOptions = {
rateLimit?: RateLimitOptions | RateLimitOptions[];
export type ProtectSignupOptions<Characteristics extends string[]> = {
rateLimit?:
| SlidingWindowRateLimitOptions<Characteristics>
| SlidingWindowRateLimitOptions<Characteristics>[];
bots?: BotOptions | BotOptions[];

@@ -721,10 +972,16 @@ email?: EmailOptions | EmailOptions[];

export function protectSignup(
options?: ProtectSignupOptions,
): Product<{ email: string }> {
export function protectSignup<const Characteristics extends string[] = []>(
options?: ProtectSignupOptions<Characteristics>,
): Product<
Simplify<
UnionToIntersection<
{ email: string } | PropsForCharacteristic<Characteristics[number]>
>
>
> {
let rateLimitRules: Primitive<{}> = [];
if (Array.isArray(options?.rateLimit)) {
rateLimitRules = rateLimit(...options.rateLimit);
rateLimitRules = slidingWindow(...options.rateLimit);
} else {
rateLimitRules = rateLimit(options?.rateLimit);
rateLimitRules = slidingWindow(options?.rateLimit);
}

@@ -739,3 +996,3 @@

let emailRules: Primitive<{}> = [];
let emailRules: Primitive<{ email: string }> = [];
if (Array.isArray(options?.email)) {

@@ -776,11 +1033,3 @@ emailRules = validateEmail(...options.email);

*
* @param {ArcjetRequest} request - The details about the request that Arcjet needs to make a decision.
* @param {string} request.ip - The IP address of the client.
* @param {string} request.method - The HTTP method of the request.
* @param {string} request.protocol - The protocol of the request.
* @param {string} request.host - The host of the request.
* @param {string} request.path - The path of the request.
* @param {Headers} request.headers - The headers of the request.
* @param request.extra - Extra data to send to the Arcjet API.
*
* @param {ArcjetRequest} request - Details about the {@link ArcjetRequest} that Arcjet needs to make a decision.
* @returns An {@link ArcjetDecision} indicating Arcjet's decision about the request.

@@ -943,2 +1192,3 @@ */

runtime: runtime(),
ttl: results[idx].ttl,
conclusion: results[idx].conclusion,

@@ -982,3 +1232,3 @@ reason: results[idx].reason,

if (results[idx].ttl > 0) {
log.debug("Caching decision for %d milliseconds", decision.ttl, {
log.debug("Caching decision for %d seconds", decision.ttl, {
fingerprint,

@@ -989,3 +1239,7 @@ conclusion: decision.conclusion,

blockCache.set(fingerprint, decision.reason, decision.ttl);
blockCache.set(
fingerprint,
decision.reason,
nowInSeconds() + decision.ttl,
);
}

@@ -1018,7 +1272,11 @@

log.debug(
"decide: Caching block locally for %d milliseconds",
"decide: Caching block locally for %d seconds",
decision.ttl,
);
blockCache.set(fingerprint, decision.reason, decision.ttl);
blockCache.set(
fingerprint,
decision.reason,
nowInSeconds() + decision.ttl,
);
}

@@ -1025,0 +1283,0 @@

{
"name": "arcjet",
"version": "1.0.0-alpha.7",
"version": "1.0.0-alpha.8",
"description": "Arcjet TypeScript and JavaScript SDK core",

@@ -34,13 +34,14 @@ "license": "Apache-2.0",

"dependencies": {
"@arcjet/analyze": "1.0.0-alpha.7",
"@arcjet/logger": "1.0.0-alpha.7",
"@arcjet/protocol": "1.0.0-alpha.7"
"@arcjet/analyze": "1.0.0-alpha.8",
"@arcjet/duration": "1.0.0-alpha.8",
"@arcjet/logger": "1.0.0-alpha.8",
"@arcjet/protocol": "1.0.0-alpha.8"
},
"devDependencies": {
"@arcjet/eslint-config": "1.0.0-alpha.7",
"@arcjet/rollup-config": "1.0.0-alpha.7",
"@arcjet/tsconfig": "1.0.0-alpha.7",
"@edge-runtime/jest-environment": "2.3.7",
"@arcjet/eslint-config": "1.0.0-alpha.8",
"@arcjet/rollup-config": "1.0.0-alpha.8",
"@arcjet/tsconfig": "1.0.0-alpha.8",
"@edge-runtime/jest-environment": "2.3.9",
"@jest/globals": "29.7.0",
"@rollup/wasm-node": "4.9.1",
"@rollup/wasm-node": "4.9.6",
"@types/node": "18.18.0",

@@ -47,0 +48,0 @@ "jest": "29.7.0",

@@ -17,6 +17,5 @@ <a href="https://arcjet.com" target="_arcjet-home">

[Arcjet][arcjet] helps developers protect their apps. Installed as an SDK, it
provides a set of core primitives such as rate limiting and bot protection.
These can be used independently or combined to create a set of layered defenses,
such as signup form protection.
[Arcjet][arcjet] helps developers protect their apps in just a few lines of
code. Implement rate limiting, bot protection, email verification & defend
against common attacks.

@@ -48,3 +47,6 @@ This is the [Arcjet][arcjet] TypeScript and JavaScript SDK core.

const aj = arcjet({
key: "ajkey_mykey",
// Get your site key from https://app.arcjet.com
// and set it as an environment variable rather than hard coding.
// See: https://www.npmjs.com/package/dotenv
key: process.env.ARCJET_KEY,
rules: [],

@@ -64,7 +66,12 @@ client: createRemoteClient({

// Construct an object with Arcjet request details
const path = new URL(req.url || "", `http://${req.headers.host}`);
const details = {
ip: req.socket.remoteAddress,
method: req.method,
host: req.headers.host,
path: path.pathname,
};
const decision = await aj.protect(details);
console.log(decision);

@@ -71,0 +78,0 @@ if (decision.isDenied()) {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc