Merge branch 'BATON-SEC-003-backend_dev'
This commit is contained in:
commit
dbd1048a51
13 changed files with 593 additions and 125 deletions
|
|
@ -4,14 +4,16 @@ import asyncio
|
|||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import secrets
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
from fastapi import Depends, FastAPI, HTTPException, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
from backend import config, db, telegram
|
||||
from backend.middleware import rate_limit_register, rate_limit_signal, verify_admin_token, verify_webhook_secret
|
||||
|
|
@ -25,10 +27,17 @@ from backend.models import (
|
|||
SignalResponse,
|
||||
)
|
||||
|
||||
_api_key_bearer = HTTPBearer(auto_error=False)
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _hash_api_key(key: str) -> str:
|
||||
"""SHA-256 хэш для API-ключа (без соли — для быстрого сравнения)."""
|
||||
return hashlib.sha256(key.encode()).hexdigest()
|
||||
|
||||
|
||||
def _hash_password(password: str) -> str:
|
||||
"""Hash a password using PBKDF2-HMAC-SHA256 (stdlib, no external deps).
|
||||
|
||||
|
|
@ -104,7 +113,7 @@ app.add_middleware(
|
|||
CORSMiddleware,
|
||||
allow_origins=[config.FRONTEND_ORIGIN],
|
||||
allow_methods=["POST"],
|
||||
allow_headers=["Content-Type"],
|
||||
allow_headers=["Content-Type", "Authorization"],
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -116,12 +125,24 @@ async def health() -> dict[str, Any]:
|
|||
|
||||
@app.post("/api/register", response_model=RegisterResponse)
|
||||
async def register(body: RegisterRequest, _: None = Depends(rate_limit_register)) -> RegisterResponse:
|
||||
result = await db.register_user(uuid=body.uuid, name=body.name)
|
||||
return RegisterResponse(user_id=result["user_id"], uuid=result["uuid"])
|
||||
api_key = secrets.token_hex(32)
|
||||
result = await db.register_user(uuid=body.uuid, name=body.name, api_key_hash=_hash_api_key(api_key))
|
||||
return RegisterResponse(user_id=result["user_id"], uuid=result["uuid"], api_key=api_key)
|
||||
|
||||
|
||||
@app.post("/api/signal", response_model=SignalResponse)
|
||||
async def signal(body: SignalRequest, _: None = Depends(rate_limit_signal)) -> SignalResponse:
|
||||
async def signal(
|
||||
body: SignalRequest,
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(_api_key_bearer),
|
||||
_: None = Depends(rate_limit_signal),
|
||||
) -> SignalResponse:
|
||||
if credentials is None:
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
key_hash = _hash_api_key(credentials.credentials)
|
||||
stored_hash = await db.get_api_key_hash_by_uuid(body.user_id)
|
||||
if stored_hash is None or not secrets.compare_digest(key_hash, stored_hash):
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
|
||||
if await db.is_user_blocked(body.user_id):
|
||||
raise HTTPException(status_code=403, detail="User is blocked")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue