Files
space-booking/backend/app/api/auth.py
Claude Agent df4031d99c feat: Space Booking System - MVP complet
Sistem web pentru rezervarea de birouri și săli de ședință
cu flux de aprobare administrativă.

Stack: FastAPI + Vue.js 3 + SQLite + TypeScript

Features implementate:
- Autentificare JWT + Self-registration cu email verification
- CRUD Spații, Utilizatori, Settings (Admin)
- Calendar interactiv (FullCalendar) cu drag-and-drop
- Creare rezervări cu validare (durată, program, overlap, max/zi)
- Rezervări recurente (săptămânal)
- Admin: aprobare/respingere/anulare cereri
- Admin: creare directă rezervări (bypass approval)
- Admin: editare orice rezervare
- User: editare/anulare rezervări proprii
- Notificări in-app (bell icon + dropdown)
- Notificări email (async SMTP cu BackgroundTasks)
- Jurnal acțiuni administrative (audit log)
- Rapoarte avansate (utilizare, top users, approval rate)
- Șabloane rezervări (booking templates)
- Atașamente fișiere (upload/download)
- Conflict warnings (verificare disponibilitate real-time)
- Integrare Google Calendar (OAuth2)
- Suport timezone (UTC storage + user preference)
- 225+ teste backend

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:51:29 +00:00

218 lines
6.2 KiB
Python

"""Authentication endpoints."""
from datetime import datetime, timedelta
from typing import Annotated
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
from jose import JWTError, jwt
from sqlalchemy.orm import Session
from app.core.config import settings
from app.core.deps import get_db
from app.core.security import create_access_token, get_password_hash, verify_password
from app.models.user import User
from app.schemas.auth import EmailVerificationRequest, LoginRequest, UserRegister
from app.schemas.user import Token
from app.services.email_service import send_email
router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/login", response_model=Token)
def login(
login_data: LoginRequest,
db: Annotated[Session, Depends(get_db)],
) -> Token:
"""
Login with email and password.
Returns JWT token for authenticated requests.
"""
user = db.query(User).filter(User.email == login_data.email).first()
if not user or not verify_password(login_data.password, str(user.hashed_password)):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Bearer"},
)
if not user.is_active:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User account is disabled",
)
access_token = create_access_token(subject=int(user.id))
return Token(access_token=access_token, token_type="bearer")
@router.post("/register", status_code=201)
async def register(
data: UserRegister,
background_tasks: BackgroundTasks,
db: Annotated[Session, Depends(get_db)],
) -> dict:
"""
Register new user with email verification.
Creates an inactive user account and sends verification email.
"""
# Check if email already exists
existing = db.query(User).filter(User.email == data.email).first()
if existing:
raise HTTPException(status_code=400, detail="Email already registered")
# Create user (inactive until verified)
user = User(
email=data.email,
hashed_password=get_password_hash(data.password),
full_name=data.full_name,
organization=data.organization,
role="user", # Default role
is_active=False, # Inactive until email verified
)
db.add(user)
db.commit()
db.refresh(user)
# Generate verification token (JWT, expires in 24h)
verification_token = jwt.encode(
{
"sub": str(user.id),
"type": "email_verification",
"exp": datetime.utcnow() + timedelta(hours=24),
},
settings.secret_key,
algorithm="HS256",
)
# Send verification email (background task)
verification_link = f"{settings.frontend_url}/verify?token={verification_token}"
subject = "Verifică contul tău - Space Booking"
body = f"""Bună ziua {user.full_name},
Bine ai venit pe platforma Space Booking!
Pentru a-ți activa contul, te rugăm să accesezi link-ul de mai jos:
{verification_link}
Link-ul va expira în 24 de ore.
Dacă nu ai creat acest cont, te rugăm să ignori acest email.
Cu stimă,
Echipa Space Booking
"""
background_tasks.add_task(send_email, user.email, subject, body)
return {
"message": "Registration successful. Please check your email to verify your account.",
"email": user.email,
}
@router.post("/verify")
def verify_email(
data: EmailVerificationRequest,
db: Annotated[Session, Depends(get_db)],
) -> dict:
"""
Verify email address with token.
Activates the user account if token is valid.
"""
try:
# Decode token
payload = jwt.decode(
data.token,
settings.secret_key,
algorithms=["HS256"],
)
# Check token type
if payload.get("type") != "email_verification":
raise HTTPException(status_code=400, detail="Invalid verification token")
user_id = int(payload.get("sub"))
# Get user
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
# Check if already verified
if user.is_active:
return {"message": "Email already verified"}
# Activate user
user.is_active = True
db.commit()
return {"message": "Email verified successfully. You can now log in."}
except jwt.ExpiredSignatureError:
raise HTTPException(
status_code=400,
detail="Verification link expired. Please request a new one.",
)
except JWTError:
raise HTTPException(status_code=400, detail="Invalid verification token")
@router.post("/resend-verification")
async def resend_verification(
email: str,
background_tasks: BackgroundTasks,
db: Annotated[Session, Depends(get_db)],
) -> dict:
"""
Resend verification email.
For security, always returns success even if email doesn't exist.
"""
user = db.query(User).filter(User.email == email).first()
if not user:
# Don't reveal if email exists
return {"message": "If the email exists, a verification link has been sent."}
if user.is_active:
raise HTTPException(status_code=400, detail="Account already verified")
# Generate new token
verification_token = jwt.encode(
{
"sub": str(user.id),
"type": "email_verification",
"exp": datetime.utcnow() + timedelta(hours=24),
},
settings.secret_key,
algorithm="HS256",
)
# Send email
verification_link = f"{settings.frontend_url}/verify?token={verification_token}"
subject = "Verifică contul tău - Space Booking"
body = f"""Bună ziua {user.full_name},
Ai solicitat un nou link de verificare pentru contul tău pe Space Booking.
Pentru a-ți activa contul, te rugăm să accesezi link-ul de mai jos:
{verification_link}
Link-ul va expira în 24 de ore.
Cu stimă,
Echipa Space Booking
"""
background_tasks.add_task(send_email, user.email, subject, body)
return {"message": "If the email exists, a verification link has been sent."}