UNPKG

@arcjet/next

Version:

Arcjet SDK for the Next.js framework

282 lines (281 loc) 11 kB
import { NextResponse } from "next/server.js"; import type { NextMiddleware } from "next/server.js"; import type { ArcjetDecision, ArcjetOptions as CoreOptions, Primitive, Product, ExtraProps, CharacteristicProps } from "arcjet"; export * from "arcjet"; export declare function request(): Promise<ArcjetNextRequest>; type Simplify<T> = { [KeyType in keyof T]: T[KeyType]; } & {}; declare const emptyObjectSymbol: unique symbol; type WithoutCustomProps = { [emptyObjectSymbol]?: never; }; type PlainObject = { [key: string]: unknown; }; export type RemoteClientOptions = { baseUrl?: string; timeout?: number; }; export declare function createRemoteClient(options?: RemoteClientOptions): import("@arcjet/protocol/client.js").Client; export interface ArcjetNextRequest { headers?: Record<string, string | string[] | undefined> | Headers; socket?: Partial<{ remoteAddress: string; encrypted: boolean; }>; info?: Partial<{ remoteAddress: string; }>; requestContext?: Partial<{ identity: Partial<{ sourceIp: string; }>; }>; method?: string; httpVersion?: string; url?: string; ip?: string; nextUrl?: Partial<{ pathname: string; search: string; protocol: string; }>; cookies?: { [Symbol.iterator](): IterableIterator<[ string, { name: string; value: string; } ]>; } | Partial<{ [key: string]: string; }>; clone?: () => Request; body?: unknown; } /** * The options used to configure an {@link ArcjetNest} client. */ export type ArcjetOptions<Rules extends [...Array<Primitive | Product>], Characteristics extends readonly string[]> = Simplify<CoreOptions<Rules, Characteristics> & { /** * One or more IP Address of trusted proxies in front of the application. * These addresses will be excluded when Arcjet detects a public IP address. */ proxies?: Array<string>; }>; /** * The ArcjetNext client provides a public `protect()` method to * make a decision about how a Next.js request should be handled. */ export interface ArcjetNext<Props extends PlainObject> { /** * Makes a decision about the provided request using your configured rules. * * Arcjet can protect Next.js routes and pages that are server components (the * default). Client components cannot be protected because they run on the * client side and do not have access to the request object. * * Server actions are supported in both Next.js 14 and 15 for all features * except sensitive data detection. You need to call a utility function * `request()` that accesses the headers we need to analyze the request (see * example below). * * Calls to `protect()` will not throw an error. Arcjet is designed to fail * open so that a service issue or misconfiguration does not block all * requests. If there is an error condition when processing the rule, Arcjet * will label an `"ERROR"` result for that rule and you can check the message * property on the rule’s error result for more information. * * @param {ArcjetNextRequest} request - A `NextApiRequest` or `NextRequest` * provided to the request handler. * @param props - Any additional properties required for running rules against * a request. Whether this is required depends on the rules you've configured. * @returns {Promise<ArcjetDecision>} A decision indicating a high-level * conclusion and detailed explanations of the decision made by Arcjet. This * contains the following properties: * * - `id` (string) - The unique ID for the request. This can be used to look * up the request in the Arcjet dashboard. It is prefixed with `req_` for * decisions involving the Arcjet cloud API. For decisions taken locally, * the prefix is `lreq_`. * - `conclusion` (`"ALLOW" | "DENY" | "CHALLENGE" | "ERROR"`) - The final * conclusion based on evaluating each of the configured rules. If you wish * to accept Arcjet’s recommended action based on the configured rules then * you can use this property. * - `reason` (`ArcjetReason`) - An object containing more detailed * information about the conclusion. * - `results` (`ArcjetRuleResult[]`) - An array of {@link ArcjetRuleResult} objects * containing the results of each rule that was executed. * - `ttl` (number) - The time-to-live for the decision in seconds. This is * the time that the decision is valid for. After this time, the decision * will be re-evaluated. The SDK automatically caches DENY decisions for the * length of the TTL. * - `ip` (`ArcjetIpDetails`) - An object containing Arcjet’s analysis of the * client IP address. See * https://docs.arcjet.com/reference/nextjs#ip-analysis for more * information. * * @example * Inside server components and API route handlers: * * ```ts * // /app/api/hello/route.ts * import arcjet, { shield } from "@arcjet/next"; * import { NextResponse } from "next/server"; * * const aj = arcjet({ * key: process.env.ARCJET_KEY!, * rules: [ * // Protect against common attacks with Arcjet Shield * shield({ * mode: "LIVE", // will block requests. Use "DRY_RUN" to log only * }), * ], * }); * * export async function POST(req: Request) { * const decision = await aj.protect(req); * * if (decision.isDenied()) { * return NextResponse.json({ error: "Forbidden" }, { status: 403 }); * } * * return NextResponse.json({ * message: "Hello world", * }); * } * ``` * * @example * Server action which can be passed as a prop to a client component. See https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#client-components * * ```ts * // /app/actions.ts * "use server"; * * import arcjet, { detectBot, request } from "@arcjet/next"; * * const aj = arcjet({ * key: process.env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com * rules: [ * detectBot({ * mode: "LIVE", * allow: [], * }), * ], *}); * * export async function create() { * const req = await request(); * const decision = await aj.protect(req); * if (decision.isDenied()) { * throw new Error("Forbidden"); * } * // mutate data *} * ``` * @link https://docs.arcjet.com/reference/nextjs#protect * @link https://docs.arcjet.com/reference/nextjs#error-handling * @link https://docs.arcjet.com/reference/nextjs#decision * @link https://docs.arcjet.com/reference/nextjs#server-actions * @link https://github.com/arcjet/example-nextjs */ protect(request: ArcjetNextRequest, ...props: Props extends WithoutCustomProps ? [] : [Props]): Promise<ArcjetDecision>; /** * Augments the client with another rule. Useful for varying rules based on * criteria in your handler—e.g. different rate limit for logged in users. * * @param rule The rule to add to this execution. * @returns An augmented {@link ArcjetNext} client. * * @example * Create a base client in a separate file which sets a Shield base rule * and then use `withRule` to add a bot detection rule in a specific route * handler. * * ```ts * // /lib/arcjet.ts * import arcjet, { shield } from "@arcjet/next"; * * // Create a base Arcjet instance for use by each handler * export default arcjet({ * key: process.env.ARCJET_KEY, * rules: [ * shield({ * mode: "LIVE", * }), * ], * }); * ``` * * ```ts * // /app/api/hello/route.ts * import arcjet from "@lib/arcjet"; * import { detectBot, fixedWindow } from "@arcjet/next"; * * // Add rules to the base Arcjet instance outside of the handler function * const aj = arcjet * .withRule( * detectBot({ * mode: "LIVE", * allow: [], // blocks all automated clients * }), * ) * // You can chain multiple rules, so we'll include a rate limit * .withRule( * fixedWindow({ * mode: "LIVE", * max: 100, * window: "60s", * }), * ); * * export async function GET(req: NextRequest) { * const decision = await aj.protect(req); * if (decision.isDenied()) { * throw new Error("Forbidden"); * } * // continue with request processing * } * ``` * @link https://docs.arcjet.com/reference/nextjs#ad-hoc-rules * @link https://github.com/arcjet/example-nextjs */ withRule<Rule extends Primitive | Product>(rule: Rule): ArcjetNext<Simplify<Props & ExtraProps<Rule>>>; } /** * Create a new {@link ArcjetNext} client. Always build your initial client * outside of a request handler so it persists across requests. If you need to * augment a client inside a handler, call the `withRule()` function on the base * client. * * @param options - Arcjet configuration options to apply to all requests. */ export default function arcjet<const Rules extends (Primitive | Product)[], const Characteristics extends readonly string[]>(options: ArcjetOptions<Rules, Characteristics>): ArcjetNext<Simplify<ExtraProps<Rules> & CharacteristicProps<Characteristics>>>; /** * Protects your Next.js application using Arcjet middleware. * * @param arcjet An instantiated Arcjet SDK * @param middleware Any existing middleware you'd like to be called after * Arcjet decides a request is allowed. * @returns If the request is allowed, the next middleware or handler will be * called. If the request is denied, a `Response` will be returned immediately * and the no further middleware or handlers will be called. */ export declare function createMiddleware(arcjet: ArcjetNext<WithoutCustomProps>, existingMiddleware?: NextMiddleware): NextMiddleware; /** * Wraps a Next.js page route, edge middleware, or an API route running on the * Edge Runtime. * * @param arcjet An instantiated Arcjet SDK * @param handler The request handler to wrap * @returns If the request is allowed, the wrapped `handler` will be called. If * the request is denied, a `Response` will be returned based immediately and * the wrapped `handler` will never be called. */ export declare function withArcjet<Args extends [ArcjetNextRequest, ...unknown[]], Res>(arcjet: ArcjetNext<WithoutCustomProps>, handler: (...args: Args) => Promise<Res>): (...args: Args) => Promise<void | Res | NextResponse<{ code: number; message: string; }>>;