2026.06.04 frontend építés közben
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/services/auth_service.py
|
||||
import logging
|
||||
import uuid
|
||||
import re
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, and_, update
|
||||
@@ -23,6 +24,41 @@ from app.services.gamification_service import GamificationService
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class AuthService:
|
||||
@staticmethod
|
||||
async def _validate_password_complexity(db: AsyncSession, password: str, region_code: str = None):
|
||||
"""
|
||||
Dinamikus jelszó komplexitás ellenőrzése az admin beállítások alapján.
|
||||
"""
|
||||
# Alap beállítások lekérése
|
||||
min_pass = await config.get_setting(db, "auth_min_password_length", default=8)
|
||||
password_strict = await config.get_setting(db, "auth_password_strict", default=False)
|
||||
|
||||
# Minimum hossz ellenőrzése
|
||||
if len(password) < int(min_pass):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"A jelszónak legalább {min_pass} karakter hosszúnak kell lennie."
|
||||
)
|
||||
|
||||
# Ha a strict mód be van kapcsolva, komplexitás ellenőrzés
|
||||
if password_strict and str(password_strict).lower() in ("true", "1", "yes"):
|
||||
# Ellenőrizzük: legalább 1 nagybetű, 1 kisbetű, 1 szám vagy speciális karakter
|
||||
if not re.search(r'[A-Z]', password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="A jelszónak tartalmaznia kell legalább egy nagybetűt."
|
||||
)
|
||||
if not re.search(r'[a-z]', password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="A jelszónak tartalmaznia kell legalább egy kisbetűt."
|
||||
)
|
||||
if not re.search(r'[0-9!@#$%^&*()_+\-=\[\]{};:"\\|,.<>/?]', password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="A jelszónak tartalmaznia kell legalább egy számot vagy speciális karaktert."
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def register_lite(db: AsyncSession, user_in: UserLiteRegister):
|
||||
""" 1. FÁZIS: Lite regisztráció dinamikus korlátokkal és Sentinel naplózással. """
|
||||
@@ -32,11 +68,8 @@ class AuthService:
|
||||
default_role_name = await config.get_setting(db, "auth_default_role", default="user")
|
||||
reg_token_hours = await config.get_setting(db, "auth_registration_hours", region_code=user_in.region_code, default=48)
|
||||
|
||||
if len(user_in.password) < int(min_pass):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"A jelszónak legalább {min_pass} karakter hosszúnak kell lennie."
|
||||
)
|
||||
# Jelszó komplexitás ellenőrzése
|
||||
await AuthService._validate_password_complexity(db, user_in.password, user_in.region_code)
|
||||
|
||||
# Check if email already exists
|
||||
existing_user = await db.execute(select(User).where(User.email == user_in.email))
|
||||
@@ -46,6 +79,17 @@ class AuthService:
|
||||
detail="Ez az email cím már regisztrálva van."
|
||||
)
|
||||
|
||||
# Meghívó keresése referral kód alapján
|
||||
referred_by_id = None
|
||||
if user_in.referred_by_code:
|
||||
referrer_stmt = select(User).where(User.referral_code == user_in.referred_by_code)
|
||||
referrer = (await db.execute(referrer_stmt)).scalar_one_or_none()
|
||||
if referrer:
|
||||
referred_by_id = referrer.id
|
||||
logger.info(f"User {user_in.email} referred by {referrer.email} (ID: {referrer.id})")
|
||||
else:
|
||||
logger.warning(f"Referral code '{user_in.referred_by_code}' not found, ignoring.")
|
||||
|
||||
new_person = Person(
|
||||
first_name=user_in.first_name,
|
||||
last_name=user_in.last_name,
|
||||
@@ -65,6 +109,9 @@ class AuthService:
|
||||
# Szerepkör dinamikus feloldása
|
||||
assigned_role = UserRole[default_role_name] if default_role_name in UserRole.__members__ else UserRole.user
|
||||
|
||||
# Referral kód generálása
|
||||
referral_code = generate_secure_slug(8).upper()
|
||||
|
||||
new_user = User(
|
||||
email=user_in.email,
|
||||
hashed_password=get_password_hash(user_in.password),
|
||||
@@ -80,6 +127,8 @@ class AuthService:
|
||||
preferred_currency="HUF",
|
||||
scope_level="individual",
|
||||
custom_permissions={},
|
||||
referral_code=referral_code,
|
||||
referred_by_id=referred_by_id,
|
||||
created_at=datetime.now(timezone.utc)
|
||||
)
|
||||
db.add(new_user)
|
||||
@@ -94,21 +143,6 @@ class AuthService:
|
||||
expires_at=datetime.now(timezone.utc) + timedelta(hours=int(reg_token_hours))
|
||||
))
|
||||
|
||||
# Email küldés a beállított template alapján
|
||||
verification_link = f"{settings.FRONTEND_BASE_URL}/verify?token={token_val}"
|
||||
email_result = await email_manager.send_email(
|
||||
recipient=user_in.email,
|
||||
template_key="reg",
|
||||
variables={"first_name": user_in.first_name, "link": verification_link},
|
||||
lang=user_in.lang
|
||||
)
|
||||
# Check if email sending failed
|
||||
if email_result and email_result.get("status") == "error":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Email delivery failed. Please contact support."
|
||||
)
|
||||
|
||||
# Sentinel Audit Log
|
||||
await security_service.log_event(
|
||||
db, user_id=new_user.id, action="USER_REGISTER_LITE",
|
||||
@@ -117,6 +151,23 @@ class AuthService:
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
|
||||
# Email küldés a beállított template alapján
|
||||
verification_link = f"{settings.FRONTEND_BASE_URL}/verify?token={token_val}"
|
||||
email_result = await email_manager.send_email(
|
||||
recipient=user_in.email,
|
||||
template_key="reg",
|
||||
variables={"first_name": user_in.first_name, "link": verification_link},
|
||||
lang=user_in.lang
|
||||
)
|
||||
# Check if email sending failed - LOG but don't crash registration
|
||||
if email_result and email_result.get("status") == "error":
|
||||
logger.error(f"Email sending failed for {user_in.email}: {email_result.get('message')}")
|
||||
# Don't raise exception - registration should succeed even if email fails
|
||||
# Just log the error and continue
|
||||
else:
|
||||
logger.info(f"Email sent successfully to {user_in.email}")
|
||||
|
||||
return new_user
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
@@ -143,16 +194,53 @@ class AuthService:
|
||||
house_number=kyc_in.address_house_number, parcel_id=kyc_in.address_hrsz
|
||||
)
|
||||
|
||||
|
||||
# Person adatok dúsítása
|
||||
p = user.person
|
||||
p.mothers_last_name = kyc_in.mothers_last_name
|
||||
p.mothers_first_name = kyc_in.mothers_first_name
|
||||
p.birth_place = kyc_in.birth_place
|
||||
p.birth_date = kyc_in.birth_date
|
||||
p.phone = kyc_in.phone_number
|
||||
p.address_id = addr_id
|
||||
p.identity_docs = jsonable_encoder(kyc_in.identity_docs)
|
||||
p.is_active = True
|
||||
|
||||
# --- SHADOW IDENTITY CHECK ---
|
||||
if kyc_in.birth_date:
|
||||
shadow_stmt = select(Person).where(
|
||||
and_(
|
||||
Person.last_name == p.last_name,
|
||||
Person.first_name == p.first_name,
|
||||
Person.birth_date == kyc_in.birth_date,
|
||||
Person.id != p.id # Ne a jelenlegit találja meg
|
||||
)
|
||||
)
|
||||
shadow_person = (await db.execute(shadow_stmt)).scalar_one_or_none()
|
||||
else:
|
||||
shadow_person = None
|
||||
|
||||
if shadow_person:
|
||||
logger.info(f"Shadow Identity megtalálva a {user.id} userhez: Person ID {shadow_person.id}")
|
||||
user.person_id = shadow_person.id
|
||||
|
||||
# Frissítjük a megtalált Person rekordot a KYC adatokkal
|
||||
shadow_person.mothers_last_name = kyc_in.mothers_last_name
|
||||
shadow_person.mothers_first_name = kyc_in.mothers_first_name
|
||||
shadow_person.birth_place = kyc_in.birth_place
|
||||
shadow_person.phone = kyc_in.phone_number
|
||||
shadow_person.address_id = addr_id
|
||||
shadow_person.identity_docs = jsonable_encoder(kyc_in.identity_docs)
|
||||
shadow_person.is_active = True
|
||||
|
||||
# Inaktiváljuk/töröljük a megárvult eredeti Person-t
|
||||
p.is_active = False
|
||||
p.is_ghost = True
|
||||
|
||||
p = shadow_person
|
||||
else:
|
||||
p.mothers_last_name = kyc_in.mothers_last_name
|
||||
p.mothers_first_name = kyc_in.mothers_first_name
|
||||
p.birth_place = kyc_in.birth_place
|
||||
p.birth_date = kyc_in.birth_date
|
||||
p.phone = kyc_in.phone_number
|
||||
p.address_id = addr_id
|
||||
p.identity_docs = jsonable_encoder(kyc_in.identity_docs)
|
||||
p.is_active = True
|
||||
# --- SHADOW CHECK VÉGE ---
|
||||
|
||||
|
||||
# Dinamikus szervezet generálás
|
||||
org_full_name = org_tpl.format(last_name=p.last_name, first_name=p.first_name)
|
||||
@@ -183,16 +271,30 @@ class AuthService:
|
||||
db.add(Branch(organization_id=new_org.id, address_id=addr_id, name="Home Base", is_main=True))
|
||||
db.add(OrganizationMember(organization_id=new_org.id, user_id=user.id, role="OWNER"))
|
||||
db.add(Wallet(user_id=user.id, currency=kyc_in.preferred_currency or base_cur))
|
||||
db.add(UserStats(user_id=user.id))
|
||||
# db.add(UserStats(user_id=user.id)) # GamificationService kezeli
|
||||
|
||||
user.is_active = True
|
||||
user.folder_slug = generate_secure_slug(12)
|
||||
# Set user's scope_id to the new personal organization ID
|
||||
user.scope_id = str(new_org.id)
|
||||
# Update region_code from KYC form (EU country selection)
|
||||
if kyc_in.region_code:
|
||||
user.region_code = kyc_in.region_code
|
||||
|
||||
# Gamification XP jóváírás
|
||||
await GamificationService.award_points(db, user_id=user.id, amount=int(kyc_reward), reason="KYC_VERIFICATION")
|
||||
|
||||
# P2P Referral XP a meghívónak (ha van referred_by_id)
|
||||
if user.referred_by_id:
|
||||
p2p_xp = await config.get_setting(db, "gamification_p2p_invite_xp", default=50)
|
||||
await GamificationService.award_points(
|
||||
db,
|
||||
user_id=user.referred_by_id,
|
||||
amount=int(p2p_xp),
|
||||
reason="P2P_REFERRAL_SUCCESS"
|
||||
)
|
||||
logger.info(f"P2P XP ({p2p_xp}) awarded to referrer ID {user.referred_by_id} for user {user.id}")
|
||||
|
||||
await db.commit()
|
||||
return user
|
||||
except Exception as e:
|
||||
@@ -224,7 +326,19 @@ class AuthService:
|
||||
if not token: return False
|
||||
|
||||
token.is_used = True
|
||||
# Itt aktiválhatnánk a júzert, ha a Lite regnél még nem tennénk meg
|
||||
|
||||
# Activate user
|
||||
user_stmt = select(User).where(User.id == token.user_id)
|
||||
user = (await db.execute(user_stmt)).scalar_one_or_none()
|
||||
if user:
|
||||
user.is_active = True
|
||||
|
||||
# Activate person
|
||||
person_stmt = select(Person).where(Person.user_id == user.id)
|
||||
person = (await db.execute(person_stmt)).scalar_one_or_none()
|
||||
if person:
|
||||
person.is_active = True
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
except: return False
|
||||
@@ -268,13 +382,18 @@ class AuthService:
|
||||
token_rec = (await db.execute(stmt)).scalar_one_or_none()
|
||||
if not token_rec: return False
|
||||
|
||||
# Jelszó komplexitás ellenőrzése
|
||||
user_stmt = select(User).where(User.id == token_rec.user_id)
|
||||
user = (await db.execute(user_stmt)).scalar_one()
|
||||
await AuthService._validate_password_complexity(db, new_password, user.region_code)
|
||||
|
||||
user.hashed_password = get_password_hash(new_password)
|
||||
token_rec.is_used = True
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
except HTTPException:
|
||||
raise
|
||||
except: return False
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user