Files
org-parking/app/routes/auth.py
Stefano Manfredi ce9e2fdf2a fix landing page
2025-12-02 23:18:43 +00:00

160 lines
4.9 KiB
Python

"""
Authentication Routes
Login, register, logout, and user info
"""
from fastapi import APIRouter, Depends, HTTPException, status, Response, Request
from pydantic import BaseModel, EmailStr
from sqlalchemy.orm import Session
from slowapi import Limiter
from slowapi.util import get_remote_address
from database.connection import get_db
from services.auth import (
create_user, authenticate_user, create_access_token,
get_user_by_email
)
from utils.auth_middleware import get_current_user
from utils.helpers import validate_password, format_password_errors, get_notification_default
from app import config
router = APIRouter(prefix="/api/auth", tags=["auth"])
limiter = Limiter(key_func=get_remote_address)
class RegisterRequest(BaseModel):
email: EmailStr
password: str
name: str
manager_id: str | None = None
class LoginRequest(BaseModel):
email: EmailStr
password: str
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
class UserResponse(BaseModel):
id: str
email: str
name: str | None
manager_id: str | None
role: str
manager_parking_quota: int | None = None
week_start_day: int = 0
# Notification preferences
notify_weekly_parking: int = 1
notify_daily_parking: int = 1
notify_daily_parking_hour: int = 8
notify_daily_parking_minute: int = 0
notify_parking_changes: int = 1
@router.post("/register", response_model=TokenResponse)
@limiter.limit(f"{config.RATE_LIMIT_REQUESTS}/minute")
def register(request: Request, data: RegisterRequest, db: Session = Depends(get_db)):
"""Register a new user"""
if get_user_by_email(db, data.email):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already registered"
)
# Validate password strength
password_errors = validate_password(data.password)
if password_errors:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=format_password_errors(password_errors)
)
user = create_user(
db=db,
email=data.email,
password=data.password,
name=data.name,
manager_id=data.manager_id
)
config.logger.info(f"New user registered: {data.email}")
token = create_access_token(user.id, user.email)
return TokenResponse(access_token=token)
@router.post("/login", response_model=TokenResponse)
@limiter.limit(f"{config.RATE_LIMIT_REQUESTS}/minute")
def login(request: Request, data: LoginRequest, response: Response, db: Session = Depends(get_db)):
"""Login with email and password"""
user = authenticate_user(db, data.email, data.password)
if not user:
config.logger.warning(f"Failed login attempt for: {data.email}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials"
)
token = create_access_token(user.id, user.email)
response.set_cookie(
key="session_token",
value=token,
httponly=True,
max_age=config.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
samesite="lax"
)
config.logger.info(f"User logged in: {data.email}")
return TokenResponse(access_token=token)
@router.post("/logout")
def logout(response: Response):
"""Logout and clear session"""
response.delete_cookie("session_token")
return {"message": "Logged out"}
@router.get("/me", response_model=UserResponse)
def get_me(user=Depends(get_current_user)):
"""Get current user info"""
return UserResponse(
id=user.id,
email=user.email,
name=user.name,
manager_id=user.manager_id,
role=user.role,
manager_parking_quota=user.manager_parking_quota,
week_start_day=get_notification_default(user.week_start_day, 0),
notify_weekly_parking=get_notification_default(user.notify_weekly_parking, 1),
notify_daily_parking=get_notification_default(user.notify_daily_parking, 1),
notify_daily_parking_hour=get_notification_default(user.notify_daily_parking_hour, 8),
notify_daily_parking_minute=get_notification_default(user.notify_daily_parking_minute, 0),
notify_parking_changes=get_notification_default(user.notify_parking_changes, 1)
)
@router.get("/config")
def get_auth_config():
"""Get authentication configuration for frontend.
Returns info about auth mode and external URLs.
"""
return {
"authelia_enabled": config.AUTHELIA_ENABLED,
"login_url": config.AUTHELIA_LOGIN_URL if config.AUTHELIA_ENABLED else None,
"registration_url": config.REGISTRATION_URL if config.AUTHELIA_ENABLED else None
}
@router.get("/holidays/{year}")
def get_holidays(year: int):
"""Get public holidays for a given year"""
from services.holidays import get_holidays_for_year
if year < 2000 or year > 2100:
raise HTTPException(status_code=400, detail="Year must be between 2000 and 2100")
return get_holidays_for_year(year)