auth: replace UUID-based login with JWT credential verification
Login now requires login/email + password verified against DB via /api/auth/login. Only approved registrations can access the app. Signal endpoint accepts JWT Bearer tokens alongside legacy api_key auth. Old UUID-only registration flow removed from frontend. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1adcabf3a6
commit
04f7bd79e2
8 changed files with 173 additions and 128 deletions
|
|
@ -18,6 +18,7 @@ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|||
|
||||
from backend import config, db, push, telegram
|
||||
from backend.middleware import (
|
||||
_verify_jwt_token,
|
||||
create_auth_token,
|
||||
rate_limit_auth_login,
|
||||
rate_limit_auth_register,
|
||||
|
|
@ -176,13 +177,36 @@ async def 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")
|
||||
user_identifier: str = ""
|
||||
user_name: str = ""
|
||||
|
||||
# Try JWT auth first (new registration flow)
|
||||
jwt_payload = None
|
||||
try:
|
||||
jwt_payload = _verify_jwt_token(credentials.credentials)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if jwt_payload is not None:
|
||||
reg_id = int(jwt_payload["sub"])
|
||||
reg = await db.get_registration(reg_id)
|
||||
if reg is None or reg["status"] != "approved":
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
user_identifier = reg["login"]
|
||||
user_name = reg["login"]
|
||||
else:
|
||||
# Legacy api_key auth
|
||||
if not body.user_id:
|
||||
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")
|
||||
user_identifier = body.user_id
|
||||
user_name = await db.get_user_name(body.user_id) or body.user_id[:8]
|
||||
|
||||
geo = body.geo
|
||||
lat = geo.lat if geo else None
|
||||
|
|
@ -190,23 +214,21 @@ async def signal(
|
|||
accuracy = geo.accuracy if geo else None
|
||||
|
||||
signal_id = await db.save_signal(
|
||||
user_uuid=body.user_id,
|
||||
user_uuid=user_identifier,
|
||||
timestamp=body.timestamp,
|
||||
lat=lat,
|
||||
lon=lon,
|
||||
accuracy=accuracy,
|
||||
)
|
||||
|
||||
user_name = await db.get_user_name(body.user_id)
|
||||
ts = datetime.fromtimestamp(body.timestamp / 1000, tz=timezone.utc)
|
||||
name = user_name or body.user_id[:8]
|
||||
geo_info = (
|
||||
f"📍 {lat}, {lon} (±{accuracy}м)"
|
||||
if geo
|
||||
else "Без геолокации"
|
||||
)
|
||||
text = (
|
||||
f"🚨 Сигнал от {name}\n"
|
||||
f"🚨 Сигнал от {user_name}\n"
|
||||
f"⏰ {ts.strftime('%H:%M:%S')} UTC\n"
|
||||
f"{geo_info}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class GeoData(BaseModel):
|
|||
|
||||
|
||||
class SignalRequest(BaseModel):
|
||||
user_id: str = Field(..., pattern=r'^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$')
|
||||
user_id: Optional[str] = None # UUID for legacy api_key auth; omit for JWT auth
|
||||
timestamp: int = Field(..., gt=0)
|
||||
geo: Optional[GeoData] = None
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue