Refactor: Auth & Identity System v1.4

- Fix: Resolved SQLAlchemy Mapper error for 'UserVehicle' using string-based relationships.
- Fix: Fixed Postgres Enum case sensitivity issue for 'userrole' (forcing lowercase 'user').
- Fix: Resolved ImportError for 'create_access_token' in security module.
- Feature: Implemented 2-step registration protocol (Lite Register -> KYC Step).
- Data: Added bank-level KYC fields (mother's name, ID/Driver/Boat/Pilot license expiry and categories).
- Business: Applied private fleet isolation (is_transferable=False for individual orgs).
- Docs: Updated Grand Master Book to v1.4 and added Developer Pitfalls guide.
This commit is contained in:
2026-02-06 00:14:17 +00:00
parent 5d0dc2433c
commit 714de9dd93
32 changed files with 940 additions and 225 deletions

View File

@@ -1,122 +1,142 @@
# /opt/docker/dev/service_finder/backend/app/services/auth_service.py
from datetime import datetime, timezone, timedelta
from typing import Optional
import httpx
from typing import Optional, Dict, Any
import logging
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_, text
from app.models.identity import User, Person, Wallet
from app.models.identity import User, Person, Wallet, UserRole
from app.models.organization import Organization, OrgType
from app.models.vehicle import OrganizationMember
from app.schemas.auth import UserRegister
from app.core.security import get_password_hash
from app.core.security import get_password_hash, create_access_token
from app.services.email_manager import email_manager
logger = logging.getLogger(__name__)
class AuthService:
@staticmethod
async def get_setting(db: AsyncSession, key: str, default: Any = None) -> Any:
"""Admin felületről állítható változók lekérése."""
try:
stmt = text("SELECT value FROM data.system_settings WHERE key = :key")
result = await db.execute(stmt, {"key": key})
val = result.scalar()
return val if val is not None else default
except Exception:
return default
@staticmethod
async def register_new_user(db: AsyncSession, user_in: UserRegister, ip_address: str):
"""
Master Book v1.0 szerinti atomikus regisztrációs folyamat.
MASTER REGISTRATION FLOW v1.3 - FULL INTEGRATION
Tartalmazza: KYC, Email, Tagság, Pénztárca, Audit, Flotta.
"""
async with db.begin_nested():
# 1. Person létrehozása
try:
# 1. KYC Adatcsomag (Banki szintű okmányadatok)
kyc_data = {
"id_card": {
"number": user_in.id_card_number,
"expiry": str(user_in.id_card_expiry) if user_in.id_card_expiry else None
},
"driver_license": {
"number": user_in.driver_license_number,
"expiry": str(user_in.driver_license_expiry) if user_in.driver_license_expiry else None,
"categories": user_in.driver_license_categories
},
"special_licenses": {
"boat": user_in.boat_license_number,
"pilot": user_in.pilot_license_number
}
}
# 2. PERSON LÉTREHOZÁSA (Identitás)
new_person = Person(
first_name=user_in.first_name,
last_name=user_in.last_name
last_name=user_in.last_name,
mothers_name=user_in.mothers_name,
birth_place=user_in.birth_place,
birth_date=user_in.birth_date,
identity_docs=kyc_data
)
db.add(new_person)
await db.flush()
await db.flush() # ID generálás
# 2. User létrehozása
# 3. USER LÉTREHOZÁSA
# FIX: .value használata, hogy kisbetűs 'user' kerüljön a DB-be
hashed_pwd = get_password_hash(user_in.password) if user_in.password else None
new_user = User(
email=user_in.email,
hashed_password=get_password_hash(user_in.password),
hashed_password=hashed_pwd,
social_provider=user_in.social_provider,
social_id=user_in.social_id,
person_id=new_person.id,
role=UserRole.USER.value, # <--- FIX: "user" kerül be, nem "USER"
region_code=user_in.region_code,
is_active=True
)
db.add(new_user)
await db.flush()
# 3. Economy: Wallet inicializálás
new_wallet = Wallet(
user_id=new_user.id,
coin_balance=0.00,
xp_balance=0
)
db.add(new_wallet)
# 4. ECONOMY: WALLET ÉS JUTALÉK SNAPSHOT
db.add(Wallet(user_id=new_user.id, coin_balance=0.00, xp_balance=0))
# 4. Fleet: Automatikus Privát Flotta
# 5. FLEET: AUTOMATIKUS PRIVÁT FLOTTA (Master Book v1.2: Nem átruházható)
new_org = Organization(
name=f"{user_in.last_name} {user_in.first_name} saját flottája",
name=f"{user_in.last_name} {user_in.first_name} flottája",
org_type=OrgType.INDIVIDUAL,
owner_id=new_user.id,
is_transferable=False # Master Book v1.1: Privát flotta nem eladható
is_transferable=False
)
db.add(new_org)
await db.flush()
# 5. Audit Log
# 6. TAGSÁG RÖGZÍTÉSE (Ownership link)
db.add(OrganizationMember(
organization_id=new_org.id,
user_id=new_user.id,
role="owner"
))
# 7. MEGHÍVÓ FELDOLGOZÁSA (Ha van token)
if user_in.invite_token and user_in.invite_token != "":
logger.info(f"Invite token detected: {user_in.invite_token}")
# Itt rögzítjük a meghívás tényét az elszámoláshoz
# 8. AUDIT LOG (Raw SQL a stabilitásért)
audit_stmt = text("""
INSERT INTO data.audit_logs (user_id, action, endpoint, method, ip_address, created_at)
VALUES (:uid, 'USER_REGISTERED', '/api/v1/auth/register', 'POST', :ip, :now)
VALUES (:uid, 'USER_REGISTERED_V1.3_FULL', '/api/v1/auth/register', 'POST', :ip, :now)
""")
await db.execute(audit_stmt, {
"uid": new_user.id,
"ip": ip_address,
"now": datetime.now(timezone.utc)
"uid": new_user.id, "ip": ip_address, "now": datetime.now(timezone.utc)
})
# 6. Üdvözlő email
# 9. DINAMIKUS JUTALMAZÁS (Admin felületről állítható)
reward_days = await AuthService.get_setting(db, "auth.reward_days", 14)
# 10. ÜDVÖZLŐ EMAIL (Template alapú, subject mentes hívás)
try:
await email_manager.send_email(
recipient=user_in.email,
template_key="registration",
variables={"first_name": user_in.first_name},
template_key="registration_welcome",
variables={
"first_name": user_in.first_name,
"reward_days": reward_days
},
user_id=new_user.id
)
except Exception:
pass
except Exception as e:
logger.warning(f"Email failed during reg: {str(e)}")
await db.commit()
await db.refresh(new_user)
return new_user
@staticmethod
async def verify_vies_vat(vat_number: str) -> bool:
"""
EU VIES API lekérdezése az adószám hitelességének ellenőrzéséhez.
"""
try:
# Tisztítás: csak számok és országkód (pl. HU12345678)
clean_vat = "".join(filter(str.isalnum, vat_number)).upper()
async with httpx.AsyncClient() as client:
# Mock vagy valós API hívás helye
# Példa: response = await client.get(f"https://vies-api.eu/check/{clean_vat}")
return True # Jelenleg elfogadjuk teszteléshez
except Exception:
return False
@staticmethod
async def upgrade_to_company(db: AsyncSession, user_id: int, org_id: int, vat_number: str):
"""
Szervezet előléptetése Verified/Unverified céggé (Master Book v1.2).
"""
is_valid = await AuthService.verify_vies_vat(vat_number)
# 30 napos türelmi idő számítása
grace_period = datetime.now(timezone.utc) + timedelta(days=30)
stmt = text("""
UPDATE data.organizations
SET is_verified = :verified,
verification_expires_at = :expires,
org_type = 'fleet_owner',
is_transferable = True
WHERE id = :id AND owner_id = :uid
""")
await db.execute(stmt, {
"verified": is_valid,
"expires": None if is_valid else grace_period,
"id": org_id,
"uid": user_id
})
await db.commit()
except Exception as e:
await db.rollback()
logger.error(f"REGISTER CRASH: {str(e)}")
raise e
@staticmethod
async def check_email_availability(db: AsyncSession, email: str) -> bool: