Единый тип ответа авторизации, добавление кнопки создания автора на странице авторы

This commit is contained in:
2026-01-31 23:41:56 +03:00
parent dfa4d14afc
commit 19d322c9d9
8 changed files with 50 additions and 43 deletions
+1 -1
View File
@@ -40,5 +40,5 @@ RECOVERY_MIN_REMAINING_WARNING=3
RECOVERY_MAX_AGE_DAYS=365
# TOTP_2FA
TOTP_ISSUER=LiB
TOTP_ISSUER=LiB-local
TOTP_VALID_WINDOW=1
+1 -3
View File
@@ -7,7 +7,7 @@ from .role import RoleBase, RoleCreate, RoleList, RoleRead, RoleUpdate
from .user import UserBase, UserCreate, UserList, UserRead, UserUpdate, UserLogin
from .loan import LoanBase, LoanCreate, LoanList, LoanRead, LoanUpdate
from .recovery import RecoveryCodesResponse, RecoveryCodesStatus, RecoveryCodeUse
from .token import Token, TokenData, PartialToken
from .token import TokenData
from .misc import (
AuthorWithBooks,
GenreWithBooks,
@@ -62,9 +62,7 @@ __all__ = [
"RoleUpdate",
"RoleRead",
"RoleList",
"Token",
"TokenData",
"PartialToken",
"TOTPSetupResponse",
"TOTPVerifyRequest",
"TOTPDisableRequest",
+2 -2
View File
@@ -11,8 +11,8 @@ class AuthorBase(SQLModel):
name: str = Field(description="Псевдоним")
model_config = ConfigDict( # pyright: ignore
json_schema_extra={"example": {"name": "author_name"}}
model_config = ConfigDict(
json_schema_extra={"example": {"name": "John Doe"}}
)
+11
View File
@@ -2,6 +2,7 @@
from typing import List
from pydantic import ConfigDict
from sqlmodel import SQLModel, Field
@@ -12,6 +13,16 @@ class RoleBase(SQLModel):
description: str | None = Field(None, description="Описание")
payroll: int = Field(0, description="Оплата")
model_config = ConfigDict(
json_schema_extra={
"example": {
"name": "admin",
"description": "system administrator",
"payroll": 500,
}
}
)
class RoleCreate(RoleBase):
"""Модель роли для создания"""
+1 -17
View File
@@ -1,24 +1,8 @@
"""Модуль DTO-моделей токенов"""
"""Модуль DTO-модели токена"""
from sqlmodel import SQLModel, Field
class Token(SQLModel):
"""Модель токена"""
access_token: str = Field(description="Токен доступа")
token_type: str = Field("bearer", description="Тип токена")
refresh_token: str | None = Field(None, description="Токен обновления")
class PartialToken(SQLModel):
"""Частичный токен — для подтверждения 2FA"""
partial_token: str = Field(description="Частичный токен")
token_type: str = Field("partial", description="Тип токена")
requires_2fa: bool = Field(True, description="Требуется TOTP-код")
class TokenData(SQLModel):
"""Модель содержимого токена"""
+23 -20
View File
@@ -12,15 +12,12 @@ from sqlmodel import Session, select
from library_service.services import require_captcha
from library_service.models.db import Role, User
from library_service.models.dto import (
Token,
UserCreate,
UserRead,
UserUpdate,
UserList,
RoleRead,
RoleList,
Token,
PartialToken,
LoginResponse,
RecoveryCodeUse,
RegisterResponse,
@@ -147,11 +144,14 @@ def login(
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
new_access_token = create_access_token(
data=token_data, expires_delta=access_token_expires
)
new_refresh_token = create_refresh_token(data=token_data)
return LoginResponse(
access_token=create_access_token(
data=token_data, expires_delta=access_token_expires
),
refresh_token=create_refresh_token(data=token_data),
access_token=new_access_token,
refresh_token=new_refresh_token,
token_type="bearer",
requires_2fa=False,
)
@@ -159,7 +159,7 @@ def login(
@router.post(
"/refresh",
response_model=Token,
response_model=LoginResponse,
summary="Обновление токена",
description="Получение новой пары токенов, используя действующий Refresh токен",
)
@@ -190,19 +190,18 @@ def refresh_token(
detail="User is inactive",
)
token_data = {"sub": user.username, "user_id": user.id}
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
new_access_token = create_access_token(
data={"sub": user.username, "user_id": user.id},
expires_delta=access_token_expires,
)
new_refresh_token = create_refresh_token(
data={"sub": user.username, "user_id": user.id}
data=token_data, expires_delta=access_token_expires
)
new_refresh_token = create_refresh_token(data=token_data)
return Token(
return LoginResponse(
access_token=new_access_token,
refresh_token=new_refresh_token,
token_type="bearer",
requires_2fa=False,
)
@@ -343,7 +342,7 @@ def disable_2fa(
@router.post(
"/2fa/verify",
response_model=Token,
response_model=LoginResponse,
summary="Верификация 2FA",
description="Завершает аутентификацию с помощью TOTP кода или резервного кода",
)
@@ -374,12 +373,16 @@ def verify_2fa(
token_data = {"sub": user.username, "user_id": user.id}
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
new_access_token = create_access_token(
data=token_data, expires_delta=access_token_expires
)
new_refresh_token = create_refresh_token(data=token_data)
return Token(
access_token=create_access_token(
data=token_data, expires_delta=access_token_expires
),
refresh_token=create_refresh_token(data=token_data),
return LoginResponse(
access_token=new_access_token,
refresh_token=new_refresh_token,
token_type="bearer",
requires_2fa=False,
)
+5
View File
@@ -6,6 +6,11 @@ $(document).ready(() => {
let currentSort = "name_asc";
loadAuthors();
const USER_CAN_MANAGE =
typeof window.canManage === "function" && window.canManage();
if (USER_CAN_MANAGE) {
$("#add-author-btn").removeClass("hidden");
}
function loadAuthors() {
showLoadingState();
+6
View File
@@ -6,6 +6,12 @@
<h2 class="text-2xl font-bold text-gray-800">Авторы</h2>
<div class="flex flex-col sm:flex-row gap-4 w-full md:w-auto">
<a href="/author/create" id="add-author-btn" class="hidden flex justify-center items-center px-4 py-2 bg-green-600 text-white font-medium rounded-lg hover:bg-green-700 transition whitespace-nowrap">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
</svg>
Добавить автора
</a>
<div class="relative">
<input
type="text"