mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-09-30 05:34:52 +02:00
213 lines
6.1 KiB
Python
213 lines
6.1 KiB
Python
from typing import Annotated
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlmodel import select
|
|
|
|
from app.api.deps import CurrentSuperuser, CurrentUser
|
|
from app.config import settings
|
|
from app.core.database import get_session
|
|
from app.core.security import (
|
|
create_access_token,
|
|
create_refresh_token,
|
|
decode_token,
|
|
hash_password,
|
|
verify_password,
|
|
)
|
|
from app.models.user import User, UserCreate, UserRead
|
|
from app.schemas.auth import LoginRequest, TokenResponse
|
|
|
|
router = APIRouter(prefix="/auth", tags=["authentication"])
|
|
|
|
|
|
def set_auth_cookies(
|
|
response: Response,
|
|
access_token: str,
|
|
refresh_token: str,
|
|
) -> None:
|
|
response.set_cookie(
|
|
key="access_token",
|
|
value=access_token,
|
|
httponly=True,
|
|
secure=not settings.DEBUG,
|
|
samesite="lax",
|
|
max_age=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
|
|
path="/",
|
|
)
|
|
|
|
response.set_cookie(
|
|
key="refresh_token",
|
|
value=refresh_token,
|
|
httponly=True,
|
|
secure=not settings.DEBUG,
|
|
samesite="lax",
|
|
max_age=settings.REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60,
|
|
path="/",
|
|
)
|
|
|
|
|
|
def clear_auth_cookies(response: Response) -> None:
|
|
response.delete_cookie(key="access_token", path="/")
|
|
response.delete_cookie(key="refresh_token", path="/")
|
|
|
|
|
|
@router.post("/login", response_model=TokenResponse)
|
|
async def login(
|
|
login_data: LoginRequest,
|
|
response: Response,
|
|
session: Annotated[AsyncSession, Depends(get_session)],
|
|
):
|
|
result = await session.execute(
|
|
select(User).where(User.username == login_data.username)
|
|
)
|
|
user = result.scalar_one_or_none()
|
|
|
|
if not user or not verify_password(login_data.password, user.hashed_password):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Incorrect username or password",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
if not user.is_active:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="User account is inactive",
|
|
)
|
|
|
|
access_token = create_access_token(str(user.id))
|
|
refresh_token = create_refresh_token(str(user.id))
|
|
|
|
set_auth_cookies(response, access_token, refresh_token)
|
|
|
|
return TokenResponse(
|
|
access_token=access_token,
|
|
refresh_token=refresh_token,
|
|
)
|
|
|
|
|
|
@router.post("/refresh", response_model=TokenResponse)
|
|
async def refresh_access_token(
|
|
request: Request,
|
|
response: Response,
|
|
session: Annotated[AsyncSession, Depends(get_session)],
|
|
):
|
|
refresh_token = request.cookies.get("refresh_token")
|
|
if not refresh_token:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Refresh token not found",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
payload = decode_token(refresh_token)
|
|
if payload is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid or expired refresh token",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
if payload.get("type") != "refresh":
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid token type",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
user_id_str = payload.get("sub")
|
|
if not user_id_str:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid token payload",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
try:
|
|
user_id = UUID(user_id_str)
|
|
except ValueError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid user ID in token",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
) from e
|
|
|
|
result = await session.execute(select(User).where(User.id == user_id))
|
|
user = result.scalar_one_or_none()
|
|
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="User not found",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
if not user.is_active:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="User account is inactive",
|
|
)
|
|
|
|
new_access_token = create_access_token(str(user.id))
|
|
new_refresh_token = create_refresh_token(str(user.id))
|
|
|
|
set_auth_cookies(response, new_access_token, new_refresh_token)
|
|
|
|
return TokenResponse(
|
|
access_token=new_access_token,
|
|
refresh_token=new_refresh_token,
|
|
)
|
|
|
|
|
|
@router.post("/logout")
|
|
async def logout(response: Response):
|
|
clear_auth_cookies(response)
|
|
return {"message": "Successfully logged out"}
|
|
|
|
|
|
# lets add protected routers here
|
|
|
|
|
|
# get logged in user info
|
|
@router.get("/me", response_model=UserRead)
|
|
async def get_current_user_info(current_user: CurrentUser):
|
|
return current_user
|
|
|
|
|
|
@router.post("/register", response_model=UserRead, status_code=status.HTTP_201_CREATED)
|
|
async def register_user(
|
|
user_in: UserCreate,
|
|
session: Annotated[AsyncSession, Depends(get_session)],
|
|
current_user: CurrentSuperuser, # noqa: ARG001
|
|
):
|
|
"""
|
|
Register a new user. **Admin only**.
|
|
"""
|
|
result = await session.execute(select(User).where(User.email == user_in.email))
|
|
if result.scalar_one_or_none():
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="User with this email already exists",
|
|
)
|
|
|
|
result = await session.execute(
|
|
select(User).where(User.username == user_in.username)
|
|
)
|
|
if result.scalar_one_or_none():
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="User with this username already exists",
|
|
)
|
|
|
|
user = User(
|
|
email=user_in.email,
|
|
username=user_in.username,
|
|
hashed_password=hash_password(user_in.password),
|
|
)
|
|
session.add(user)
|
|
await session.commit()
|
|
await session.refresh(user)
|
|
|
|
return user
|