feat: server-side password security checks

This commit is contained in:
2026-08-18 12:36:22 +07:00
parent 9e209dd695
commit 039babf540
4 changed files with 26 additions and 2 deletions

View File

@@ -21,14 +21,20 @@ class Settings(BaseSettings):
pally_shop_id: str = Field() pally_shop_id: str = Field()
pally_token: str = Field() pally_token: str = Field()
### Remnawave ###
remnawave_base_url: str = Field() remnawave_base_url: str = Field()
remnawave_sub_url: str = Field() remnawave_sub_url: str = Field()
remnawave_token: str = Field() remnawave_token: str = Field()
remnawave_default_squads_raw: str = Field(alias="REMNAWAVE_DEFAULT_SQUADS_UUIDS") remnawave_default_squads_raw: str = Field(alias="REMNAWAVE_DEFAULT_SQUADS_UUIDS")
###
minimal_deposit: int = Field() minimal_deposit: int = Field()
referal_bonus: int = Field(30) referal_bonus: int = Field(30)
### Security related ###
min_password_length: int = Field(8)
password_security_threshold: int = Field(2)
@computed_field @computed_field
@property @property
def remnawave_default_squads(self) -> list[str]: def remnawave_default_squads(self) -> list[str]:

View File

@@ -6,6 +6,7 @@ from typing import Any
import jwt import jwt
from argon2 import PasswordHasher from argon2 import PasswordHasher
from argon2.exceptions import InvalidHashError, VerificationError, VerifyMismatchError from argon2.exceptions import InvalidHashError, VerificationError, VerifyMismatchError
from zxcvbn import zxcvbn
from config import cfg from config import cfg
from schemas.dto import KeyPair from schemas.dto import KeyPair
@@ -52,3 +53,11 @@ def generate_pair(user_id: int, iss: ProvidersType) -> KeyPair:
def hash_refresh_token(token: str): def hash_refresh_token(token: str):
return hashlib.sha256(token.encode()).hexdigest() 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

View File

@@ -9,3 +9,4 @@ alembic>=1.18.0
aiohttp>=3.14.0 aiohttp>=3.14.0
python-multipart==0.0.32 python-multipart==0.0.32
remnawave>=2.6.1 remnawave>=2.6.1
zxcvbn>=4.5.0

View File

@@ -2,7 +2,12 @@ from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from sqlalchemy.ext.asyncio import AsyncSession 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 db.session import get_db
from repositories.sessions import SessionsRepository from repositories.sessions import SessionsRepository
from repositories.users import UserRepository from repositories.users import UserRepository
@@ -27,6 +32,9 @@ async def signup(req: UserRegistration, session: AsyncSession = Depends(get_db))
if user: if user:
raise HTTPException(status_code=409, detail="User already exists") 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) password_hash = hash_password(req.password)
referal_id = None referal_id = None
if req.referal_code: if req.referal_code: