feat(auth): add 2FA with OTP, backup codes and trusted devices
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,10 @@ class LoginRequest(BaseModel):
|
||||
description="ID-ul serverului Oracle pentru autentificare (opțional în modul single-server)",
|
||||
example="romfast"
|
||||
)
|
||||
trusted_device_token: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Token de trusted device din localStorage (pentru skip 2FA)"
|
||||
)
|
||||
|
||||
@validator('username')
|
||||
def username_alphanumeric(cls, v):
|
||||
@@ -83,17 +87,25 @@ class TokenResponse(BaseModel):
|
||||
"""Model pentru răspunsul de autentificare cu token-uri"""
|
||||
access_token: str = Field(description="JWT access token")
|
||||
refresh_token: Optional[str] = Field(
|
||||
default=None,
|
||||
default=None,
|
||||
description="JWT refresh token (opțional)"
|
||||
)
|
||||
token_type: str = Field(
|
||||
default="bearer",
|
||||
default="bearer",
|
||||
description="Tipul token-ului (întotdeauna 'bearer')"
|
||||
)
|
||||
expires_in: int = Field(
|
||||
description="Timpul de expirare al access token-ului în secunde"
|
||||
)
|
||||
user: 'CurrentUser' = Field(description="Informațiile utilizatorului autentificat")
|
||||
trusted_device_token: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Token de stocat în localStorage (prezent doar dacă trust_device=True)"
|
||||
)
|
||||
backup_codes: Optional[list[str]] = Field(
|
||||
default=None,
|
||||
description="Coduri de backup generate la primul 2FA reușit (afișați utilizatorului o singură dată!)"
|
||||
)
|
||||
|
||||
|
||||
class RefreshTokenRequest(BaseModel):
|
||||
@@ -340,5 +352,70 @@ class CheckEmailResponse(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class VerifyBackupCodeRequest(BaseModel):
|
||||
"""Request pentru POST /auth/verify-backup-code"""
|
||||
code: str = Field(..., min_length=6, max_length=12, description="Codul de recuperare (ex: AB3K9PQR)")
|
||||
email: str = Field(..., description="Email sau username")
|
||||
server_id: Optional[str] = Field(default=None, description="ID server Oracle")
|
||||
trust_device: bool = Field(default=False, description="Ține minte dispozitivul 30 de zile")
|
||||
|
||||
|
||||
# Update la forward references pentru TokenResponse
|
||||
TokenResponse.model_rebuild()
|
||||
TokenResponse.model_rebuild()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# MODELE 2FA WEB LOGIN
|
||||
# ============================================================================
|
||||
|
||||
class LoginRequires2FAResponse(BaseModel):
|
||||
"""
|
||||
Răspuns returnat de POST /auth/login când 2FA este necesar.
|
||||
|
||||
Frontend-ul detectează câmpul requires_2fa=True și afișează pasul de cod.
|
||||
Email-ul complet se trimite la /auth/verify-2fa-code.
|
||||
"""
|
||||
requires_2fa: bool = Field(
|
||||
default=True,
|
||||
description="Întotdeauna True când se solicită 2FA"
|
||||
)
|
||||
masked_email: str = Field(
|
||||
description="Emailul mascat pentru afișare (ex: m***@romfast.ro)"
|
||||
)
|
||||
email: str = Field(
|
||||
description="Emailul complet — de trimis la /auth/verify-2fa-code"
|
||||
)
|
||||
|
||||
|
||||
class Verify2FARequest(BaseModel):
|
||||
"""Request pentru POST /auth/verify-2fa-code"""
|
||||
code: str = Field(
|
||||
...,
|
||||
min_length=6,
|
||||
max_length=6,
|
||||
description="Codul OTP de 6 cifre primit pe email"
|
||||
)
|
||||
email: str = Field(
|
||||
...,
|
||||
description="Emailul primit în răspunsul de la /auth/login (câmpul 'email')"
|
||||
)
|
||||
server_id: Optional[str] = Field(
|
||||
default=None,
|
||||
description="ID-ul serverului Oracle (pentru multi-server mode)"
|
||||
)
|
||||
trust_device: bool = Field(
|
||||
default=False,
|
||||
description="Dacă utilizatorul vrea să fie ținut minte pe acest dispozitiv"
|
||||
)
|
||||
|
||||
|
||||
class Resend2FARequest(BaseModel):
|
||||
"""Request pentru POST /auth/resend-2fa-code"""
|
||||
email: str = Field(
|
||||
...,
|
||||
description="Emailul unde se retrimite codul OTP"
|
||||
)
|
||||
server_id: Optional[str] = Field(
|
||||
default=None,
|
||||
description="ID-ul serverului Oracle (pentru multi-server mode)"
|
||||
)
|
||||
Reference in New Issue
Block a user