Compare commits
6 Commits
c6e82f292d
...
c0ee89f175
Author | SHA1 | Date | |
---|---|---|---|
c0ee89f175 | |||
623e9ba325 | |||
67f980d162 | |||
b71f6d2a81 | |||
2520d49a2b | |||
b10286773a |
@ -4,3 +4,4 @@ DB_PASSWORD=db_password
|
|||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
DB_ECHO=True
|
DB_ECHO=True
|
||||||
|
SECRET_KEY=1234567890abcdefghigklmnopqrstuvwxyz
|
||||||
|
7
lkeep/apps/__init__.py
Normal file
7
lkeep/apps/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
"""
|
||||||
|
Проект: Lkeep
|
||||||
|
Автор: Иван Ашихмин
|
||||||
|
Год: 2025
|
||||||
|
Специально для проекта "Код на салфетке"
|
||||||
|
https://pressanybutton.ru/category/servis-na-fastapi/
|
||||||
|
"""
|
7
lkeep/apps/auth/__init__.py
Normal file
7
lkeep/apps/auth/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
"""
|
||||||
|
Проект: Lkeep
|
||||||
|
Автор: Иван Ашихмин
|
||||||
|
Год: 2025
|
||||||
|
Специально для проекта "Код на салфетке"
|
||||||
|
https://pressanybutton.ru/category/servis-na-fastapi/
|
||||||
|
"""
|
28
lkeep/apps/auth/handlers.py
Normal file
28
lkeep/apps/auth/handlers.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from passlib.context import CryptContext
|
||||||
|
|
||||||
|
from lkeep.core.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
class AuthHandler:
|
||||||
|
"""
|
||||||
|
Обрабатывает аутентификационные запросы и обеспечивает безопасность пользовательских данных.
|
||||||
|
|
||||||
|
:ivar secret: Секретный ключ, используемый для дополнительной безопасности при генерации хешей.
|
||||||
|
:type secret: str
|
||||||
|
:ivar pwd_context: Контекст для использования bcrypt-алгоритма хеширования паролей.
|
||||||
|
:type pwd_context: CryptContext
|
||||||
|
"""
|
||||||
|
|
||||||
|
secret = settings.secret_key.get_secret_value()
|
||||||
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
|
||||||
|
async def get_password_hash(self, password: str) -> str:
|
||||||
|
"""
|
||||||
|
Генерирует хэш-значение пароля для безопасного сохранения и сравнения.
|
||||||
|
|
||||||
|
:param password: Пароль пользователя, который нужно зашифровать.
|
||||||
|
:type password: str
|
||||||
|
:returns: Хешированный вариант пароля.
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
return self.pwd_context.hash(password)
|
56
lkeep/apps/auth/managers.py
Normal file
56
lkeep/apps/auth/managers.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
"""
|
||||||
|
Проект: Lkeep
|
||||||
|
Автор: Иван Ашихмин
|
||||||
|
Год: 2025
|
||||||
|
Специально для проекта "Код на салфетке"
|
||||||
|
https://pressanybutton.ru/category/servis-na-fastapi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from fastapi import Depends, HTTPException
|
||||||
|
from sqlalchemy import insert
|
||||||
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
|
from lkeep.apps.auth.schemas import CreateUser, UserReturnData
|
||||||
|
from lkeep.core.core_dependency.db_dependency import DBDependency
|
||||||
|
from lkeep.database.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class UserManager:
|
||||||
|
"""
|
||||||
|
Класс для управления пользователями.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, model: type[User] = User, db: DBDependency = Depends(DBDependency)) -> None:
|
||||||
|
"""
|
||||||
|
Инициализирует экземпляр класса.
|
||||||
|
|
||||||
|
:param model: Модель, используемая для работы с данными.
|
||||||
|
:type model: Type[User]
|
||||||
|
:param db: Зависимость от базы данных. По умолчанию используется Depends(DBDependency).
|
||||||
|
:type db: DBDependency
|
||||||
|
"""
|
||||||
|
self.db = db
|
||||||
|
self.model = model
|
||||||
|
|
||||||
|
async def create_user(self, user: CreateUser) -> UserReturnData:
|
||||||
|
"""
|
||||||
|
Создает нового пользователя в базе данных.
|
||||||
|
|
||||||
|
:param user: Объект с данными для создания пользователя.
|
||||||
|
:type user: CreateUser
|
||||||
|
:returns: Данные созданного пользователя.
|
||||||
|
:rtype: UserReturnData
|
||||||
|
:raises HTTPException: Если пользователь уже существует.
|
||||||
|
"""
|
||||||
|
async with self.db.db_session as session:
|
||||||
|
query = insert(self.model).values(**user.model_dump()).returning(self.model)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = await session.execute(query)
|
||||||
|
except IntegrityError:
|
||||||
|
raise HTTPException(status_code=400, detail="User already exists.")
|
||||||
|
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
user_data = await result.scalar_one()
|
||||||
|
return UserReturnData(**user_data.__dict__)
|
79
lkeep/apps/auth/schemas.py
Normal file
79
lkeep/apps/auth/schemas.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
"""
|
||||||
|
Проект: Lkeep
|
||||||
|
Автор: Иван Ашихмин
|
||||||
|
Год: 2025
|
||||||
|
Специально для проекта "Код на салфетке"
|
||||||
|
https://pressanybutton.ru/category/servis-na-fastapi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from pydantic import BaseModel, EmailStr
|
||||||
|
|
||||||
|
|
||||||
|
class GetUserByID(BaseModel):
|
||||||
|
"""
|
||||||
|
Класс для получения пользователя по его уникальному идентификатору (ID).
|
||||||
|
|
||||||
|
:ivar id: Уникальный идентификатор пользователя, может быть представлен как объект типа uuid.UUID или строкой.
|
||||||
|
:type id: uuid.UUID | str
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: uuid.UUID | str
|
||||||
|
|
||||||
|
|
||||||
|
class GetUserByEmail(BaseModel):
|
||||||
|
"""
|
||||||
|
Класс для поиска пользователя по электронной почте.
|
||||||
|
|
||||||
|
:ivar email: Электронная почта пользователя.
|
||||||
|
:type email: EmailStr
|
||||||
|
"""
|
||||||
|
|
||||||
|
email: EmailStr
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterUser(GetUserByEmail):
|
||||||
|
"""
|
||||||
|
Класс для регистрации пользователя, наследующий класс GetUserByEmail.
|
||||||
|
|
||||||
|
:ivar password: Пароль пользователя.
|
||||||
|
:type password: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
class CreateUser(GetUserByEmail):
|
||||||
|
"""
|
||||||
|
Класс для создания пользователя.
|
||||||
|
|
||||||
|
:ivar hashed_password: Хэшированный пароль пользователя.
|
||||||
|
:type hashed_password: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
hashed_password: str
|
||||||
|
|
||||||
|
|
||||||
|
class UserReturnData(GetUserByID, GetUserByEmail):
|
||||||
|
"""
|
||||||
|
Класс для представления данных пользователя, возвращаемых из API.
|
||||||
|
|
||||||
|
:ivar is_active: Статус активности пользователя.
|
||||||
|
:type is_active: bool
|
||||||
|
:ivar is_verified: Статус верификации пользователя.
|
||||||
|
:type is_verified: bool
|
||||||
|
:ivar is_superuser: Флаг, указывающий на наличие привилегий суперпользователя.
|
||||||
|
:type is_superuser: bool
|
||||||
|
:ivar created_at: Временная метка создания записи о пользователе.
|
||||||
|
:type created_at: datetime.datetime
|
||||||
|
:ivar updated_at: Временная метка последнего обновления записи о пользователе.
|
||||||
|
:type updated_at: datetime.datetime
|
||||||
|
"""
|
||||||
|
|
||||||
|
is_active: bool
|
||||||
|
is_verified: bool
|
||||||
|
is_superuser: bool
|
||||||
|
created_at: datetime.datetime
|
||||||
|
updated_at: datetime.datetime
|
@ -1,21 +1,18 @@
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||||
|
|
||||||
|
from lkeep.core.settings import settings
|
||||||
|
|
||||||
|
|
||||||
class DBDependency:
|
class DBDependency:
|
||||||
"""
|
"""
|
||||||
Класс для управления зависимостями базы данных, используя SQLAlchemy.
|
Класс для управления зависимостями базы данных, используя SQLAlchemy.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, db_url: str, db_echo: bool) -> None:
|
def __init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
Инициализирует экземпляр класса, отвечающего за взаимодействие с асинхронной базой данных.
|
Инициализирует экземпляр класса, отвечающего за взаимодействие с асинхронной базой данных.
|
||||||
|
|
||||||
:param db_url: URL для подключения к базе данных.
|
|
||||||
:type db_url: str
|
|
||||||
:param db_echo: Флаг, определяющий вывод подробных логов при взаимодействии с базой данных.
|
|
||||||
:type db_echo: bool
|
|
||||||
"""
|
"""
|
||||||
self._engine = create_async_engine(url=db_url, echo=db_echo)
|
self._engine = create_async_engine(url=settings.db_settings.db_url, echo=settings.db_settings.db_echo)
|
||||||
self._session_factory = async_sessionmaker(bind=self._engine, expire_on_commit=False, autocommit=False)
|
self._session_factory = async_sessionmaker(bind=self._engine, expire_on_commit=False, autocommit=False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -54,9 +54,14 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
:ivar db_settings: Экземпляр класса DBSettings, содержащий настройки базы данных.
|
:ivar db_settings: Экземпляр класса DBSettings, содержащий настройки базы данных.
|
||||||
:type db_settings: DBSettings
|
:type db_settings: DBSettings
|
||||||
|
:ivar secret_key: Секретный ключ для шифрования
|
||||||
|
:type secret_key: SecretStr
|
||||||
"""
|
"""
|
||||||
|
|
||||||
db_settings: DBSettings = DBSettings()
|
db_settings: DBSettings = DBSettings()
|
||||||
|
secret_key: SecretStr
|
||||||
|
|
||||||
|
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf8", extra="ignore")
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
56
poetry.lock
generated
56
poetry.lock
generated
@ -118,6 +118,41 @@ docs = ["Sphinx (>=8.1.3,<8.2.0)", "sphinx-rtd-theme (>=1.2.2)"]
|
|||||||
gssauth = ["gssapi", "sspilib"]
|
gssauth = ["gssapi", "sspilib"]
|
||||||
test = ["distro (>=1.9.0,<1.10.0)", "flake8 (>=6.1,<7.0)", "flake8-pyi (>=24.1.0,<24.2.0)", "gssapi", "k5test", "mypy (>=1.8.0,<1.9.0)", "sspilib", "uvloop (>=0.15.3)"]
|
test = ["distro (>=1.9.0,<1.10.0)", "flake8 (>=6.1,<7.0)", "flake8-pyi (>=24.1.0,<24.2.0)", "gssapi", "k5test", "mypy (>=1.8.0,<1.9.0)", "sspilib", "uvloop (>=0.15.3)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bcrypt"
|
||||||
|
version = "4.0.1"
|
||||||
|
description = "Modern password hashing for your software and your servers"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"},
|
||||||
|
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"},
|
||||||
|
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"},
|
||||||
|
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"},
|
||||||
|
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"},
|
||||||
|
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"},
|
||||||
|
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"},
|
||||||
|
{file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"},
|
||||||
|
{file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"},
|
||||||
|
{file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"},
|
||||||
|
{file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"},
|
||||||
|
{file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"},
|
||||||
|
{file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"},
|
||||||
|
{file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"},
|
||||||
|
{file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"},
|
||||||
|
{file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"},
|
||||||
|
{file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"},
|
||||||
|
{file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"},
|
||||||
|
{file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"},
|
||||||
|
{file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"},
|
||||||
|
{file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tests = ["pytest (>=3.2.1,!=3.3.0)"]
|
||||||
|
typecheck = ["mypy"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2024.12.14"
|
version = "2024.12.14"
|
||||||
@ -674,6 +709,24 @@ files = [
|
|||||||
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
|
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "passlib"
|
||||||
|
version = "1.7.4"
|
||||||
|
description = "comprehensive password hashing framework supporting over 30 schemes"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
|
||||||
|
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
argon2 = ["argon2-cffi (>=18.2.0)"]
|
||||||
|
bcrypt = ["bcrypt (>=3.1.0)"]
|
||||||
|
build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"]
|
||||||
|
totp = ["cryptography"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platformdirs"
|
name = "platformdirs"
|
||||||
version = "4.3.6"
|
version = "4.3.6"
|
||||||
@ -724,6 +777,7 @@ files = [
|
|||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
annotated-types = ">=0.6.0"
|
annotated-types = ">=0.6.0"
|
||||||
|
email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""}
|
||||||
pydantic-core = "2.27.2"
|
pydantic-core = "2.27.2"
|
||||||
typing-extensions = ">=4.12.2"
|
typing-extensions = ">=4.12.2"
|
||||||
|
|
||||||
@ -1426,4 +1480,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = ">=3.12"
|
python-versions = ">=3.12"
|
||||||
content-hash = "b171a7f89cc3ee2207778b1e189fbb1821fccda26de00d0181156d0e183a5b75"
|
content-hash = "3d4c2b89136e414e183f596de6faca17a5c9d78e0b2788ad4cb4fd598e486faa"
|
||||||
|
@ -16,6 +16,9 @@ dependencies = [
|
|||||||
"pydantic-settings (>=2.7.1,<3.0.0)",
|
"pydantic-settings (>=2.7.1,<3.0.0)",
|
||||||
"alembic (>=1.14.0,<2.0.0)",
|
"alembic (>=1.14.0,<2.0.0)",
|
||||||
"ruff (>=0.9.0,<0.10.0)",
|
"ruff (>=0.9.0,<0.10.0)",
|
||||||
|
"pydantic[email] (>=2.10.5,<3.0.0)",
|
||||||
|
"passlib (>=1.7.4,<2.0.0)",
|
||||||
|
"bcrypt (==4.0.1)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user