2026-06-09 23:32:41 +00:00
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
2026-06-10 21:35:12 +00:00
|
|
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
2026-06-09 23:32:41 +00:00
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
|
|
|
|
from app.database import get_db
|
|
|
|
|
from app.models.user import User
|
2026-06-10 21:35:12 +00:00
|
|
|
from app.schemas.auth import LoginRequest, Token
|
|
|
|
|
from app.schemas.user import UserCreate, UserRead
|
|
|
|
|
from app.utils.security import (
|
|
|
|
|
verify_password,
|
|
|
|
|
get_password_hash,
|
|
|
|
|
create_access_token,
|
|
|
|
|
decode_access_token,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
security = HTTPBearer()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_current_user(
|
|
|
|
|
credentials: HTTPAuthorizationCredentials = Depends(security),
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
) -> User:
|
|
|
|
|
"""Dependency to get the current authenticated user."""
|
|
|
|
|
token = credentials.credentials
|
|
|
|
|
payload = decode_access_token(token)
|
|
|
|
|
if payload is None:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail="Invalid or expired token",
|
|
|
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
|
|
|
)
|
|
|
|
|
user_id = payload.get("sub")
|
|
|
|
|
if user_id is None:
|
2026-06-09 23:32:41 +00:00
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
2026-06-10 21:35:12 +00:00
|
|
|
detail="Invalid token payload",
|
|
|
|
|
headers={"WWW-Authenticate": "Bearer"},
|
2026-06-09 23:32:41 +00:00
|
|
|
)
|
2026-06-10 21:35:12 +00:00
|
|
|
user = db.query(User).filter(User.id == int(user_id)).first()
|
|
|
|
|
if user is None:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail="User not found",
|
|
|
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
|
|
|
)
|
|
|
|
|
return user
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post(
|
|
|
|
|
"/auth/register", response_model=UserRead, status_code=status.HTTP_201_CREATED
|
|
|
|
|
)
|
|
|
|
|
def register(user_data: UserCreate, db: Session = Depends(get_db)):
|
|
|
|
|
"""Register a new user."""
|
|
|
|
|
# Check if email already exists
|
|
|
|
|
existing_user = db.query(User).filter(User.email == user_data.email).first()
|
|
|
|
|
if existing_user:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Create new user
|
|
|
|
|
hashed_password = get_password_hash(user_data.password)
|
|
|
|
|
new_user = User(
|
|
|
|
|
email=user_data.email,
|
|
|
|
|
password_hash=hashed_password,
|
|
|
|
|
name=user_data.name,
|
|
|
|
|
role="member",
|
2026-06-09 23:32:41 +00:00
|
|
|
)
|
2026-06-10 21:35:12 +00:00
|
|
|
db.add(new_user)
|
|
|
|
|
db.commit()
|
|
|
|
|
db.refresh(new_user)
|
|
|
|
|
return new_user
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/auth/login", response_model=Token)
|
|
|
|
|
def login(login_data: LoginRequest, db: Session = Depends(get_db)):
|
|
|
|
|
"""Login and return JWT token."""
|
|
|
|
|
user = db.query(User).filter(User.email == login_data.email).first()
|
|
|
|
|
if not user or not verify_password(login_data.password, user.password_hash):
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or password"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
access_token = create_access_token(data={"sub": str(user.id), "email": user.email})
|
|
|
|
|
return Token(access_token=access_token)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/auth/logout")
|
|
|
|
|
def logout(current_user: User = Depends(get_current_user)):
|
|
|
|
|
"""Logout (client should discard the token)."""
|
|
|
|
|
return {"message": "Logged out successfully"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/auth/me", response_model=UserRead)
|
|
|
|
|
def get_me(current_user: User = Depends(get_current_user)):
|
|
|
|
|
"""Get current user info."""
|
|
|
|
|
return current_user
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/auth/password")
|
|
|
|
|
def change_password(
|
|
|
|
|
current_password: str,
|
|
|
|
|
new_password: str,
|
|
|
|
|
current_user: User = Depends(get_current_user),
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
):
|
|
|
|
|
"""Change password."""
|
|
|
|
|
if not verify_password(current_password, current_user.password_hash):
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
|
|
|
detail="Current password is incorrect",
|
|
|
|
|
)
|
|
|
|
|
current_user.password_hash = get_password_hash(new_password)
|
|
|
|
|
db.commit()
|
|
|
|
|
return {"message": "Password changed successfully"}
|