Добавление авторизации и фронтэнда

This commit is contained in:
2025-12-18 18:52:09 +03:00
parent 2c24f66de0
commit 756e941f99
55 changed files with 2314 additions and 577 deletions
+156
View File
@@ -0,0 +1,156 @@
"""Модуль работы с авторизацией и аутентификацией пользователей"""
from datetime import timedelta
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlmodel import Session, select
from library_service.models.db import Role, User
from library_service.models.dto import Token, UserCreate, UserRead, UserUpdate
from library_service.settings import get_session
from library_service.auth import (ACCESS_TOKEN_EXPIRE_MINUTES, RequireAdmin,
RequireAuth, authenticate_user, get_password_hash,
create_access_token, create_refresh_token)
router = APIRouter(prefix="/auth", tags=["authentication"])
@router.post(
"/register",
response_model=UserRead,
status_code=status.HTTP_201_CREATED,
summary="Регистрация нового пользователя",
description="Создает нового пользователя в системе",
)
def register(user_data: UserCreate, session: Session = Depends(get_session)):
"""Эндпоинт регистрации пользователя"""
# Проверка если username существует
existing_user = session.exec(
select(User).where(User.username == user_data.username)
).first()
if existing_user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Username already registered",
)
# Проверка если email существует
existing_email = session.exec(
select(User).where(User.email == user_data.email)
).first()
if existing_email:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered"
)
# Создание пользователя
db_user = User(
**user_data.model_dump(exclude={"password"}),
hashed_password=get_password_hash(user_data.password)
)
# Назначение роли по умолчанию
default_role = session.exec(select(Role).where(Role.name == "user")).first()
if default_role:
db_user.roles.append(default_role)
session.add(db_user)
session.commit()
session.refresh(db_user)
return UserRead(**db_user.model_dump(), roles=[role.name for role in db_user.roles])
@router.post(
"/token",
response_model=Token,
summary="Получение токена",
description="Аутентификация и получение JWT токена",
)
def login(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
session: Session = Depends(get_session),
):
"""Эндпоинт аутентификации и получения JWT токена"""
user = authenticate_user(session, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username, "user_id": user.id},
expires_delta=access_token_expires,
)
refresh_token = create_refresh_token(
data={"sub": user.username, "user_id": user.id}
)
return Token(
access_token=access_token, refresh_token=refresh_token, token_type="bearer"
)
@router.get(
"/me",
response_model=UserRead,
summary="Текущий пользователь",
description="Получить информацию о текущем авторизованном пользователе",
)
def read_users_me(current_user: RequireAuth):
"""Эндпоинт получения информации о себе"""
return UserRead(
**current_user.model_dump(), roles=[role.name for role in current_user.roles]
)
@router.put(
"/me",
response_model=UserRead,
summary="Обновить профиль",
description="Обновить информацию текущего пользователя",
)
def update_user_me(
user_update: UserUpdate,
current_user: RequireAuth,
session: Session = Depends(get_session),
):
"""Эндпоинт обновления пользователя"""
if user_update.email:
current_user.email = user_update.email
if user_update.full_name:
current_user.full_name = user_update.full_name
if user_update.password:
current_user.hashed_password = get_password_hash(user_update.password)
session.add(current_user)
session.commit()
session.refresh(current_user)
return UserRead(
**current_user.model_dump(), roles=[role.name for role in current_user.roles]
)
@router.get(
"/users",
response_model=list[UserRead],
summary="Список пользователей",
description="Получить список всех пользователей (только для админов)",
)
def read_users(
admin: RequireAdmin,
session: Session = Depends(get_session),
skip: int = 0,
limit: int = 100,
):
"""Эндпоинт получения списка всех пользователей"""
users = session.exec(select(User).offset(skip).limit(limit)).all()
return [
UserRead(**user.model_dump(), roles=[role.name for role in user.roles])
for user in users
]