from datetime import datetime, timedelta, timezone import uuid from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, text from app.models.identity import User, Person, UserRole, VerificationToken from app.models.organization import Organization from app.schemas.auth import UserLiteRegister from app.core.security import get_password_hash, verify_password from app.services.email_manager import email_manager from app.core.config import settings class AuthService: @staticmethod async def register_lite(db: AsyncSession, user_in: UserLiteRegister): """Step 1: Lite regisztráció kormányozható token élettartammal.""" try: # 1. Person shell new_person = Person( first_name=user_in.first_name, last_name=user_in.last_name, is_active=False ) db.add(new_person) await db.flush() # 2. User fiók new_user = User( email=user_in.email, hashed_password=get_password_hash(user_in.password), person_id=new_person.id, role=UserRole.user, is_active=False, region_code=user_in.region_code ) db.add(new_user) await db.flush() # 3. Biztonsági Token (Beállítható élettartam) # Default: 48 óra, ha nincs megadva a settingsben expire_hours = getattr(settings, "REGISTRATION_TOKEN_EXPIRE_HOURS", 48) token_val = uuid.uuid4() new_token = VerificationToken( token=token_val, user_id=new_user.id, token_type="registration", expires_at=datetime.now(timezone.utc) + timedelta(hours=expire_hours) ) db.add(new_token) await db.flush() # 4. Email küldés verification_link = f"{settings.FRONTEND_BASE_URL}/verify?token={token_val}" try: await email_manager.send_email( recipient=user_in.email, template_key="registration", variables={ "first_name": user_in.first_name, "link": verification_link } ) except Exception as email_err: print(f"CRITICAL: Email sending failed: {str(email_err)}") await db.commit() await db.refresh(new_user) return new_user except Exception as e: await db.rollback() raise e @staticmethod async def verify_email(db: AsyncSession, token_str: str): """Token ellenőrzése és regisztráció megerősítése.""" try: # Token UUID-vá alakítása az összehasonlításhoz token_uuid = uuid.UUID(token_str) stmt = select(VerificationToken).where( VerificationToken.token == token_uuid, VerificationToken.is_used == False, VerificationToken.expires_at > datetime.now(timezone.utc) ) result = await db.execute(stmt) token_obj = result.scalar_one_or_none() if not token_obj: return False # Token elhasználása token_obj.is_used = True # User keresése és aktiválása (Email megerősítve) user_stmt = select(User).where(User.id == token_obj.user_id) user_res = await db.execute(user_stmt) user = user_res.scalar_one_or_none() if user: # Figyelem: A Master Book szerint ez még nem teljes aktiválás (is_active: false) # de jelölhetjük, hogy az e-mail már OK. pass await db.commit() return True except Exception as e: print(f"Verify error: {e}") await db.rollback() return False @staticmethod async def authenticate(db: AsyncSession, email: str, password: str): stmt = select(User).where(User.email == email, User.is_deleted == False) res = await db.execute(stmt) user = res.scalar_one_or_none() if not user or not user.hashed_password or not verify_password(password, user.hashed_password): return None return user @staticmethod async def initiate_password_reset(db: AsyncSession, email: str): """Jelszó-emlékeztető kormányozható élettartammal.""" stmt = select(User).where(User.email == email, User.is_deleted == False) res = await db.execute(stmt) user = res.scalar_one_or_none() if user: expire_hours = getattr(settings, "PASSWORD_RESET_TOKEN_EXPIRE_HOURS", 1) token_val = uuid.uuid4() new_token = VerificationToken( token=token_val, user_id=user.id, token_type="password_reset", expires_at=datetime.now(timezone.utc) + timedelta(hours=expire_hours) ) db.add(new_token) reset_link = f"{settings.FRONTEND_BASE_URL}/reset-password?token={token_val}" await email_manager.send_email( recipient=email, template_key="password_reset", variables={"link": reset_link}, user_id=user.id ) await db.commit() return True return False