{ "db_name": "PostgreSQL", "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 = $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 ", "describe": { "columns": [ { "ordinal": 0, "name": "failed_login_attempts", "type_info": "Int4" }, { "ordinal": 1, "name": "just_locked!", "type_info": "Bool" } ], "parameters": { "Left": [ "Uuid", "Int4", "Text" ] }, "nullable": [ false, null ] }, "hash": "524b7050f2ed0959f47cf18c5260b12e09dd3dc23ecb808482432b60592d7e97" }