@arcjet/next
Version:
Arcjet SDK for the Next.js framework
416 lines (415 loc) • 14 kB
TypeScript
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";
/**
* Get minimal request details (cookies, headers).
*
* This function can be used in server components, server actions,
* route handlers, and middleware.
*
* @returns
* Promise that resolves to the request details.
*/
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;
};
/**
* Configuration for {@linkcode createRemoteClient}.
*/
export type RemoteClientOptions = {
/**
* Base URI for HTTP requests to Decide API (optional).
*
* Defaults to the environment variable `ARCJET_BASE_URL` (if that value
* is known and allowed) and the standard production API otherwise.
*/
baseUrl?: string;
/**
* Timeout in milliseconds for the Decide API (optional).
*
* Defaults to `500` in production and `1000` in development.
*/
timeout?: number;
};
/**
* Create a remote client.
*
* @param options
* Configuration (optional).
* @returns
* Client.
*/
export declare function createRemoteClient(options?: RemoteClientOptions): import("@arcjet/protocol/client.js").Client;
/**
* Request for the Next.js integration of Arcjet.
*
* This is the minimum interface that can be supplied via
* {@linkcode NextRequest} and {@linkcode NextApiRequest},
* but also from our {@linkcode request} helper in server components,
* server actions, route handlers, and middleware.
*/
export interface ArcjetNextRequest {
/**
* Headers of the request.
*/
headers?: Record<string, string | string[] | undefined> | Headers;
/**
* `net.Socket` object associated with the connection.
*
* This field is available on Express requests,
* as those inherit from Node `http.IncomingMessage`.
*
* See <https://nodejs.org/api/http.html#messagesocket>.
*/
socket?: Partial<{
remoteAddress: string;
encrypted: boolean;
}>;
/**
* Some platforms pass `info`.
*/
info?: Partial<{
remoteAddress: string;
}>;
/**
* Some platforms pass info in `requestContext`.
*/
requestContext?: Partial<{
identity: Partial<{
sourceIp: string;
}>;
}>;
/**
* HTTP method of the request.
*/
method?: string;
/**
* In case of server request, the HTTP version sent by the client.
*
* See <https://nodejs.org/api/http.html#messagehttpversion>.
*/
httpVersion?: string;
/**
* URL.
*/
url?: string;
/**
* IP address of the client.
*/
ip?: string;
/**
* Next.js URL, with lots of info, but this is what we use.
*/
nextUrl?: Partial<{
pathname: string;
search: string;
protocol: string;
}>;
/**
* Object of `cookies` from header.
*/
cookies?: {
[Symbol.iterator](): IterableIterator<[
string,
{
name: string;
value: string;
}
]>;
} | Partial<{
[key: string]: string;
}>;
/**
* Clones the request.
*
* @returns
* Cloned request.
*/
clone?: () => Request;
/**
* Body of the request.
*/
body?: unknown;
}
/**
* Configuration for the Next.js integration of Arcjet.
*
* @template Rules
* List of rules.
* @template Characteristics
* Characteristics to track a user by.
*/
export type ArcjetOptions<Rules extends [...Array<Primitive | Product>], Characteristics extends readonly string[]> = Simplify<CoreOptions<Rules, Characteristics> & {
/**
* IP addresses and CIDR ranges of trusted load balancers and proxies
* (optional, example: `["100.100.100.100", "100.100.100.0/24"]`).
*/
proxies?: Array<string>;
}>;
/**
* Instance of the Next integration of Arcjet.
*
* Primarily has a `protect()` method to make a decision about how a Next request
* should be handled.
*
* @template Props
* Configuration.
*/
export interface ArcjetNext<Props extends PlainObject> {
/**
* Make a decision about how to handle a request.
*
* This will analyze the request locally where possible and otherwise call
* the Arcjet decision API.
*
* 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 request
* Details about the {@linkcode ArcjetNextRequest} that Arcjet needs to make a
* decision.
* @param props
* Additional properties required for running rules against a request.
* Whether this is required depends on the rules you've configured.
* @returns
* Promise that resolves to an {@linkcode ArcjetDecision} indicating
* Arcjet’s decision about the request.
*
* This contains the following properties:
*
* - `id` (`string`)
* — 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 {@linkcode 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>;
/**
* Augment the client with another rule.
*
* Useful for varying rules based on criteria in your handler such as
* different rate limit for logged in users.
*
* @template Rule
* Type of rule.
* @param rule
* Rule to add to Arcjet.
* @returns
* Arcjet instance augmented with the given rule.
*
* @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 Next.js integration of Arcjet.
*
* > 👉 **Tip**:
* > build your initial base client with as many rules as possible outside of a
* > request handler;
* > if you need more rules inside handlers later then you can call `withRule()`
* > on that base client.
*
* @template Rules
* List of rules.
* @template Characteristics
* Characteristics to track a user by.
* @param options
* Configuration.
* @returns
* Next.js integration of Arcjet.
*/
export default function arcjet<const Rules extends (Primitive | Product)[], const Characteristics extends readonly string[]>(options: ArcjetOptions<Rules, Characteristics>): ArcjetNext<Simplify<ExtraProps<Rules> & CharacteristicProps<Characteristics>>>;
/**
* Protect your Next.js application using Arcjet as middleware.
*
* @param arcjet
* Next.js integration of Arcjet.
* @param existingMiddleware
* Existing middleware to be called after Arcjet decides that a request is
* allowed.
* @returns
* Next.js middleware that will run Arcjet and when it allows the request
* call further middleware,
* but when it denies the request will immediately return a JSON
* `NextResponse` with `403` or `429`.
*/
export declare function createMiddleware(arcjet: ArcjetNext<WithoutCustomProps>, existingMiddleware?: NextMiddleware): NextMiddleware;
/**
* Wrap a Next.js page route, edge middleware, or an API route running on the
* Edge Runtime.
*
* @param arcjet
* Next.js integration of Arcjet.
* @param handler
* Request handler to be called after Arcjet decides that a request is
* allowed.
* @returns
* Function that will run Arcjet on a request and when it is allowed call
* `handler`,
* but when it is denied will immediately return a JSON `NextResponse` or
* `NextApiResponse` with `403` or `429`.
*/
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;
}>>;