diff --git a/config.py b/config.py index 9f36c9a..98505f1 100644 --- a/config.py +++ b/config.py @@ -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]: diff --git a/core/secrets.py b/core/secrets.py index c8e22c1..378ded5 100644 --- a/core/secrets.py +++ b/core/secrets.py @@ -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 diff --git a/requirements.txt b/requirements.txt index 47e494b..652c32d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,5 @@ asyncpg>=0.31.0 alembic>=1.18.0 aiohttp>=3.14.0 python-multipart==0.0.32 -remnawave>=2.6.1 \ No newline at end of file +remnawave>=2.6.1 +zxcvbn>=4.5.0 diff --git a/routes/auth.py b/routes/auth.py index 29bfcfb..21f7c27 100644 --- a/routes/auth.py +++ b/routes/auth.py @@ -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: