Добавление страницы 2FA, poetry -> uv

This commit is contained in:
2026-01-11 15:26:39 +03:00
parent 83957ff548
commit 758e0fc9e6
14 changed files with 2654 additions and 2356 deletions
+47 -7
View File
@@ -1,19 +1,40 @@
"""Модуль работы с авторизацией и аутентификацией пользователей"""
from datetime import timedelta
from typing import Annotated
from fastapi import APIRouter, Body, Depends, HTTPException, status
from fastapi import APIRouter, Body, Depends, HTTPException, status, Request
from fastapi.security import OAuth2PasswordRequestForm
from sqlmodel import Session, select
import pyotp
from library_service.models.db import Role, User
from library_service.models.dto import Token, UserCreate, UserRead, UserUpdate, UserList, RoleRead, RoleList
from library_service.models.dto import (
Token,
UserCreate,
UserRead,
UserUpdate,
UserList,
RoleRead,
RoleList,
)
from library_service.settings import get_session
from library_service.auth import (ACCESS_TOKEN_EXPIRE_MINUTES, RequireAuth, RequireAdmin, RequireStaff,
authenticate_user, get_password_hash, decode_token,
create_access_token, create_refresh_token)
from library_service.auth import (
ACCESS_TOKEN_EXPIRE_MINUTES,
RequireAuth,
RequireAdmin,
RequireStaff,
authenticate_user,
get_password_hash,
decode_token,
create_access_token,
create_refresh_token,
qr_to_bitmap_b64,
)
from pathlib import Path
from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
router = APIRouter(prefix="/auth", tags=["authentication"])
@@ -45,7 +66,7 @@ def register(user_data: UserCreate, session: Session = Depends(get_session)):
db_user = User(
**user_data.model_dump(exclude={"password"}),
hashed_password=get_password_hash(user_data.password)
hashed_password=get_password_hash(user_data.password),
)
default_role = session.exec(select(Role).where(Role.name == "member")).first()
@@ -305,3 +326,22 @@ def get_roles(
roles=[RoleRead(**role.model_dump(exclude=exclude)) for role in roles],
total=len(roles),
)
@router.get(
"/2fa",
summary="Создание QR-кода TOTP 2FA",
description="Получить информацию о текущем авторизованном пользователе",
)
def get_totp_qr_bitmap(auth: RequireAuth):
"""Возвращает qr-код bitmap"""
issuer = "issuer"
username = auth.username
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret)
provisioning_uri = totp.provisioning_uri(name=username, issuer_name=issuer)
bitmap_data = qr_to_bitmap_b64(provisioning_uri)
return {"secret": secret, "username": username, "issuer": issuer, **bitmap_data}
+16 -7
View File
@@ -1,4 +1,5 @@
"""Модуль прочих эндпоинтов"""
from datetime import datetime
from pathlib import Path
from typing import Dict
@@ -24,7 +25,7 @@ def get_info(app) -> Dict:
"app_info": {
"title": app.title,
"version": app.version,
"description": app.description.rsplit('|', 1)[0],
"description": app.description.rsplit("|", 1)[0],
},
"server_time": datetime.now().isoformat(),
}
@@ -102,6 +103,12 @@ async def auth(request: Request):
return templates.TemplateResponse(request, "auth.html")
@router.get("/set-2fa", include_in_schema=False)
async def set2fa(request: Request):
"""Рендерит страницу установки двухфакторной аутентификации"""
return templates.TemplateResponse(request, "2fa.html")
@router.get("/profile", include_in_schema=False)
async def profile(request: Request):
"""Рендерит страницу профиля пользователя"""
@@ -167,9 +174,11 @@ async def api_stats(session: Session = Depends(get_session)):
books = select(func.count()).select_from(Book)
genres = select(func.count()).select_from(Genre)
users = select(func.count()).select_from(User)
return JSONResponse(content={
"authors": session.exec(authors).one(),
"books": session.exec(books).one(),
"genres": session.exec(genres).one(),
"users": session.exec(users).one(),
})
return JSONResponse(
content={
"authors": session.exec(authors).one(),
"books": session.exec(books).one(),
"genres": session.exec(genres).one(),
"users": session.exec(users).one(),
}
)