refactor(backend): extract shared helper modules per architecture rules
- Add backend/ai/utils.py — parse_classification, parse_suggestions, strip_code_fences shared by all AI providers; removes duplicated private functions from anthropic_provider.py and openai_provider.py - Add backend/deps/utils.py — get_client_ip, parse_uuid request-parsing helpers; removes local _ip() variants from admin.py, auth.py, shares.py, folders.py - Add backend/storage/exceptions.py — canonical CloudConnectionError definition; all routers and backends import from here instead of redefining - Move validate_password_strength to backend/services/auth.py; removes duplicated _validate_password_strength from admin.py and auth.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import hmac
|
||||
import logging
|
||||
import re
|
||||
import secrets
|
||||
import uuid
|
||||
from datetime import datetime, timezone, timedelta
|
||||
@@ -36,6 +37,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from config import settings
|
||||
from db.models import BackupCode, Quota, RefreshToken, User
|
||||
|
||||
_PASSWORD_DETAIL = (
|
||||
"Password must be at least 12 characters and include uppercase, "
|
||||
"lowercase, a number, and a special character."
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ── Password hashing ────────────────────────────────────────────────────────────
|
||||
@@ -59,6 +65,22 @@ def verify_password(plain: str, hashed: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def validate_password_strength(password: str) -> None:
|
||||
"""Raise ValueError with a descriptive message if *password* fails any strength rule.
|
||||
|
||||
Rules (AUTH-01): min 12 chars, uppercase, lowercase, digit, special char.
|
||||
Callers at the API boundary should catch ValueError and map it to HTTP 422.
|
||||
"""
|
||||
if (
|
||||
len(password) < 12
|
||||
or not re.search(r"[A-Z]", password)
|
||||
or not re.search(r"[a-z]", password)
|
||||
or not re.search(r"[0-9]", password)
|
||||
or not re.search(r"[^A-Za-z0-9]", password)
|
||||
):
|
||||
raise ValueError(_PASSWORD_DETAIL)
|
||||
|
||||
|
||||
# ── JWT helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def create_access_token(user_id: str, role: str) -> str:
|
||||
|
||||
Reference in New Issue
Block a user