feat: server-side password security checks
This commit is contained in:
@@ -21,14 +21,20 @@ class Settings(BaseSettings):
|
||||
pally_shop_id: str = Field()
|
||||
pally_token: str = Field()
|
||||
|
||||
### Remnawave ###
|
||||
remnawave_base_url: str = Field()
|
||||
remnawave_sub_url: str = Field()
|
||||
remnawave_token: str = Field()
|
||||
remnawave_default_squads_raw: str = Field(alias="REMNAWAVE_DEFAULT_SQUADS_UUIDS")
|
||||
|
||||
###
|
||||
minimal_deposit: int = Field()
|
||||
referal_bonus: int = Field(30)
|
||||
|
||||
### Security related ###
|
||||
min_password_length: int = Field(8)
|
||||
password_security_threshold: int = Field(2)
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def remnawave_default_squads(self) -> list[str]:
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Any
|
||||
import jwt
|
||||
from argon2 import PasswordHasher
|
||||
from argon2.exceptions import InvalidHashError, VerificationError, VerifyMismatchError
|
||||
from zxcvbn import zxcvbn
|
||||
|
||||
from config import cfg
|
||||
from schemas.dto import KeyPair
|
||||
@@ -52,3 +53,11 @@ def generate_pair(user_id: int, iss: ProvidersType) -> KeyPair:
|
||||
|
||||
def hash_refresh_token(token: str):
|
||||
return hashlib.sha256(token.encode()).hexdigest()
|
||||
|
||||
|
||||
def estimate_password_strength(password: str) -> bool:
|
||||
if len(password) < cfg.min_password_length:
|
||||
return False
|
||||
|
||||
r = zxcvbn(password)
|
||||
return r.get("score", 0) > cfg.password_security_threshold
|
||||
|
||||
@@ -9,3 +9,4 @@ alembic>=1.18.0
|
||||
aiohttp>=3.14.0
|
||||
python-multipart==0.0.32
|
||||
remnawave>=2.6.1
|
||||
zxcvbn>=4.5.0
|
||||
|
||||
@@ -2,7 +2,12 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from core.secrets import hash_password, hash_refresh_token, verify_password
|
||||
from core.secrets import (
|
||||
estimate_password_strength,
|
||||
hash_password,
|
||||
hash_refresh_token,
|
||||
verify_password,
|
||||
)
|
||||
from db.session import get_db
|
||||
from repositories.sessions import SessionsRepository
|
||||
from repositories.users import UserRepository
|
||||
@@ -27,6 +32,9 @@ async def signup(req: UserRegistration, session: AsyncSession = Depends(get_db))
|
||||
if user:
|
||||
raise HTTPException(status_code=409, detail="User already exists")
|
||||
|
||||
if not estimate_password_strength(req.password):
|
||||
raise HTTPException(422, detail="Password is not secure.")
|
||||
|
||||
password_hash = hash_password(req.password)
|
||||
referal_id = None
|
||||
if req.referal_code:
|
||||
|
||||
Reference in New Issue
Block a user