176 lines
6.7 KiB
Python
176 lines
6.7 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
|
from fastapi.security import OAuth2PasswordRequestForm
|
|
from fastapi.responses import RedirectResponse
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
from authlib.integrations.starlette_client import OAuth
|
|
|
|
from app.db.session import get_db
|
|
from app.services.auth_service import AuthService
|
|
from app.services.social_auth_service import SocialAuthService
|
|
from app.core.security import create_tokens, DEFAULT_RANK_MAP
|
|
from app.core.config import settings
|
|
from app.schemas.auth import (
|
|
UserLiteRegister, Token, PasswordResetRequest,
|
|
UserKYCComplete, PasswordResetConfirm
|
|
)
|
|
from app.api.deps import get_current_user
|
|
from app.models.identity import User
|
|
|
|
router = APIRouter()
|
|
|
|
# --- GOOGLE OAUTH KONFIGURÁCIÓ ---
|
|
oauth = OAuth()
|
|
oauth.register(
|
|
name='google',
|
|
client_id=settings.GOOGLE_CLIENT_ID,
|
|
client_secret=settings.GOOGLE_CLIENT_SECRET,
|
|
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
|
|
client_kwargs={'scope': 'openid email profile'}
|
|
)
|
|
|
|
# --- SOCIAL AUTH ENDPOINTS ---
|
|
|
|
@router.get("/login/google")
|
|
async def login_google(request: Request):
|
|
"""
|
|
Step 1: Átirányítás a Google bejelentkező oldalára.
|
|
"""
|
|
redirect_uri = settings.GOOGLE_CALLBACK_URL
|
|
return await oauth.google.authorize_redirect(request, redirect_uri)
|
|
|
|
@router.get("/callback/google")
|
|
async def auth_google(request: Request, db: AsyncSession = Depends(get_db)):
|
|
"""
|
|
Step 2: Google visszahívás lekezelése + Dupla Token generálás.
|
|
"""
|
|
try:
|
|
token = await oauth.google.authorize_access_token(request)
|
|
user_info = token.get('userinfo')
|
|
except Exception:
|
|
raise HTTPException(status_code=400, detail="Google hitelesítési hiba.")
|
|
|
|
if not user_info:
|
|
raise HTTPException(status_code=400, detail="Nincs adat a Google-től.")
|
|
|
|
# Step 1: Technikai user létrehozása/keresése (inaktív, nincs mappa)
|
|
user = await SocialAuthService.get_or_create_social_user(
|
|
db, provider="google", social_id=user_info['sub'], email=user_info['email'],
|
|
first_name=user_info.get('given_name'), last_name=user_info.get('family_name')
|
|
)
|
|
|
|
# Dinamikus token generálás
|
|
ranks = await settings.get_db_setting(db, "rbac_rank_matrix", default=DEFAULT_RANK_MAP)
|
|
role_name = user.role.value if hasattr(user.role, 'value') else str(user.role)
|
|
user_rank = ranks.get(role_name, 10)
|
|
|
|
token_data = {
|
|
"sub": str(user.id),
|
|
"role": role_name,
|
|
"rank": user_rank,
|
|
"scope_level": user.scope_level or "individual",
|
|
"scope_id": user.scope_id or str(user.id),
|
|
"region": user.region_code
|
|
}
|
|
|
|
access, refresh = create_tokens(data=token_data)
|
|
|
|
# Visszatérés a frontendre mindkét tokennel
|
|
response_url = f"{settings.FRONTEND_BASE_URL}/auth/callback?access={access}&refresh={refresh}"
|
|
return RedirectResponse(url=response_url)
|
|
|
|
|
|
# --- STANDARD AUTH ENDPOINTS ---
|
|
|
|
@router.post("/register-lite", response_model=Token, status_code=status.HTTP_201_CREATED)
|
|
async def register_lite(user_in: UserLiteRegister, db: AsyncSession = Depends(get_db)):
|
|
"""Step 1: Manuális regisztráció (inaktív, nincs mappa)."""
|
|
stmt = select(User).where(User.email == user_in.email)
|
|
if (await db.execute(stmt)).scalar_one_or_none():
|
|
raise HTTPException(status_code=400, detail="Email már regisztrálva.")
|
|
|
|
user = await AuthService.register_lite(db, user_in)
|
|
|
|
ranks = await settings.get_db_setting(db, "rbac_rank_matrix", default=DEFAULT_RANK_MAP)
|
|
role_name = user.role.value if hasattr(user.role, 'value') else str(user.role)
|
|
|
|
token_data = {
|
|
"sub": str(user.id),
|
|
"role": role_name,
|
|
"rank": ranks.get(role_name, 10),
|
|
"scope_level": "individual",
|
|
"scope_id": str(user.id),
|
|
"region": user.region_code
|
|
}
|
|
|
|
access, refresh = create_tokens(data=token_data)
|
|
return {
|
|
"access_token": access,
|
|
"refresh_token": refresh,
|
|
"token_type": "bearer",
|
|
"is_active": user.is_active
|
|
}
|
|
|
|
@router.post("/login", response_model=Token)
|
|
async def login(db: AsyncSession = Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends()):
|
|
"""Hagyományos belépés + Dupla Token."""
|
|
user = await AuthService.authenticate(db, form_data.username, form_data.password)
|
|
if not user:
|
|
raise HTTPException(status_code=401, detail="Hibás adatok.")
|
|
|
|
ranks = await settings.get_db_setting(db, "rbac_rank_matrix", default=DEFAULT_RANK_MAP)
|
|
role_name = user.role.value if hasattr(user.role, 'value') else str(user.role)
|
|
user_rank = ranks.get(role_name, 10)
|
|
|
|
token_data = {
|
|
"sub": str(user.id),
|
|
"role": role_name,
|
|
"rank": user_rank,
|
|
"scope_level": user.scope_level or "individual",
|
|
"scope_id": user.scope_id or str(user.id),
|
|
"region": user.region_code
|
|
}
|
|
|
|
access, refresh = create_tokens(data=token_data)
|
|
return {
|
|
"access_token": access,
|
|
"refresh_token": refresh,
|
|
"token_type": "bearer",
|
|
"is_active": user.is_active
|
|
}
|
|
|
|
@router.get("/verify-email")
|
|
async def verify_email(token: str, db: AsyncSession = Depends(get_db)):
|
|
if not await AuthService.verify_email(db, token):
|
|
raise HTTPException(status_code=400, detail="Érvénytelen token.")
|
|
return {"message": "Email megerősítve!"}
|
|
|
|
@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: KYC Aktiválás.
|
|
It használjuk a get_current_user-t (nem active), mert a user még inaktív.
|
|
"""
|
|
user = await AuthService.complete_kyc(db, current_user.id, kyc_in)
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="User nem található.")
|
|
return {"status": "success", "message": "Fiók aktiválva."}
|
|
|
|
@router.post("/forgot-password")
|
|
async def forgot_password(req: PasswordResetRequest, db: AsyncSession = Depends(get_db)):
|
|
result = await AuthService.initiate_password_reset(db, req.email)
|
|
if result == "cooldown":
|
|
raise HTTPException(status_code=429, detail="Túl sok kérés.")
|
|
return {"message": "Visszaállító link kiküldve."}
|
|
|
|
@router.post("/reset-password")
|
|
async def reset_password(req: PasswordResetConfirm, db: AsyncSession = Depends(get_db)):
|
|
if req.password != req.password_confirm:
|
|
raise HTTPException(status_code=400, detail="Nem egyeznek a jelszavak.")
|
|
if not await AuthService.reset_password(db, req.email, req.token, req.password):
|
|
raise HTTPException(status_code=400, detail="Sikertelen frissítés.")
|
|
return {"message": "Jelszó frissítve!"} |