b82c10acf4
Part of the same security-hardening pass as the body-size/header commit. loginLimiter was an in-memory, per-process sync.Mutex+map -- fine for one replica, but charts/terdut-server/values.yaml has set replicaCount: 2 in production since v0.37.0. Each pod counted only its own traffic, so every limit it guarded (failed logins, sign-ups, OIDC/device-login starts) was effectively twice as generous as the constants say, not just in theory. loginLimiter now stores its counters in a new rate_limit_counters table (migration 016) instead of a map; blocked/fail/clear take a context and query/upsert/delete a row keyed by the same strings callers already used (username, client address, "signup:"+address, ...). Semantics are unchanged -- a fixed window that resets rather than slides -- so no call site's behavior changes, only where the count lives. Added purgeRateLimits to the sweeper, alongside purgeSessions/purgeAckTokens, so expired windows don't accumulate. New internal (package api) tests in rate_limiter_test.go cover the basic behavior plus the regression this exists to fix: two loginLimiter values sharing one database, standing in for two replicas, now see one combined count instead of each keeping their own. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
17 lines
846 B
SQL
17 lines
846 B
SQL
-- Backs the rate limiters (failed logins, sign-ups, OIDC/device start) with
|
|
-- Postgres instead of an in-memory map, now that the server runs more than
|
|
-- one replica in production (v0.37.0): a counter that only ever sees its own
|
|
-- pod's traffic quietly let every one of these limits through multiplied by
|
|
-- the replica count.
|
|
--
|
|
-- window_start is the start of the current fixed window for key, in the same
|
|
-- "unix seconds" shape every other timestamp in this schema uses. The window
|
|
-- resets rather than slides, matching the in-memory limiter it replaces:
|
|
-- once a key's window is older than the limiter's window length, the next
|
|
-- failure starts a fresh one instead of extending the stale one.
|
|
CREATE TABLE rate_limit_counters (
|
|
key TEXT PRIMARY KEY,
|
|
window_start BIGINT NOT NULL,
|
|
count INT NOT NULL
|
|
);
|