fix(503-4 로그인 잠금): 5 번 틀림 뒤 잠금 · 경고 메일 — 잠금 판정 별도 UPDATE · 신뢰 기기 확인 읽기만
- record_failed_login: 한 UPDATE 안 왼→오 대입으로 4 번째에 잠기던 흠 — 잠금은 failures >= 5 별도 UPDATE - has_trusted_device: last_used_at UPDATE 뺌 — 마무리 트랜잭션 _trust_device 가 갱신(커밋 1) - 시험: 4 번 안 잠김 · 5 번 잠김 · 경고 메일 4/5 · 쿠키 재로그인 커밋 1 · 살아 있는 서버 손 시험 helper_503_4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -212,13 +212,17 @@ async def record_failed_login(user_id: int) -> int:
|
||||
async with pool.acquire() as connection, connection.cursor() as cursor:
|
||||
await cursor.execute(
|
||||
"""UPDATE users SET login_failures = login_failures + 1,
|
||||
last_failed_at = CURRENT_TIMESTAMP,
|
||||
account_locked_until = CASE WHEN login_failures + 1 >= 5
|
||||
THEN DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 15 MINUTE)
|
||||
ELSE account_locked_until END
|
||||
last_failed_at = CURRENT_TIMESTAMP
|
||||
WHERE id = %s""",
|
||||
(user_id,),
|
||||
)
|
||||
# 잠금은 따로 — 한 UPDATE 안에서는 MariaDB 가 앞 대입(+1)을 본 값으로 판정해 4 번째에 잠김
|
||||
await cursor.execute(
|
||||
"""UPDATE users
|
||||
SET account_locked_until = DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 15 MINUTE)
|
||||
WHERE id = %s AND login_failures >= 5""",
|
||||
(user_id,),
|
||||
)
|
||||
await cursor.execute("SELECT login_failures FROM users WHERE id = %s", (user_id,))
|
||||
failures = (await cursor.fetchone())[0]
|
||||
await connection.commit()
|
||||
@@ -265,6 +269,7 @@ async def finish_login(
|
||||
|
||||
|
||||
async def has_trusted_device(user_id: int, token_hash: str) -> bool:
|
||||
"""읽기만 — last_used_at 갱신은 finish_login 트랜잭션의 _trust_device 가 함."""
|
||||
valid_after = datetime.utcnow() - timedelta(days=EMAIL_REVERIFY_DAYS)
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection, connection.cursor() as cursor:
|
||||
@@ -273,15 +278,7 @@ async def has_trusted_device(user_id: int, token_hash: str) -> bool:
|
||||
WHERE user_id = %s AND token_hash = %s AND last_used_at >= %s LIMIT 1""",
|
||||
(user_id, token_hash, valid_after),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
if not row:
|
||||
return False
|
||||
await cursor.execute(
|
||||
"UPDATE trusted_devices SET last_used_at = CURRENT_TIMESTAMP WHERE id = %s",
|
||||
(row[0],),
|
||||
)
|
||||
await connection.commit()
|
||||
return True
|
||||
return bool(await cursor.fetchone())
|
||||
|
||||
|
||||
async def trust_device(
|
||||
|
||||
Reference in New Issue
Block a user