added auth

parent b4e19ce9
This diff is collapsed.
...@@ -14,6 +14,10 @@ python-multipart = "^0.0.6" ...@@ -14,6 +14,10 @@ python-multipart = "^0.0.6"
miniopy-async = "^1.17" miniopy-async = "^1.17"
asyncpg = "^0.29.0" asyncpg = "^0.29.0"
aerich = "^0.7.2" aerich = "^0.7.2"
python-jose = {extras = ["cryptography"], version = "^3.3.0"}
pydantic = {extras = ["email"], version = "^2.5.2"}
passlib = "^1.7.4"
bcrypt = "^4.1.2"
......
from exceptions import common as common_exc, http as http_exc
class BaseController:
repo = None
async def get_list(self, **kwargs) -> list[dict]:
return await self.repo.get_list(**kwargs)
async def get(self, id: int) -> dict:
try:
return await self.repo.get(id)
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
async def create(self, **data) -> dict:
try:
return await self.repo.create(**data)
except common_exc.CreateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
async def update(self, id: int, **kwargs) -> dict:
try:
return await self.repo.update(id, **kwargs)
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
except common_exc.UpdateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
async def delete(self, id: int) -> None:
try:
return await self.repo.delete(id)
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
except common_exc.DeleteException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
import fastapi as fa
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
import settings
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def get_token(token: str = fa.Header(...)):
return token
def authenticate_user(token: str = Depends(get_token)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
return username
except JWTError:
raise credentials_exception
from fastapi import APIRouter from fastapi import APIRouter
from .users.routes import router as user_route, token_router
router = APIRouter(prefix='/api') router = APIRouter(prefix='/api')
router.include_router(user_route)
router.include_router(token_router)
from uuid import UUID
from starlette import status
import fastapi
from tortoise.exceptions import ValidationError
from ..base_ctrl import BaseController
from exceptions import common as common_exc, http as http_exc
from .services import UserService
class UserController(BaseController):
user_service = UserService()
async def get(self, token: str):
try:
return await self.user_service.get_user(token)
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
async def create(self, **kwargs):
try:
return await self.user_service.create_user(**kwargs)
except common_exc.CreateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
except ValidationError as e:
raise http_exc.HTTPBadRequestException(detail=f"Validation error: {str(e)}")
async def update(self, id: UUID, **kwargs):
try:
return await self.user_service.update_user(id, **kwargs)
except common_exc.UpdateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
async def delete(self, id: UUID):
try:
await self.user_service.delete_user(id)
return fastapi.responses.Response(status_code=status.HTTP_204_NO_CONTENT)
except common_exc.DeleteException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
from uuid import UUID
from datetime import timedelta
import fastapi as fa
from fastapi.security import OAuth2PasswordRequestForm
from starlette import status
from .schemas import UserCreateSchema, UserGetSchema, UserUpdateSchema
from .ctrl import UserController
from .services import UserService
import settings
from db.repositories import UserRepository
router = fa.APIRouter(prefix='/users', tags=['User'])
token_router = fa.APIRouter(tags=['AUTH'])
ctrl = UserController()
user_service = UserService()
user_repository = UserRepository()
@token_router.post('/token')
async def login(form_data: OAuth2PasswordRequestForm = fa.Depends()):
user = await user_service.authenticate_user(form_data.username, form_data.password)
if not user:
raise fa.HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Неверное имя пользователя или пароль",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=int(settings.access_token_expire_minutes))
refresh_token_expires = timedelta(days=int(settings.refresh_token_expire_minutes))
access_token = await user_service.create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
refresh_token = await user_service.create_refresh_token(
data={"sub": user.username}, expires_delta=refresh_token_expires
)
return {"access_token": access_token, "refresh_token": refresh_token, "token_type": "bearer"}
@router.get('/get', response_model=UserGetSchema)
async def get_user(token: str):
return await ctrl.get(token)
@router.post('')
async def create_user(body: UserCreateSchema):
user = await ctrl.create(**body.model_dump(exclude_none=True))
return UserGetSchema.model_validate(user)
@router.patch('/{id}')
async def update_user(
id: UUID, body: UserUpdateSchema,
token: str
):
current_user = await user_repository.get_current_user(token)
if current_user.id != id:
raise fa.HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You are not allowed to update this user",
)
user = await ctrl.update(id, **body.model_dump(exclude_none=True))
return UserGetSchema.model_validate(user)
@router.delete('/{id}')
async def delete_user(
id: UUID,
token: str
):
current_user = await user_repository.get_current_user(token)
if current_user.id != id:
raise fa.HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You are not allowed to delete this user",
)
return await ctrl.delete(id)
from uuid import UUID
from pydantic import BaseModel, ConfigDict
from pydantic import EmailStr
class UserBaseSchema(BaseModel):
username: str | None = None
password: str | None = None
class UserIdSchema(BaseModel):
id: UUID
model_config = ConfigDict(from_attributes=True)
class UserSchema(UserBaseSchema, UserIdSchema):
pass
class UserCreateSchema(UserBaseSchema):
name: str
surname: str
email: EmailStr
class UserGetSchema(UserIdSchema):
name: str
surname: str
email: EmailStr
class UserUpdateSchema(BaseModel):
name: str
surname: str
email: EmailStr
class Token(BaseModel):
access_token: str
refresh_token: str
from uuid import UUID
from datetime import datetime, timedelta
import fastapi as fa
from jose import jwt
from passlib.context import CryptContext
from starlette import status
from db.repositories import UserRepository
import settings
from db.models import User
class UserService:
user_repository = UserRepository()
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
async def get_user(self, token: str):
return await self.user_repository.get_current_user(token)
async def create_user(self, **kwargs):
return await self.user_repository.create(**kwargs)
async def update_user(self, id: UUID, **kwargs):
return await self.user_repository.update(id, **kwargs)
async def delete_user(self, id: UUID):
return await self.user_repository.delete(id)
async def verify_password(self, plain_password, hashed_password):
return self.pwd_context.verify(plain_password, hashed_password)
async def authenticate_user(self, username: str, password: str) -> User:
user = await self.user_repository.get_user_by_username(username)
if user and await self.verify_password(password, user.password):
return user
return None
async def create_access_token(self, data: dict, expires_delta: timedelta):
to_encode = data.copy()
expire = datetime.utcnow() + expires_delta
to_encode.update({"exp": expire})
return jwt.encode(to_encode, settings.secret_key, algorithm=settings.algorithm)
async def create_refresh_token(self, data: dict, expires_delta: timedelta):
to_encode = data.copy()
expire = datetime.utcnow() + expires_delta
to_encode.update({"exp": expire})
return jwt.encode(to_encode, settings.secret_key, algorithm=settings.algorithm)
async def get_object_user(self, username: str):
user = self.user_repository.get(username == username).first()
if not user:
raise fa.HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authorized",
headers={"WWW-Authenticate": "Bearer"},
)
return user
from uuid import UUID
import tortoise
from passlib.context import CryptContext
from .models import User
from exceptions import common as common_exc
from api.dependencies import authenticate_user
class BaseRepository:
model: tortoise.Model
async def get_list(self, **kwargs) -> list[tortoise.Model]:
return await self.model.filter(**kwargs)
async def get(self, id: UUID) -> tortoise.Model:
try:
return await self.model.get(id=id)
except tortoise.exceptions.DoesNotExist as e:
raise common_exc.NotFoundException(str(e))
async def create(self, **kwargs) -> tortoise.Model:
try:
return await self.model.create(**kwargs)
except tortoise.exceptions.IntegrityError as e:
raise common_exc.CreateException(str(e))
async def update(self, id: UUID, **kwargs) -> tortoise.Model:
try:
instance = await self.get(id=id)
await instance.update_from_dict(kwargs).save()
return instance
except tortoise.exceptions.IntegrityError as e:
raise common_exc.UpdateException(str(e))
async def delete(self, id: UUID) -> None:
try:
instance = await self.get(id=id)
await instance.delete()
except tortoise.exceptions.IntegrityError as e:
raise common_exc.DeleteException(str(e))
class UserRepository(BaseRepository):
model = User
@staticmethod
def __get_password_hash(password: str) -> str:
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
return pwd_context.hash(password)
async def get_current_user(self, token):
return await self.get_user_by_username(authenticate_user(token))
async def create(self, **kwargs):
kwargs['password'] = self.__get_password_hash(kwargs['password'])
return await super().create(**kwargs)
async def get_user_by_username(self, username: str) -> User:
return await self.model.get(username=username)
...@@ -9,3 +9,7 @@ minio_root_user = os.getenv('MINIO_ROOT_USER') ...@@ -9,3 +9,7 @@ minio_root_user = os.getenv('MINIO_ROOT_USER')
minio_root_password = os.getenv('MINIO_ROOT_PASSWORD') minio_root_password = os.getenv('MINIO_ROOT_PASSWORD')
minio_url = os.getenv('MINIO_URL') minio_url = os.getenv('MINIO_URL')
minio_bucket_name = os.getenv('MINIO_BUCKET_NAME') minio_bucket_name = os.getenv('MINIO_BUCKET_NAME')
secret_key = os.getenv('SECRET_KEY')
algorithm = os.getenv('ALGORITHM')
access_token_expire_minutes = os.getenv('ACCESS_TOKEN_EXPIRE_MINUTES')
refresh_token_expire_minutes = os.getenv('REFRESH_TOKEN_EXPIRE_DAYS')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment