brumate

Protecting server functions

A server function answers any caller. This page shows how to limit how often a caller can call it, and which sites can call it. Security lists what brumate checks for you.

Rate limiting

Server functions are public endpoints: anyone can call them with fetch, as often as they like. A sign-in, a search or a form that sends mail needs a limit. rateLimit counts the calls of each key and refuses the ones over the limit:

import { rateLimit } from 'brumate/server';

async function signIn(email: string, password: string) {
  'use server';
  await rateLimit({ ...rateLimit.strict, key: () => email.toLowerCase() });
  // …
}

Keys

key: (event, args) => … says what to count by. args are the arguments of the call, which middleware sees too. Each server function counts on its own, and each route for middleware, whatever the key.

The address of the client

The address of the client comes from the server. A server passes it to handle as address, such as the remote address of the socket. Behind a proxy, the address the server sees is the proxy's, and the client's is in X-Forwarded-For. The header is read only when the address that the server passed is one of the proxies that the app names, as addresses or CIDR ranges, and from the right, past them, because a client can write anything at its left:

import { configureRateLimit } from 'brumate/server';

configureRateLimit({ proxies: ['10.0.0.0/8'] });

A range that is not an address, or an address with a prefix length, such as 10.0.0.0/ or 10.0.0.0/33, throws. clientAddress(event) gives the address that the limits use. An IPv6 address counts by its /64, since one client often holds a whole subnet.

A limit with no key and no address would count every caller together, so one client could use up the limit of all. In production, it throws an error that names the function or the route. In development, it warns once and does not count. Pass address to the handler of the server, or set key. See Deploying for passing the address.

Calls from other sites

A call carries the cookies of the user, so the server refuses calls from pages of other sites with status 403. To allow another origin of your own, such as an admin app on another domain, list it in the plugin options:

brumate({ origins: ['https://admin.example.com'] });

Security describes the check.