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:
Binary file not shown.
Binary file not shown.
@@ -1,11 +1,5 @@
|
||||
from fastapi import APIRouter
|
||||
from app.api.v1.endpoints import auth # Fontos a helyes import!
|
||||
from app.api.v1.endpoints import auth
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
# Minden auth funkciót ide gyűjtünk (Register, Login, Recover)
|
||||
api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"])
|
||||
|
||||
# Itt jönnek majd a további modulok:
|
||||
# api_router.include_router(users.router, prefix="/users", tags=["Users"])
|
||||
# api_router.include_router(fleet.router, prefix="/fleet", tags=["Fleet"])
|
||||
api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"])
|
||||
Binary file not shown.
44
backend/app/api/v1/endpoints/auth.py
Executable file → Normal file
44
backend/app/api/v1/endpoints/auth.py
Executable file → Normal file
@@ -1,34 +1,32 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
# /opt/docker/dev/service_finder/backend/app/api/v1/endpoints/auth.py
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status, Body
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db.session import get_db
|
||||
from app.schemas.auth import UserRegister, UserLogin, Token
|
||||
from app.schemas.auth import UserRegister, Token, UserLogin
|
||||
from app.services.auth_service import AuthService
|
||||
from app.core.security import create_access_token
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/register", status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/register", response_model=Token, status_code=status.HTTP_201_CREATED)
|
||||
async def register(
|
||||
request: Request,
|
||||
user_in: UserRegister,
|
||||
request: Request,
|
||||
user_in: UserRegister = Body(...),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
# 1. Email check
|
||||
is_available = await AuthService.check_email_availability(db, user_in.email)
|
||||
if not is_available:
|
||||
# 1. Foglalt email ellenőrzése
|
||||
if not await AuthService.check_email_availability(db, user_in.email):
|
||||
raise HTTPException(status_code=400, detail="Az e-mail cím már foglalt.")
|
||||
|
||||
# 2. Process
|
||||
try:
|
||||
user = await AuthService.register_new_user(
|
||||
db=db,
|
||||
user_in=user_in,
|
||||
ip_address=request.client.host
|
||||
)
|
||||
return {"status": "success", "message": "Regisztráció sikeres!"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Szerver hiba: {str(e)}")
|
||||
|
||||
@router.post("/login")
|
||||
async def login(user_in: UserLogin, db: AsyncSession = Depends(get_db)):
|
||||
# ... A korábbi login logika itt maradhat ...
|
||||
pass
|
||||
# 2. Atomi regisztráció (Person, User, Wallet, Org, Member, Audit, Email)
|
||||
user = await AuthService.register_new_user(
|
||||
db=db,
|
||||
user_in=user_in,
|
||||
ip_address=request.client.host
|
||||
)
|
||||
|
||||
# 3. Token kiállítása
|
||||
token_data = {"sub": str(user.id), "email": user.email}
|
||||
access_token = create_access_token(data=token_data)
|
||||
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
38
backend/app/api/v1/endpoints/auth_old.py
Executable file
38
backend/app/api/v1/endpoints/auth_old.py
Executable file
@@ -0,0 +1,38 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status, Body
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db.session import get_db
|
||||
from app.schemas.auth import UserRegister, Token, UserLogin
|
||||
from app.services.auth_service import AuthService
|
||||
from app.core.security import create_access_token
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/register", response_model=Token, status_code=status.HTTP_201_CREATED)
|
||||
async def register(
|
||||
request: Request,
|
||||
user_in: UserRegister = Body(...),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Atomi Regisztráció KYC adatokkal és privát flotta létrehozásával."""
|
||||
# 1. Elérhetőség
|
||||
is_available = await AuthService.check_email_availability(db, user_in.email)
|
||||
if not is_available:
|
||||
raise HTTPException(status_code=400, detail="Az e-mail cím már foglalt.")
|
||||
|
||||
# 2. Végrehajtás
|
||||
user = await AuthService.register_new_user(
|
||||
db=db,
|
||||
user_in=user_in,
|
||||
ip_address=request.client.host
|
||||
)
|
||||
|
||||
# 3. Token generálás
|
||||
token_data = {"sub": str(user.id), "email": user.email}
|
||||
access_token = create_access_token(data=token_data)
|
||||
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
@router.post("/login", response_model=Token)
|
||||
async def login(user_in: UserLogin = Body(...), db: AsyncSession = Depends(get_db)):
|
||||
# TODO: Implement login logic
|
||||
raise HTTPException(status_code=501, detail="Login not yet implemented")
|
||||
Binary file not shown.
43
backend/app/core/security.py
Executable file → Normal file
43
backend/app/core/security.py
Executable file → Normal file
@@ -1,49 +1,20 @@
|
||||
# /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, JWTError
|
||||
|
||||
from jose import jwt
|
||||
from app.core.config import settings
|
||||
|
||||
# --- JELSZÓ KEZELÉS ---
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
"""
|
||||
Összehasonlítja a nyers jelszót a hash-elt változattal.
|
||||
"""
|
||||
try:
|
||||
if not hashed_password:
|
||||
return False
|
||||
return bcrypt.checkpw(
|
||||
plain_password.encode("utf-8"),
|
||||
hashed_password.encode("utf-8")
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
if not hashed_password: return False
|
||||
return bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8"))
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
"""
|
||||
Biztonságos hash-t generál a jelszóból.
|
||||
"""
|
||||
salt = bcrypt.gensalt()
|
||||
return bcrypt.hashpw(password.encode("utf-8"), salt).decode("utf-8")
|
||||
|
||||
# --- JWT TOKEN KEZELÉS ---
|
||||
|
||||
def create_access_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
|
||||
"""
|
||||
JWT Access tokent generál a megadott adatokkal és lejárati idővel.
|
||||
"""
|
||||
to_encode = dict(data)
|
||||
expire = datetime.now(timezone.utc) + (
|
||||
expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
)
|
||||
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)
|
||||
|
||||
def decode_token(token: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Dekódolja a JWT tokent.
|
||||
"""
|
||||
return jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
||||
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
||||
24
backend/app/core/security_old.py
Executable file
24
backend/app/core/security_old.py
Executable file
@@ -0,0 +1,24 @@
|
||||
# /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 app.core.config import settings
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
if not hashed_password:
|
||||
return False
|
||||
return bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8"))
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
salt = bcrypt.gensalt()
|
||||
return bcrypt.hashpw(password.encode("utf-8"), salt).decode("utf-8")
|
||||
|
||||
def create_access_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
else:
|
||||
expire = datetime.now(timezone.utc) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
to_encode.update({"exp": expire})
|
||||
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
||||
@@ -1,50 +1,27 @@
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlalchemy import text
|
||||
from app.api.v1.api import api_router
|
||||
from app.db.base import Base
|
||||
from app.db.session import engine
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# Séma és alap táblák ellenőrzése indításkor
|
||||
async with engine.begin() as conn:
|
||||
await conn.execute(text("CREATE SCHEMA IF NOT EXISTS data"))
|
||||
# Base.metadata.create_all helyett javasolt az Alembic,
|
||||
# de fejlesztési fázisban a run_sync biztonságos
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
yield
|
||||
await engine.dispose()
|
||||
from app.core.config import settings
|
||||
|
||||
app = FastAPI(
|
||||
title="Service Finder API",
|
||||
version="1.0.0",
|
||||
docs_url="/docs",
|
||||
version="2.0.0",
|
||||
openapi_url="/api/v1/openapi.json",
|
||||
lifespan=lifespan
|
||||
docs_url="/docs"
|
||||
)
|
||||
|
||||
# BIZTONSÁG: CORS beállítások .env-ből
|
||||
# Ha nincs megadva, csak a localhost-ot engedi
|
||||
origins = os.getenv("CORS_ORIGINS", "http://localhost:3000").split(",")
|
||||
|
||||
# CORS beállítások
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=origins,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# ÚTVONALAK KONSZOLIDÁCIÓJA (V2 törölve, minden a V1 alatt)
|
||||
# Routerek befűzése
|
||||
app.include_router(api_router, prefix="/api/v1")
|
||||
|
||||
@app.get("/", tags=["health"])
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {
|
||||
"status": "online",
|
||||
"version": "1.0.0",
|
||||
"environment": os.getenv("ENV", "production")
|
||||
}
|
||||
return {"status": "online", "message": "Service Finder API v2.0"}
|
||||
@@ -1,25 +1,21 @@
|
||||
from app.db.base import Base
|
||||
from .identity import User, Person, Wallet, UserRole # ÚJ központ
|
||||
from .company import Company, CompanyMember, VehicleAssignment
|
||||
from .identity import User, Person, Wallet, UserRole
|
||||
from .organization import Organization, OrgType
|
||||
from .vehicle import (
|
||||
Vehicle,
|
||||
VehicleOwnership,
|
||||
VehicleBrand,
|
||||
EngineSpec,
|
||||
ServiceProvider,
|
||||
ServiceRecord,
|
||||
VehicleCategory,
|
||||
VehicleModel,
|
||||
VehicleVariant
|
||||
OrganizationMember
|
||||
)
|
||||
|
||||
# Aliasok a kompatibilitás kedvéért
|
||||
UserVehicle = Vehicle
|
||||
# Aliasok a kód többi részének
|
||||
UserVehicle = Vehicle
|
||||
|
||||
__all__ = [
|
||||
"Base", "User", "Person", "Wallet", "UserRole", "Vehicle", "VehicleOwnership",
|
||||
"VehicleBrand", "EngineSpec", "ServiceProvider", "ServiceRecord", "Company",
|
||||
"CompanyMember", "VehicleAssignment", "UserVehicle", "VehicleCategory",
|
||||
"VehicleModel", "VehicleVariant", "Organization", "OrgType"
|
||||
"Base", "User", "Person", "Wallet", "UserRole",
|
||||
"Vehicle", "UserVehicle", "VehicleBrand", "EngineSpec",
|
||||
"ServiceProvider", "ServiceRecord", "Organization",
|
||||
"OrgType", "OrganizationMember"
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/app/models/__pycache__/vehicle.cpython-312.pyc
Executable file → Normal file
BIN
backend/app/models/__pycache__/vehicle.cpython-312.pyc
Executable file → Normal file
Binary file not shown.
@@ -11,6 +11,7 @@ class UserRole(str, enum.Enum):
|
||||
USER = "user"
|
||||
SERVICE = "service"
|
||||
FLEET_MANAGER = "fleet_manager"
|
||||
DRIVER = "driver"
|
||||
|
||||
class Person(Base):
|
||||
__tablename__ = "persons"
|
||||
@@ -25,6 +26,7 @@ class Person(Base):
|
||||
birth_place = Column(String, nullable=True)
|
||||
birth_date = Column(DateTime, nullable=True)
|
||||
|
||||
# KYC Okmányok és Safety adatok
|
||||
identity_docs = Column(JSON, server_default=text("'{}'::jsonb"))
|
||||
medical_emergency = Column(JSON, server_default=text("'{}'::jsonb"))
|
||||
ice_contact = Column(JSON, server_default=text("'{}'::jsonb"))
|
||||
@@ -37,26 +39,26 @@ class User(Base):
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
email = Column(String, unique=True, index=True, nullable=False)
|
||||
hashed_password = Column(String, nullable=False)
|
||||
hashed_password = Column(String, nullable=True) # Social Auth esetén null lehet!
|
||||
|
||||
# Social Auth mezők
|
||||
social_provider = Column(String, nullable=True) # google, facebook
|
||||
social_id = Column(String, nullable=True)
|
||||
|
||||
role = Column(Enum(UserRole), default=UserRole.USER)
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_superuser = Column(Boolean, default=False)
|
||||
is_company = Column(Boolean, default=False)
|
||||
company_name = Column(String, nullable=True)
|
||||
tax_number = Column(String, nullable=True)
|
||||
region_code = Column(String, default="HU")
|
||||
|
||||
# Soft Delete
|
||||
is_deleted = Column(Boolean, default=False)
|
||||
deleted_at = Column(DateTime, nullable=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
person_id = Column(Integer, ForeignKey("data.persons.id"), nullable=True)
|
||||
|
||||
person = relationship("Person", back_populates="users")
|
||||
wallet = relationship("Wallet", back_populates="user", uselist=False)
|
||||
owned_organizations = relationship("Organization", backref="owner")
|
||||
owned_organizations = relationship("Organization", back_populates="owner")
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
class Wallet(Base):
|
||||
__tablename__ = "wallets"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/organization.py
|
||||
import enum
|
||||
from sqlalchemy import Column, Integer, String, Boolean, Enum, DateTime, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
@@ -19,22 +18,22 @@ class Organization(Base):
|
||||
name = Column(String, nullable=False)
|
||||
org_type = Column(Enum(OrgType), default=OrgType.INDIVIDUAL)
|
||||
|
||||
# A flotta technikai tulajdonosa (User)
|
||||
owner_id = Column(Integer, ForeignKey("data.users.id"), nullable=True)
|
||||
|
||||
# MASTER BOOK v1.2 kiegészítések
|
||||
# Üzleti szabályok
|
||||
is_active = Column(Boolean, default=True)
|
||||
|
||||
# Csak cégek (nem INDIVIDUAL) esetén adható el a flotta
|
||||
# Privát flotta (INDIVIDUAL) esetén False, cégeknél True
|
||||
is_transferable = Column(Boolean, default=True)
|
||||
|
||||
# Hitelesítési adatok
|
||||
# Verifikáció
|
||||
is_verified = Column(Boolean, default=False)
|
||||
# Türelmi idő vagy hitelesítés lejárata
|
||||
verification_expires_at = Column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# Kapcsolatok
|
||||
vehicles = relationship("UserVehicle", back_populates="current_org")
|
||||
members = relationship("OrganizationMember", back_populates="organization")
|
||||
vehicles = relationship("Vehicle", back_populates="current_org")
|
||||
members = relationship("OrganizationMember", back_populates="organization")
|
||||
owner = relationship("User", back_populates="owned_organizations")
|
||||
@@ -44,7 +44,7 @@ class Vehicle(Base):
|
||||
__tablename__ = "vehicles"
|
||||
__table_args__ = {"schema": "data"}
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
current_company_id = Column(Integer, ForeignKey("data.companies.id"))
|
||||
current_company_id = Column(Integer, ForeignKey("data.organizations.id"))
|
||||
brand_id = Column(Integer, ForeignKey("data.vehicle_brands.id"))
|
||||
model_name = Column(String(100))
|
||||
engine_spec_id = Column(Integer, ForeignKey("data.engine_specs.id"))
|
||||
@@ -54,14 +54,10 @@ class Vehicle(Base):
|
||||
current_rating_pct = Column(Integer, default=100)
|
||||
total_real_usage = Column(Numeric(15, 2), default=0)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
engine_spec = relationship("EngineSpec", back_populates="vehicles")
|
||||
service_records = relationship("ServiceRecord", back_populates="vehicle", cascade="all, delete-orphan")
|
||||
|
||||
# --- KOMPATIBILITÁSI RÉTEG A RÉGI KÓDOKHOZ ---
|
||||
VehicleOwnership = Vehicle
|
||||
VehicleModel = Vehicle
|
||||
VehicleVariant = Vehicle
|
||||
VehicleCategory = VehicleBrand # JAVÍTVA: Nagy "B" betűvel
|
||||
current_org = relationship("Organization", back_populates="vehicles")
|
||||
|
||||
class ServiceRecord(Base):
|
||||
__tablename__ = "service_records"
|
||||
@@ -74,4 +70,18 @@ class ServiceRecord(Base):
|
||||
repair_quality_pct = Column(Integer, default=100)
|
||||
|
||||
vehicle = relationship("Vehicle", back_populates="service_records")
|
||||
provider = relationship("ServiceProvider", back_populates="records") # JAVÍTVA
|
||||
provider = relationship("ServiceProvider", back_populates="records")
|
||||
|
||||
class OrganizationMember(Base):
|
||||
__tablename__ = "organization_members"
|
||||
__table_args__ = {"schema": "data"}
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
organization_id = Column(Integer, ForeignKey("data.organizations.id"), nullable=False)
|
||||
user_id = Column(Integer, ForeignKey("data.users.id"), nullable=False)
|
||||
role = Column(String, default="driver")
|
||||
|
||||
organization = relationship("Organization", back_populates="members")
|
||||
|
||||
# --- KOMPATIBILITÁSI RÉTEG ---
|
||||
UserVehicle = Vehicle
|
||||
VehicleOwnership = Vehicle
|
||||
Binary file not shown.
44
backend/app/schemas/auth.py
Executable file → Normal file
44
backend/app/schemas/auth.py
Executable file → Normal file
@@ -1,27 +1,37 @@
|
||||
from pydantic import BaseModel, EmailStr, Field, validator
|
||||
from typing import Optional
|
||||
# /opt/docker/dev/service_finder/backend/app/schemas/auth.py
|
||||
from pydantic import BaseModel, EmailStr, Field, field_validator
|
||||
from typing import Optional, List
|
||||
from datetime import date
|
||||
|
||||
class UserRegister(BaseModel):
|
||||
email: EmailStr
|
||||
password: str = Field(..., min_length=8)
|
||||
first_name: str = Field(..., min_length=2)
|
||||
email: EmailStr = Field(..., example="pilot@profibot.hu")
|
||||
password: Optional[str] = Field(None, min_length=8)
|
||||
last_name: str = Field(..., min_length=2)
|
||||
region_code: str = Field(default="HU", min_length=2, max_length=2) # ISO kód: HU, DE, AT stb.
|
||||
device_id: Optional[str] = None # Eszköz azonosító a biztonsághoz
|
||||
first_name: str = Field(..., min_length=2)
|
||||
mothers_name: str = Field(..., description="Kötelező banki azonosító")
|
||||
birth_place: Optional[str] = None
|
||||
birth_date: Optional[date] = None
|
||||
id_card_number: Optional[str] = None
|
||||
id_card_expiry: Optional[date] = None
|
||||
driver_license_number: Optional[str] = None
|
||||
driver_license_expiry: Optional[date] = None
|
||||
driver_license_categories: List[str] = Field(default_factory=list)
|
||||
boat_license_number: Optional[str] = None
|
||||
pilot_license_number: Optional[str] = None
|
||||
region_code: str = Field(default="HU")
|
||||
invite_token: Optional[str] = None
|
||||
social_provider: Optional[str] = None
|
||||
social_id: Optional[str] = None
|
||||
|
||||
@validator('region_code')
|
||||
def validate_region(cls, v):
|
||||
return v.upper() if v else v
|
||||
|
||||
# EZ HIÁNYZOTT: Az azonosításhoz (login) szükséges séma
|
||||
class UserLogin(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
@field_validator('region_code')
|
||||
@classmethod
|
||||
def validate_region(cls, v: str) -> str:
|
||||
return v.upper() if v else "HU"
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
class TokenData(BaseModel):
|
||||
email: Optional[str] = None
|
||||
class UserLogin(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
46
backend/app/schemas/auth_old.py
Executable file
46
backend/app/schemas/auth_old.py
Executable file
@@ -0,0 +1,46 @@
|
||||
from pydantic import BaseModel, EmailStr, Field, field_validator
|
||||
from typing import Optional, List
|
||||
from datetime import date
|
||||
|
||||
class UserRegister(BaseModel):
|
||||
# --- AUTH ---
|
||||
email: EmailStr = Field(..., example="teszt.user@profibot.hu")
|
||||
password: Optional[str] = Field(None, min_length=8, description="Social login esetén üres maradhat")
|
||||
|
||||
# --- IDENTITY (KYC Step 2) ---
|
||||
last_name: str = Field(..., min_length=2)
|
||||
first_name: str = Field(..., min_length=2)
|
||||
mothers_name: str = Field(..., description="Anyja születési neve")
|
||||
birth_place: Optional[str] = None
|
||||
birth_date: Optional[date] = None
|
||||
|
||||
# --- OKMÁNYOK (Banki szint) ---
|
||||
id_card_number: Optional[str] = None
|
||||
id_card_expiry: Optional[date] = None
|
||||
|
||||
driver_license_number: Optional[str] = None
|
||||
driver_license_expiry: Optional[date] = None
|
||||
driver_license_categories: List[str] = Field(default_factory=list, example=["B", "A"])
|
||||
|
||||
# --- SPECIÁLIS ENGEDÉLYEK ---
|
||||
boat_license_number: Optional[str] = None
|
||||
pilot_license_number: Optional[str] = None
|
||||
|
||||
# --- SYSTEM ---
|
||||
region_code: str = Field(default="HU")
|
||||
invite_token: Optional[str] = None
|
||||
social_provider: Optional[str] = None
|
||||
social_id: Optional[str] = None
|
||||
|
||||
@field_validator('region_code')
|
||||
@classmethod
|
||||
def validate_region(cls, v: str) -> str:
|
||||
return v.upper() if v else "HU"
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
class UserLogin(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
Binary file not shown.
@@ -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:
|
||||
|
||||
130
backend/app/services/auth_service_old.py
Normal file
130
backend/app/services/auth_service_old.py
Normal file
@@ -0,0 +1,130 @@
|
||||
from datetime import datetime, timezone, timedelta
|
||||
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, 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.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:
|
||||
"""Kiolvassa a beállítást az adatbázisból (Admin UI kompatibilis)."""
|
||||
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):
|
||||
try:
|
||||
# 1. KYC Adatcsomag összeállítása (JSONB tároláshoz)
|
||||
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
|
||||
new_person = Person(
|
||||
first_name=user_in.first_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()
|
||||
|
||||
# 3. User létrehozása
|
||||
hashed_pwd = get_password_hash(user_in.password) if user_in.password else None
|
||||
new_user = User(
|
||||
email=user_in.email,
|
||||
hashed_password=hashed_pwd,
|
||||
social_provider=user_in.social_provider,
|
||||
social_id=user_in.social_id,
|
||||
person_id=new_person.id,
|
||||
role=UserRole.USER,
|
||||
region_code=user_in.region_code,
|
||||
is_active=True
|
||||
)
|
||||
db.add(new_user)
|
||||
await db.flush()
|
||||
|
||||
# 4. Wallet inicializálás
|
||||
new_wallet = Wallet(user_id=new_user.id, coin_balance=0.00, xp_balance=0)
|
||||
db.add(new_wallet)
|
||||
|
||||
# 5. Privát Flotta (SZABÁLY: Nem átruházható)
|
||||
new_org = Organization(
|
||||
name=f"{user_in.last_name} {user_in.first_name} flottája",
|
||||
org_type=OrgType.INDIVIDUAL,
|
||||
owner_id=new_user.id,
|
||||
is_active=True,
|
||||
is_transferable=False
|
||||
)
|
||||
db.add(new_org)
|
||||
await db.flush()
|
||||
|
||||
# 6. Tagság rögzítése
|
||||
db.add(OrganizationMember(organization_id=new_org.id, user_id=new_user.id, role="owner"))
|
||||
|
||||
# 7. Audit Log
|
||||
audit_stmt = text("""
|
||||
INSERT INTO data.audit_logs (user_id, action, endpoint, method, ip_address, created_at)
|
||||
VALUES (:uid, 'USER_REGISTERED_V1.3_FULL_KYC', '/api/v1/auth/register', 'POST', :ip, :now)
|
||||
""")
|
||||
await db.execute(audit_stmt, {
|
||||
"uid": new_user.id,
|
||||
"ip": ip_address,
|
||||
"now": datetime.now(timezone.utc)
|
||||
})
|
||||
|
||||
# 8. Jutalmazás (Dinamikus)
|
||||
reward_days = await AuthService.get_setting(db, "auth.reward_days", 14)
|
||||
|
||||
# 9. Email küldés (Try-Except, hogy a regisztráció ne akadjon el)
|
||||
try:
|
||||
await email_manager.send_email(
|
||||
recipient=user_in.email,
|
||||
template_key="registration_welcome",
|
||||
variables={"first_name": user_in.first_name, "reward_days": reward_days},
|
||||
user_id=new_user.id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Email delivery failed: {str(e)}")
|
||||
|
||||
await db.commit()
|
||||
return new_user
|
||||
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Critical error in register_new_user: {str(e)}")
|
||||
raise e
|
||||
|
||||
@staticmethod
|
||||
async def check_email_availability(db: AsyncSession, email: str) -> bool:
|
||||
query = select(User).where(and_(User.email == email, User.is_deleted == False))
|
||||
result = await db.execute(query)
|
||||
return result.scalar_one_or_none() is None
|
||||
145
backend/app/services/auth_service_old2.py
Normal file
145
backend/app/services/auth_service_old2.py
Normal file
@@ -0,0 +1,145 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/services/auth_service.py
|
||||
from datetime import datetime, timezone, timedelta
|
||||
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, 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, 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 REGISTRATION FLOW v1.3 (Full Integration)
|
||||
"""
|
||||
try:
|
||||
# 1. KYC ADATOK (Banki szintű nyilvántartás)
|
||||
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 (Digitális Iker alapja)
|
||||
new_person = Person(
|
||||
first_name=user_in.first_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()
|
||||
|
||||
# 3. USER LÉTREHOZÁSA (Hibrid Auth támogatás)
|
||||
hashed_pwd = get_password_hash(user_in.password) if user_in.password else None
|
||||
new_user = User(
|
||||
email=user_in.email,
|
||||
hashed_password=hashed_pwd,
|
||||
social_provider=user_in.social_provider,
|
||||
social_id=user_in.social_id,
|
||||
person_id=new_person.id,
|
||||
role=UserRole.USER,
|
||||
region_code=user_in.region_code,
|
||||
is_active=True
|
||||
)
|
||||
db.add(new_user)
|
||||
await db.flush()
|
||||
|
||||
# 4. ECONOMY: WALLET ÉS REFERRAL SNAPSHOT
|
||||
# Itt olvassuk ki az adminból a jutalék szintet (pl. 10%)
|
||||
l1_commission = await AuthService.get_setting(db, "referral.level1", 10)
|
||||
|
||||
db.add(Wallet(user_id=new_user.id, coin_balance=0.00, xp_balance=0))
|
||||
|
||||
# 5. FLEET: AUTOMATIKUS PRIVÁT FLOTTA (Nem eladható)
|
||||
new_org = Organization(
|
||||
name=f"{user_in.last_name} {user_in.first_name} Private Fleet",
|
||||
org_type=OrgType.INDIVIDUAL,
|
||||
owner_id=new_user.id,
|
||||
is_transferable=False
|
||||
)
|
||||
db.add(new_org)
|
||||
await db.flush()
|
||||
|
||||
# Saját flotta tulajdonjog rögzítése
|
||||
db.add(OrganizationMember(organization_id=new_org.id, user_id=new_user.id, role="owner"))
|
||||
|
||||
# 6. MEGHÍVÓ FELDOLGOZÁSA (Csatlakozás másik céghez)
|
||||
if user_in.invite_token:
|
||||
# Egyszerűsített logika: megnézzük a tokent (példa hívás)
|
||||
# Itt valójában egy 'invitations' táblából kellene lekérni az adatokat
|
||||
# De a logika készen áll a bekötésre:
|
||||
logger.info(f"Processing invite token: {user_in.invite_token}")
|
||||
# db.add(OrganizationMember(organization_id=invited_org_id, user_id=new_user.id, role=invited_role))
|
||||
|
||||
# 7. AUDIT LOG (Minden lépés visszakövethető)
|
||||
audit_stmt = text("""
|
||||
INSERT INTO data.audit_logs (user_id, action, endpoint, method, ip_address, created_at)
|
||||
VALUES (:uid, 'USER_REGISTERED_COMPLETE_V1.3', '/api/v1/auth/register', 'POST', :ip, :now)
|
||||
""")
|
||||
await db.execute(audit_stmt, {
|
||||
"uid": new_user.id, "ip": ip_address, "now": datetime.now(timezone.utc)
|
||||
})
|
||||
|
||||
# 8. JUTALMAZÁS (Admin beállítás alapján)
|
||||
reward_days = await AuthService.get_setting(db, "auth.reward_days", 14)
|
||||
|
||||
# 9. EMAIL KÜLDÉS
|
||||
try:
|
||||
await email_manager.send_email(
|
||||
recipient=user_in.email,
|
||||
template_key="registration_welcome",
|
||||
variables={
|
||||
"first_name": user_in.first_name,
|
||||
"reward_days": reward_days
|
||||
},
|
||||
user_id=new_user.id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Email delivery skipped during reg: {str(e)}")
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(new_user)
|
||||
return new_user
|
||||
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Critical error in register_new_user: {str(e)}")
|
||||
raise e
|
||||
|
||||
@staticmethod
|
||||
async def check_email_availability(db: AsyncSession, email: str) -> bool:
|
||||
query = select(User).where(and_(User.email == email, User.is_deleted == False))
|
||||
result = await db.execute(query)
|
||||
return result.scalar_one_or_none() is None
|
||||
129
backend/app/services/auth_service_old3.py
Normal file
129
backend/app/services/auth_service_old3.py
Normal file
@@ -0,0 +1,129 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/services/auth_service.py
|
||||
from datetime import datetime, timezone
|
||||
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, 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, 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:
|
||||
"""Kiolvassa az Admin felületről állítható változókat."""
|
||||
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 ONBOARDING v1.3 - Atomi folyamat:
|
||||
Person -> User -> Wallet -> Organization -> Membership -> Audit -> Email
|
||||
"""
|
||||
try:
|
||||
# 1. KYC Adatok struktúrálása
|
||||
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 (Identitás) létrehozása
|
||||
new_person = Person(
|
||||
first_name=user_in.first_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()
|
||||
|
||||
# 3. User (Auth) létrehozása
|
||||
hashed_pwd = get_password_hash(user_in.password) if user_in.password else None
|
||||
new_user = User(
|
||||
email=user_in.email,
|
||||
hashed_password=hashed_pwd,
|
||||
social_provider=user_in.social_provider,
|
||||
social_id=user_in.social_id,
|
||||
person_id=new_person.id,
|
||||
role=UserRole.USER,
|
||||
region_code=user_in.region_code,
|
||||
is_active=True
|
||||
)
|
||||
db.add(new_user)
|
||||
await db.flush()
|
||||
|
||||
# 4. Economy: Wallet
|
||||
db.add(Wallet(user_id=new_user.id, coin_balance=0.00, xp_balance=0))
|
||||
|
||||
# 5. Fleet: Automatikus Privát Flotta (SZABÁLY: Nem átruházható)
|
||||
new_org = Organization(
|
||||
name=f"{user_in.last_name} {user_in.first_name} Private Fleet",
|
||||
org_type=OrgType.INDIVIDUAL,
|
||||
owner_id=new_user.id,
|
||||
is_transferable=False
|
||||
)
|
||||
db.add(new_org)
|
||||
await db.flush()
|
||||
|
||||
# 6. Tagság rögzítése (Privát flotta tulajdonos)
|
||||
db.add(OrganizationMember(organization_id=new_org.id, user_id=new_user.id, role="owner"))
|
||||
|
||||
# 7. Meghívó kezelése (Ha másik céghez is csatlakozik)
|
||||
if user_in.invite_token and user_in.invite_token != "string":
|
||||
logger.info(f"Processing invite token: {user_in.invite_token}")
|
||||
# Itt majd az invitation tábla alapján adunk hozzá plusz tagságot
|
||||
|
||||
# 8. Audit Log
|
||||
audit_stmt = text("""
|
||||
INSERT INTO data.audit_logs (user_id, action, endpoint, method, ip_address, created_at)
|
||||
VALUES (:uid, 'REGISTER_V1.3_KYC_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)})
|
||||
|
||||
# 9. Dinamikus jutalom beállítása (Adminból)
|
||||
reward_days = await AuthService.get_setting(db, "auth.reward_days", 14)
|
||||
|
||||
# 10. Email küldés
|
||||
try:
|
||||
await email_manager.send_email(
|
||||
recipient=user_in.email,
|
||||
template_key="registration_welcome",
|
||||
variables={"first_name": user_in.first_name, "reward_days": reward_days},
|
||||
user_id=new_user.id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Email skipped: {str(e)}")
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(new_user)
|
||||
return new_user
|
||||
|
||||
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:
|
||||
query = select(User).where(and_(User.email == email, User.is_deleted == False))
|
||||
result = await db.execute(query)
|
||||
return result.scalar_one_or_none() is None
|
||||
Reference in New Issue
Block a user