Initial commit: Parking Manager
Features: - Manager-centric parking spot management - Fair assignment algorithm (parking/presence ratio) - Presence tracking calendar - Closing days (specific & weekly recurring) - Guarantees and exclusions - Authelia/LLDAP integration for SSO Stack: - FastAPI backend - SQLite database - Vanilla JS frontend - Docker deployment
This commit is contained in:
137
app/routes/auth.py
Normal file
137
app/routes/auth.py
Normal file
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
Authentication Routes
|
||||
Login, register, logout, and user info
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Response
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from database.connection import get_db
|
||||
from services.auth import (
|
||||
create_user, authenticate_user, create_access_token,
|
||||
get_user_by_email, hash_password, verify_password
|
||||
)
|
||||
from utils.auth_middleware import get_current_user
|
||||
from app import config
|
||||
import re
|
||||
|
||||
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
||||
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
name: str
|
||||
office_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
|
||||
office_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)
|
||||
def register(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"
|
||||
)
|
||||
|
||||
if len(data.password) < 8:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Password must be at least 8 characters"
|
||||
)
|
||||
|
||||
user = create_user(
|
||||
db=db,
|
||||
email=data.email,
|
||||
password=data.password,
|
||||
name=data.name,
|
||||
office_id=data.office_id
|
||||
)
|
||||
|
||||
token = create_access_token(user.id, user.email)
|
||||
return TokenResponse(access_token=token)
|
||||
|
||||
|
||||
@router.post("/login", response_model=TokenResponse)
|
||||
def login(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:
|
||||
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"
|
||||
)
|
||||
|
||||
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,
|
||||
office_id=user.office_id,
|
||||
role=user.role,
|
||||
manager_parking_quota=user.manager_parking_quota,
|
||||
week_start_day=user.week_start_day or 0,
|
||||
notify_weekly_parking=user.notify_weekly_parking if user.notify_weekly_parking is not None else 1,
|
||||
notify_daily_parking=user.notify_daily_parking if user.notify_daily_parking is not None else 1,
|
||||
notify_daily_parking_hour=user.notify_daily_parking_hour if user.notify_daily_parking_hour is not None else 8,
|
||||
notify_daily_parking_minute=user.notify_daily_parking_minute if user.notify_daily_parking_minute is not None else 0,
|
||||
notify_parking_changes=user.notify_parking_changes if user.notify_parking_changes is not None else 1
|
||||
)
|
||||
|
||||
|
||||
@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)
|
||||
Reference in New Issue
Block a user