MySQL · 2698 bytes Raw Blame History
1 -- SPDX-License-Identifier: AGPL-3.0-or-later
2 --
3 -- S35 — Generalized rate-limiting + per-/24 signup throttle.
4 --
5 -- * rate_limits — counter table for any (scope, key) pair,
6 -- generalizing S05's auth_throttle. The
7 -- ratelimit package is the single writer;
8 -- auth_throttle stays in place for the
9 -- existing auth surface (kept for back-
10 -- compat — generalising S05 callers can
11 -- land in a follow-up if profiling shows
12 -- the dual table to be wasteful).
13 --
14 -- * signup_ip_throttle — per-/24 signup counter. Distinct from
15 -- rate_limits because the key is a CIDR
16 -- block (not a string). Used to throw a
17 -- soft-block at 5 signups/hour and a hard
18 -- block at 20/24h, matching the spec's
19 -- anti-abuse heuristics. (Captcha gating
20 -- is the natural next step for the soft
21 -- block; vendor decision is deferred —
22 -- the gate stays here as a 429 today.)
23 --
24 -- Pruning: a periodic worker (sweep job, S34's worker pool) deletes
25 -- rows whose window started more than 24h ago. The covering index
26 -- on window_started_at keeps the prune cheap.
27
28 -- +goose Up
29 CREATE TABLE rate_limits (
30 scope text NOT NULL,
31 key text NOT NULL,
32 hits integer NOT NULL DEFAULT 0,
33 window_started_at timestamptz NOT NULL DEFAULT now(),
34
35 PRIMARY KEY (scope, key),
36 CONSTRAINT rate_limits_scope_length CHECK (char_length(scope) BETWEEN 1 AND 64),
37 CONSTRAINT rate_limits_key_length CHECK (char_length(key) BETWEEN 1 AND 256)
38 );
39
40 -- Periodic prune scans by window_started_at; partial index on the
41 -- "old enough to delete" predicate isn't worth it because the cutoff
42 -- moves continuously.
43 CREATE INDEX rate_limits_window_started_idx ON rate_limits (window_started_at);
44
45 CREATE TABLE signup_ip_throttle (
46 -- inet column accepts the CIDR (/24 for v4, /48 for v6) as a
47 -- subtype. Storing the network and the rolling counter together.
48 cidr inet NOT NULL,
49 hits integer NOT NULL DEFAULT 0,
50 window_started_at timestamptz NOT NULL DEFAULT now(),
51
52 PRIMARY KEY (cidr)
53 );
54
55 CREATE INDEX signup_ip_throttle_window_started_idx
56 ON signup_ip_throttle (window_started_at);
57
58 -- +goose Down
59 DROP TABLE IF EXISTS signup_ip_throttle;
60 DROP TABLE IF EXISTS rate_limits;
61