feat: complete auth life-cycle with Step 2 KYC and Private Fleet generation

This commit is contained in:
2026-02-06 22:38:44 +00:00
parent cfd1e365e0
commit 9d06be4f87
5 changed files with 108 additions and 30 deletions

View File

@@ -1,7 +1,8 @@
from pydantic import BaseModel, EmailStr, Field from pydantic import BaseModel, EmailStr, Field
from typing import Optional from typing import Optional, Dict
from datetime import date from datetime import date
# --- STEP 1: LITE REGISTRATION ---
class UserLiteRegister(BaseModel): class UserLiteRegister(BaseModel):
email: EmailStr email: EmailStr
password: str = Field(..., min_length=8) password: str = Field(..., min_length=8)
@@ -13,6 +14,26 @@ class UserLogin(BaseModel):
email: EmailStr email: EmailStr
password: str password: str
# --- STEP 2: KYC & ONBOARDING ---
class ICEContact(BaseModel):
name: str
phone: str
relationship: Optional[str] = None
class DocumentDetail(BaseModel):
number: str
expiry_date: date
class UserKYCComplete(BaseModel):
phone_number: str
birth_place: str
birth_date: date
mothers_name: str
# Rugalmas okmánytár, pl: {"id_card": {"number": "123", "expiry_date": "2030-01-01"}}
identity_docs: Dict[str, DocumentDetail]
ice_contact: ICEContact
# --- COMMON & SECURITY ---
class PasswordResetRequest(BaseModel): class PasswordResetRequest(BaseModel):
email: EmailStr email: EmailStr

View File

@@ -2,9 +2,9 @@ from datetime import datetime, timedelta, timezone
import uuid import uuid
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, text from sqlalchemy import select, text
from app.models.identity import User, Person, UserRole, VerificationToken from app.models.identity import User, Person, UserRole, VerificationToken, Wallet
from app.models.organization import Organization from app.models.organization import Organization
from app.schemas.auth import UserLiteRegister from app.schemas.auth import UserLiteRegister, UserKYCComplete
from app.core.security import get_password_hash, verify_password from app.core.security import get_password_hash, verify_password
from app.services.email_manager import email_manager from app.services.email_manager import email_manager
from app.core.config import settings from app.core.config import settings
@@ -12,9 +12,9 @@ from app.core.config import settings
class AuthService: class AuthService:
@staticmethod @staticmethod
async def register_lite(db: AsyncSession, user_in: UserLiteRegister): async def register_lite(db: AsyncSession, user_in: UserLiteRegister):
"""Step 1: Lite regisztráció kormányozható token élettartammal.""" """Step 1: Lite regisztráció + Token generálás + Email."""
try: try:
# 1. Person shell # 1. Person alap létrehozása
new_person = Person( new_person = Person(
first_name=user_in.first_name, first_name=user_in.first_name,
last_name=user_in.last_name, last_name=user_in.last_name,
@@ -23,7 +23,7 @@ class AuthService:
db.add(new_person) db.add(new_person)
await db.flush() await db.flush()
# 2. User fiók # 2. User technikai fiók
new_user = User( new_user = User(
email=user_in.email, email=user_in.email,
hashed_password=get_password_hash(user_in.password), hashed_password=get_password_hash(user_in.password),
@@ -35,10 +35,8 @@ class AuthService:
db.add(new_user) db.add(new_user)
await db.flush() await db.flush()
# 3. Biztonsági Token (Beállítható élettartam) # 3. Kormányozható Token generálása
# Default: 48 óra, ha nincs megadva a settingsben
expire_hours = getattr(settings, "REGISTRATION_TOKEN_EXPIRE_HOURS", 48) expire_hours = getattr(settings, "REGISTRATION_TOKEN_EXPIRE_HOURS", 48)
token_val = uuid.uuid4() token_val = uuid.uuid4()
new_token = VerificationToken( new_token = VerificationToken(
token=token_val, token=token_val,
@@ -49,9 +47,8 @@ class AuthService:
db.add(new_token) db.add(new_token)
await db.flush() await db.flush()
# 4. Email küldés # 4. Email küldés gombbal
verification_link = f"{settings.FRONTEND_BASE_URL}/verify?token={token_val}" verification_link = f"{settings.FRONTEND_BASE_URL}/verify?token={token_val}"
try: try:
await email_manager.send_email( await email_manager.send_email(
recipient=user_in.email, recipient=user_in.email,
@@ -62,7 +59,7 @@ class AuthService:
} }
) )
except Exception as email_err: except Exception as email_err:
print(f"CRITICAL: Email sending failed: {str(email_err)}") print(f"CRITICAL: Email failed: {str(email_err)}")
await db.commit() await db.commit()
await db.refresh(new_user) await db.refresh(new_user)
@@ -73,11 +70,9 @@ class AuthService:
@staticmethod @staticmethod
async def verify_email(db: AsyncSession, token_str: str): async def verify_email(db: AsyncSession, token_str: str):
"""Token ellenőrzése és regisztráció megerősítése.""" """Token ellenőrzése (Email megerősítés)."""
try: try:
# Token UUID-vá alakítása az összehasonlításhoz
token_uuid = uuid.UUID(token_str) token_uuid = uuid.UUID(token_str)
stmt = select(VerificationToken).where( stmt = select(VerificationToken).where(
VerificationToken.token == token_uuid, VerificationToken.token == token_uuid,
VerificationToken.is_used == False, VerificationToken.is_used == False,
@@ -89,18 +84,7 @@ class AuthService:
if not token_obj: if not token_obj:
return False return False
# Token elhasználása
token_obj.is_used = True 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() await db.commit()
return True return True
except Exception as e: except Exception as e:
@@ -108,19 +92,70 @@ class AuthService:
await db.rollback() await db.rollback()
return False return False
@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."""
try:
# 1. User és Person lekérése
stmt = select(User).where(User.id == user_id).join(User.person)
result = await db.execute(stmt)
user = result.scalar_one_or_none()
if not user:
return None
# 2. Személyes adatok rögzítése (tábla szinten)
p = user.person
p.phone = kyc_in.phone_number
p.birth_place = kyc_in.birth_place
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()
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"
)
db.add(new_org)
await db.flush()
# 4. WALLET automata generálása
new_wallet = Wallet(
user_id=user.id,
coin_balance=0.00,
xp_balance=0
)
db.add(new_wallet)
# 5. USER TELJES AKTIVÁLÁSA
user.is_active = True
await db.commit()
await db.refresh(user)
return user
except Exception as e:
await db.rollback()
raise e
@staticmethod @staticmethod
async def authenticate(db: AsyncSession, email: str, password: str): async def authenticate(db: AsyncSession, email: str, password: str):
stmt = select(User).where(User.email == email, User.is_deleted == False) stmt = select(User).where(User.email == email, User.is_deleted == False)
res = await db.execute(stmt) res = await db.execute(stmt)
user = res.scalar_one_or_none() user = res.scalar_one_or_none()
if not user or not user.hashed_password or not verify_password(password, user.hashed_password): if not user or not user.hashed_password or not verify_password(password, user.hashed_password):
return None return None
return user return user
@staticmethod @staticmethod
async def initiate_password_reset(db: AsyncSession, email: str): async def initiate_password_reset(db: AsyncSession, email: str):
"""Jelszó-emlékeztető kormányozható élettartammal.""" """Jelszó-visszaállítás indítása."""
stmt = select(User).where(User.email == email, User.is_deleted == False) stmt = select(User).where(User.email == email, User.is_deleted == False)
res = await db.execute(stmt) res = await db.execute(stmt)
user = res.scalar_one_or_none() user = res.scalar_one_or_none()
@@ -137,7 +172,6 @@ class AuthService:
db.add(new_token) db.add(new_token)
reset_link = f"{settings.FRONTEND_BASE_URL}/reset-password?token={token_val}" reset_link = f"{settings.FRONTEND_BASE_URL}/reset-password?token={token_val}"
await email_manager.send_email( await email_manager.send_email(
recipient=email, recipient=email,
template_key="password_reset", template_key="password_reset",

View File

@@ -78,3 +78,26 @@ Minden regisztrációnál létrejön:
... ...
- **Hibrid Validálás:** Ha a VIES API nem ad eredményt, a rendszer céges dokumentum feltöltését kéri. - **Hibrid Validálás:** Ha a VIES API nem ad eredményt, a rendszer céges dokumentum feltöltését kéri.
- **Ellenőrzés:** Adminisztrátori jóváhagyás vagy AI-alapú dokumentum-validálás után válik `Verified` státuszúvá. - **Ellenőrzés:** Adminisztrátori jóváhagyás vagy AI-alapú dokumentum-validálás után válik `Verified` státuszúvá.
# 🆔 Identitás Validációs és Bizalmi Protokoll
A rendszer a fokozatos adatszolgáltatás és a "Tier-based Access Control" (szintezett hozzáférés) elvét alkalmazza.
## 1. Bizalmi Szintek (Trust Tiers)
| Szint | Megnevezés | Követelmény | Jogosultságok |
| :--- | :--- | :--- | :--- |
| **Tier 0** | Anonymous | Nincs | Csak publikus adatok megtekintése. |
| **Tier 1** | Verified Email | Step 1 sikeres | Belépés, saját profil megtekintése. |
| **Tier 2** | KYC Submitted | Step 2 (Személyi adatok + Telefon) | **Privát Széf/Flotta aktiválása**, Wallet használat. |
| **Tier 3** | AI/OCR Verified | Okmánykép AI általi ellenőrzése | Harmadik fél szolgáltatásainak igénybevétele. |
## 2. Kötelező Adatkör (Step 2 - Tier 2)
A "Privát Széf" aktiválásához az alábbi adatok megadása kötelező:
- **Kapcsolat:** Valós telefonszám (nemzetközi formátum).
- **Személyi:** Születési hely, idő, anyja neve.
- **Okmány:** Típus, sorszám és **lejárati dátum**.
- **Biztonság:** ICE (In Case of Emergency) név és telefonszám.
## 3. Adattárolási Stratégia
- A rugalmas okmányadatokat és vészhelyzeti kapcsolatokat a `persons.identity_docs` és `persons.ice_contact` JSONB mezőkben tároljuk a kereshetőség és bővíthetőség érdekében.