diff --git a/backend/app/api/__pycache__/deps.cpython-312.pyc b/backend/app/api/__pycache__/deps.cpython-312.pyc old mode 100755 new mode 100644 index 9be3ecb..d194b41 Binary files a/backend/app/api/__pycache__/deps.cpython-312.pyc and b/backend/app/api/__pycache__/deps.cpython-312.pyc differ diff --git a/backend/app/api/deps.py b/backend/app/api/deps.py index 3eed9e3..9986682 100755 --- a/backend/app/api/deps.py +++ b/backend/app/api/deps.py @@ -1,39 +1,51 @@ -from typing import Generator +from typing import AsyncGenerator from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer -from jose import JWTError from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select -from app.db.session import SessionLocal +from app.db.session import get_db from app.core.security import decode_token -from app.models.user import User +from app.models.identity import User # Javítva identity-re -reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v2/auth/login") - -async def get_db() -> Generator: - async with SessionLocal() as session: - yield session +# Javítva v1-re +reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login") async def get_current_user( db: AsyncSession = Depends(get_db), token: str = Depends(reusable_oauth2), ) -> User: - try: - payload = decode_token(token) - user_id = payload.get("sub") - if not user_id: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token error") - except JWTError: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") + payload = decode_token(token) + if not payload: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Érvénytelen vagy lejárt token." + ) + + user_id = payload.get("sub") + if not user_id: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Token azonosítási hiba." + ) + # Felhasználó keresése az adatbázisban res = await db.execute(select(User).where(User.id == int(user_id))) - user = res.scalars().first() + user = res.scalar_one_or_none() if not user: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Felhasználó nem található." + ) - if not user.is_active: - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Fiók nem aktív.") + if user.is_deleted: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Ez a fiók törölve lett." + ) - return user + # FONTOS: Itt NEM dobunk hibát, ha user.is_active == False, + # mert a Step 2 (KYC) kitöltéséhez be kell tudnia lépni inaktívként is! + + return user \ No newline at end of file diff --git a/backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc b/backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc index 3c2ff8b..28947ec 100644 Binary files a/backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc and b/backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc differ diff --git a/backend/app/api/v1/endpoints/auth.py b/backend/app/api/v1/endpoints/auth.py index c8e27c9..5ffbfc4 100644 --- a/backend/app/api/v1/endpoints/auth.py +++ b/backend/app/api/v1/endpoints/auth.py @@ -2,16 +2,19 @@ from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import text + from app.db.session import get_db from app.services.auth_service import AuthService from app.core.security import create_access_token -from app.schemas.auth import UserLiteRegister, Token, PasswordResetRequest +from app.schemas.auth import UserLiteRegister, Token, PasswordResetRequest, UserKYCComplete +from app.api.deps import get_current_user # Ez kezeli a belépett felhasználót +from app.models.identity import User router = APIRouter() @router.post("/register-lite", response_model=Token, status_code=201) async def register_lite(user_in: UserLiteRegister, db: AsyncSession = Depends(get_db)): - # Email csekkolás nyers SQL-el + """Step 1: Alapszintű regisztráció és aktiváló e-mail küldése.""" check = await db.execute(text("SELECT id FROM data.users WHERE email = :e"), {"e": user_in.email}) if check.fetchone(): raise HTTPException(status_code=400, detail="Ez az email cím már foglalt.") @@ -25,6 +28,7 @@ async def register_lite(user_in: UserLiteRegister, db: AsyncSession = Depends(ge @router.post("/login", response_model=Token) async def login(form_data: OAuth2PasswordRequestForm = Depends(), db: AsyncSession = Depends(get_db)): + """Bejelentkezés az access_token megszerzéséhez.""" user = await AuthService.authenticate(db, form_data.username, form_data.password) if not user: raise HTTPException(status_code=401, detail="Hibás e-mail vagy jelszó.") @@ -32,15 +36,28 @@ async def login(form_data: OAuth2PasswordRequestForm = Depends(), db: AsyncSessi token = create_access_token(data={"sub": str(user.id)}) return {"access_token": token, "token_type": "bearer", "is_active": user.is_active} -@router.post("/forgot-password") -async def forgot_password(req: PasswordResetRequest, db: AsyncSession = Depends(get_db)): - await AuthService.initiate_password_reset(db, req.email) - return {"message": "Helyreállítási folyamat elindítva."} - @router.get("/verify-email") async def verify_email(token: str, db: AsyncSession = Depends(get_db)): - """Ezt hívja meg a frontend, amikor a user a levélben a gombra kattint.""" + """E-mail megerősítése a kiküldött token alapján.""" success = await AuthService.verify_email(db, token) if not success: raise HTTPException(status_code=400, detail="Érvénytelen vagy lejárt token.") - return {"message": "Email sikeresen megerősítve! Most már elvégezheti a KYC regisztrációt (Step 2)."} \ No newline at end of file + return {"message": "Email sikeresen megerősítve! Jöhet a Step 2 (KYC)."} + +@router.post("/complete-kyc") +async def complete_kyc( + kyc_in: UserKYCComplete, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(get_current_user) +): + """Step 2: Okmányok rögzítése, Privát Széf és Wallet aktiválása.""" + user = await AuthService.complete_kyc(db, current_user.id, kyc_in) + if not user: + raise HTTPException(status_code=404, detail="Felhasználó nem található.") + return {"status": "success", "message": "Gratulálunk! A Privát Széf és a Pénztárca aktiválva lett."} + +@router.post("/forgot-password") +async def forgot_password(req: PasswordResetRequest, db: AsyncSession = Depends(get_db)): + """Jelszó-visszaállító link küldése.""" + await AuthService.initiate_password_reset(db, req.email) + return {"message": "Ha a cím létezik, elküldtük a helyreállítási linket."} \ No newline at end of file diff --git a/backend/app/core/__pycache__/security.cpython-312.pyc b/backend/app/core/__pycache__/security.cpython-312.pyc index ba97cab..0708508 100644 Binary files a/backend/app/core/__pycache__/security.cpython-312.pyc and b/backend/app/core/__pycache__/security.cpython-312.pyc differ diff --git a/backend/app/core/security.py b/backend/app/core/security.py index a98c2e2..cf1fe91 100644 --- a/backend/app/core/security.py +++ b/backend/app/core/security.py @@ -1,8 +1,7 @@ -# /opt/docker/dev/service_finder/backend/app/core/security.py from datetime import datetime, timedelta, timezone from typing import Optional, Dict, Any import bcrypt -from jose import jwt +from jose import jwt, JWTError from app.core.config import settings def verify_password(plain_password: str, hashed_password: str) -> bool: @@ -17,4 +16,12 @@ def create_access_token(data: Dict[str, Any], expires_delta: Optional[timedelta] to_encode = data.copy() expire = datetime.now(timezone.utc) + (expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)) to_encode.update({"exp": expire}) - return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) \ No newline at end of file + return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) + +def decode_token(token: str) -> Optional[Dict[str, Any]]: + """JWT token visszafejtése és ellenőrzése.""" + try: + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) + return payload + except JWTError: + return None \ No newline at end of file diff --git a/backend/app/models/__pycache__/user.cpython-312.pyc b/backend/app/models/__pycache__/user.cpython-312.pyc old mode 100755 new mode 100644 index c715640..8d3f67f Binary files a/backend/app/models/__pycache__/user.cpython-312.pyc and b/backend/app/models/__pycache__/user.cpython-312.pyc differ diff --git a/backend/app/services/__pycache__/auth_service.cpython-312.pyc b/backend/app/services/__pycache__/auth_service.cpython-312.pyc index 36c5e0d..fb32aea 100644 Binary files a/backend/app/services/__pycache__/auth_service.cpython-312.pyc and b/backend/app/services/__pycache__/auth_service.cpython-312.pyc differ diff --git a/backend/app/services/auth_service.py b/backend/app/services/auth_service.py index 7805f84..7694946 100644 --- a/backend/app/services/auth_service.py +++ b/backend/app/services/auth_service.py @@ -1,13 +1,15 @@ from datetime import datetime, timedelta, timezone import uuid from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select, text +from sqlalchemy import select, text, cast, String from app.models.identity import User, Person, UserRole, VerificationToken, Wallet from app.models.organization import Organization from app.schemas.auth import UserLiteRegister, UserKYCComplete from app.core.security import get_password_hash, verify_password from app.services.email_manager import email_manager from app.core.config import settings +from sqlalchemy.orm import joinedload # <--- EZT ADD HOZZÁ AZ IMPORTOKHOZ! + class AuthService: @staticmethod @@ -94,47 +96,64 @@ class AuthService: @staticmethod async def complete_kyc(db: AsyncSession, user_id: int, kyc_in: UserKYCComplete): - """Step 2: KYC adatok, Telefon, Privát Flotta és Wallet aktiválása.""" + """Step 2: KYC adatok rögzítése JSON-biztos dátumkezeléssel.""" try: - # 1. User és Person lekérése - stmt = select(User).where(User.id == user_id).join(User.person) + # 1. User és Person lekérése joinedload-dal (a korábbi hiba javítása) + stmt = ( + select(User) + .options(joinedload(User.person)) + .where(User.id == user_id) + ) result = await db.execute(stmt) user = result.scalar_one_or_none() - if not user: + if not user or not user.person: return None - # 2. Személyes adatok rögzítése (tábla szinten) + # 2. Előkészítjük a JSON-kompatibilis adatokat + # A mode='json' átalakítja a date objektumokat string-gé! + kyc_data_json = kyc_in.model_dump(mode='json') + p = user.person p.phone = kyc_in.phone_number p.birth_place = kyc_in.birth_place + # A sima DATE oszlopba mehet a Python date objektum p.birth_date = datetime.combine(kyc_in.birth_date, datetime.min.time()) p.mothers_name = kyc_in.mothers_name - # JSONB mezők mentése Pydantic modellekből - p.identity_docs = {k: v.dict() for k, v in kyc_in.identity_docs.items()} - p.ice_contact = kyc_in.ice_contact.dict() + # A JSONB mezőkbe a már stringesített adatokat tesszük + p.identity_docs = kyc_data_json["identity_docs"] + p.ice_contact = kyc_data_json["ice_contact"] p.is_active = True - # 3. PRIVÁT FLOTTA (Organization) automata generálása - new_org = Organization( - name=f"{p.last_name} {p.first_name} - Privát Flotta", - owner_id=user.id, - is_active=True, - org_type="individual" + # 3. PRIVÁT FLOTTA (Organization) + # Megnézzük, létezik-e már (idempotencia) + org_stmt = select(Organization).where( + Organization.owner_id == user.id, + cast(Organization.org_type, String) == "individual" ) - db.add(new_org) - await db.flush() + org_res = await db.execute(org_stmt) + existing_org = org_res.scalar_one_or_none() + + if not existing_org: + new_org = Organization( + name=f"{p.last_name} {p.first_name} - Privát Flotta", + owner_id=user.id, + is_active=True, + org_type="individual", + is_verified=True, + is_transferable=True + ) + db.add(new_org) - # 4. WALLET automata generálása - new_wallet = Wallet( - user_id=user.id, - coin_balance=0.00, - xp_balance=0 - ) - db.add(new_wallet) + # 4. WALLET + wallet_stmt = select(Wallet).where(Wallet.user_id == user.id) + wallet_res = await db.execute(wallet_stmt) + if not wallet_res.scalar_one_or_none(): + new_wallet = Wallet(user_id=user.id, coin_balance=0.0, xp_balance=0) + db.add(new_wallet) - # 5. USER TELJES AKTIVÁLÁSA + # 5. USER AKTIVÁLÁSA user.is_active = True await db.commit() @@ -142,6 +161,7 @@ class AuthService: return user except Exception as e: await db.rollback() + print(f"CRITICAL KYC ERROR: {str(e)}") raise e @staticmethod