kin: BATON-BIZ-001-backend_dev
This commit is contained in:
parent
e266b6506e
commit
ea06309a6e
5 changed files with 160 additions and 1 deletions
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import asyncio
|
||||
import hashlib
|
||||
import hmac
|
||||
import logging
|
||||
import os
|
||||
import secrets
|
||||
|
|
@ -16,11 +17,21 @@ from fastapi.responses import JSONResponse
|
|||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
from backend import config, db, push, telegram
|
||||
from backend.middleware import rate_limit_auth_register, rate_limit_register, rate_limit_signal, verify_admin_token, verify_webhook_secret
|
||||
from backend.middleware import (
|
||||
create_auth_token,
|
||||
rate_limit_auth_login,
|
||||
rate_limit_auth_register,
|
||||
rate_limit_register,
|
||||
rate_limit_signal,
|
||||
verify_admin_token,
|
||||
verify_webhook_secret,
|
||||
)
|
||||
from backend.models import (
|
||||
AdminBlockRequest,
|
||||
AdminCreateUserRequest,
|
||||
AdminSetPasswordRequest,
|
||||
AuthLoginRequest,
|
||||
AuthLoginResponse,
|
||||
AuthRegisterRequest,
|
||||
AuthRegisterResponse,
|
||||
RegisterRequest,
|
||||
|
|
@ -51,6 +62,18 @@ def _hash_password(password: str) -> str:
|
|||
dk = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 260_000)
|
||||
return f"{salt.hex()}:{dk.hex()}"
|
||||
|
||||
def _verify_password(password: str, stored_hash: str) -> bool:
|
||||
"""Verify a password against a stored PBKDF2-HMAC-SHA256 hash (salt_hex:dk_hex)."""
|
||||
try:
|
||||
salt_hex, dk_hex = stored_hash.split(":", 1)
|
||||
salt = bytes.fromhex(salt_hex)
|
||||
expected_dk = bytes.fromhex(dk_hex)
|
||||
actual_dk = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 260_000)
|
||||
return hmac.compare_digest(actual_dk, expected_dk)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
# aggregator = telegram.SignalAggregator(interval=10) # v2.0 feature — отключено в v1 (ADR-004)
|
||||
|
||||
_KEEPALIVE_INTERVAL = 600 # 10 минут
|
||||
|
|
@ -225,6 +248,24 @@ async def auth_register(
|
|||
return AuthRegisterResponse(status="pending", message="Заявка отправлена на рассмотрение")
|
||||
|
||||
|
||||
@app.post("/api/auth/login", response_model=AuthLoginResponse)
|
||||
async def auth_login(
|
||||
body: AuthLoginRequest,
|
||||
_: None = Depends(rate_limit_auth_login),
|
||||
) -> AuthLoginResponse:
|
||||
reg = await db.get_registration_by_login_or_email(body.login_or_email)
|
||||
if reg is None or not _verify_password(body.password, reg["password_hash"]):
|
||||
raise HTTPException(status_code=401, detail="Неверный логин или пароль")
|
||||
if reg["status"] == "pending":
|
||||
raise HTTPException(status_code=403, detail="Ваша заявка ожидает рассмотрения")
|
||||
if reg["status"] == "rejected":
|
||||
raise HTTPException(status_code=403, detail="Ваша заявка отклонена")
|
||||
if reg["status"] != "approved":
|
||||
raise HTTPException(status_code=403, detail="Доступ запрещён")
|
||||
token = create_auth_token(reg["id"], reg["login"])
|
||||
return AuthLoginResponse(token=token, login=reg["login"])
|
||||
|
||||
|
||||
async def _handle_callback_query(cb: dict) -> None:
|
||||
"""Process approve/reject callback from admin Telegram inline buttons."""
|
||||
data = cb.get("data", "")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue