Files
service-finder/backend/app/api/v1/endpoints/auth.py

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.
Itt 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!"}