诚实性修复: API限流Retry-After(wait=60s)+断言 / purge定时接线(04:00 cron+actor+测试) / Channel表单校验(ref+rules+validate) / README PG数字513
This commit is contained in:
@@ -72,6 +72,8 @@ async def login(request):
|
||||
"""商城登录:{phone, password, tenant_code?} → {token, customer}"""
|
||||
from django.conf import settings
|
||||
|
||||
from apps.core import ratelimit as rl
|
||||
|
||||
payload = request.data or {}
|
||||
phone = (payload.get("phone") or "").strip()
|
||||
password = payload.get("password") or ""
|
||||
@@ -81,6 +83,15 @@ async def login(request):
|
||||
if not phone or not password:
|
||||
raise ValidationError({"detail": "phone 与 password 必填"})
|
||||
|
||||
# P0-5:商城登录同样防爆破(按 phone + IP)。
|
||||
ip = rl.client_ip(request)
|
||||
login_id = f"{tenant_code}:{phone}"
|
||||
if rl.is_login_locked(login_id, ip):
|
||||
return Response(
|
||||
{"code": "login_locked", "detail": "登录失败次数过多,账号已临时锁定 15 分钟"},
|
||||
status=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
)
|
||||
|
||||
def _do():
|
||||
tenant = resolve_tenant(tenant_code)
|
||||
if tenant is None:
|
||||
@@ -106,9 +117,11 @@ async def login(request):
|
||||
raise
|
||||
|
||||
if account is None:
|
||||
rl.record_login_failure(login_id, ip)
|
||||
return Response({"code": "invalid_credentials", "detail": "手机号或密码不正确"},
|
||||
status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
rl.clear_login_failures(login_id, ip)
|
||||
token = await sync_to_async(issue_session_token)(account)
|
||||
return Response({
|
||||
"token": token,
|
||||
|
||||
Reference in New Issue
Block a user