Skip to main content

max / makenotwork

1.9 KB · 31 lines History Blame Raw
1 {
2 "db_name": "PostgreSQL",
3 "query": "\n WITH prev AS (\n SELECT failed_login_attempts AS old_attempts, locked_until AS old_lock\n FROM users WHERE id = $1 FOR UPDATE\n )\n UPDATE users u\n SET failed_login_attempts = u.failed_login_attempts + 1,\n last_failed_login_at = NOW(),\n locked_until = CASE\n -- Set/refresh the lock only when reaching the threshold AND not\n -- already inside an active lock window. Without the second clause,\n -- every failed attempt during a lock pushed `locked_until` forward\n -- another window, letting an attacker keep a victim perpetually\n -- locked with one wrong password every <LOCKOUT_MINUTES. Now an\n -- active lock simply runs out; a fresh failure after it expires\n -- re-locks (the counter isn't reset until a successful login).\n WHEN u.failed_login_attempts + 1 >= $2\n AND (u.locked_until IS NULL OR u.locked_until <= NOW())\n THEN NOW() + ($3 || ' minutes')::interval\n ELSE u.locked_until\n END\n FROM prev\n WHERE u.id = $1\n RETURNING\n u.failed_login_attempts,\n (prev.old_attempts + 1 >= $2\n AND (prev.old_lock IS NULL OR prev.old_lock <= NOW())) AS \"just_locked!\"\n ",
4 "describe": {
5 "columns": [
6 {
7 "ordinal": 0,
8 "name": "failed_login_attempts",
9 "type_info": "Int4"
10 },
11 {
12 "ordinal": 1,
13 "name": "just_locked!",
14 "type_info": "Bool"
15 }
16 ],
17 "parameters": {
18 "Left": [
19 "Uuid",
20 "Int4",
21 "Text"
22 ]
23 },
24 "nullable": [
25 false,
26 null
27 ]
28 },
29 "hash": "524b7050f2ed0959f47cf18c5260b12e09dd3dc23ecb808482432b60592d7e97"
30 }
31