fix rate-limit valid login bug and fix sessions of redis and psql in tests
This commit is contained in:
Vendored
+1
-2
@@ -19,8 +19,7 @@ class RateLimit:
|
|||||||
if attempts>5:
|
if attempts>5:
|
||||||
raise self.errors.rate_limit_error(detail="too many attempts", retry_after=60)
|
raise self.errors.rate_limit_error(detail="too many attempts", retry_after=60)
|
||||||
|
|
||||||
async def check_rate_limit(self, request: Request) -> None:
|
async def check_rate_limit(self, client_ip:str) -> None:
|
||||||
client_ip = request.headers.get('x-forwarded-for', '').split(',')[0].strip() or (request.client.host if request.client else 'unknown')
|
|
||||||
await self.rate_limit(client_ip)
|
await self.rate_limit(client_ip)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,12 @@ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
|||||||
|
|
||||||
from src.models.configs_read.env import env_settings
|
from src.models.configs_read.env import env_settings
|
||||||
|
|
||||||
engine = create_async_engine(f"postgresql+asyncpg://{env_settings.DB_USER}:{env_settings.DB_PASSWORD}@{env_settings.DB_HOST}:{env_settings.DB_PORT}/{env_settings.DB_POSTGRESS}")
|
engine = create_async_engine(f"postgresql+asyncpg://{env_settings.DB_USER}:{env_settings.DB_PASSWORD}@{env_settings.DB_HOST}:{env_settings.DB_PORT}/{env_settings.DB_POSTGRESS}",
|
||||||
|
pool_size=20, # сколько соединений держать открытыми постоянно
|
||||||
|
max_overflow=10, # сколько доп. соединений можно создать при пиковой нагрузке
|
||||||
|
pool_timeout=30, # сколько ждать свободное соединение, прежде чем упасть с ошибкой
|
||||||
|
pool_pre_ping=True, # проверять соединение перед использованием (ловит "протухшие" соединения)
|
||||||
|
)
|
||||||
|
|
||||||
'''remember as a boilerplate, or just cp/pst'''
|
'''remember as a boilerplate, or just cp/pst'''
|
||||||
class Model(DeclarativeBase):
|
class Model(DeclarativeBase):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from fastapi import APIRouter, Cookie, Depends, Request, Response
|
from fastapi import APIRouter, Cookie, Depends, HTTPException, Request, Response
|
||||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||||
|
|
||||||
from src.cache.rate_limit import rate_limiter
|
from src.cache.rate_limit import rate_limiter
|
||||||
@@ -24,10 +24,15 @@ async def get_access_token(request: Request,
|
|||||||
response:Response,
|
response:Response,
|
||||||
auth:CurrentUserService=Depends(auth_service),
|
auth:CurrentUserService=Depends(auth_service),
|
||||||
form_data:OAuth2PasswordRequestForm=Depends(),
|
form_data:OAuth2PasswordRequestForm=Depends(),
|
||||||
_:None = Depends(rate_limiter.check_rate_limit)
|
|
||||||
)->dict:
|
)->dict:
|
||||||
|
|
||||||
|
client_ip = request.headers.get('x-forwarded-for', '').split(',')[0].strip() or (request.client.host if request.client else 'unknown')
|
||||||
|
|
||||||
|
try:
|
||||||
access_token, refresh_token=await auth.login(form_data_email=form_data.username, form_data_password=form_data.password, request=request)
|
access_token, refresh_token=await auth.login(form_data_email=form_data.username, form_data_password=form_data.password, request=request)
|
||||||
|
except HTTPException:
|
||||||
|
await rate_limiter.rate_limit(client_ip)
|
||||||
|
raise
|
||||||
|
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
key="refresh_token",
|
key="refresh_token",
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||||
|
|
||||||
|
from src.models.configs_read.env import env_settings
|
||||||
from src.service.auth.jwt import HashService, JwtService
|
from src.service.auth.jwt import HashService, JwtService
|
||||||
|
from src.service.users_crud.users_crud import CrudService
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
@@ -12,3 +15,16 @@ async def jwt_service()->JwtService:
|
|||||||
async def hash_service()->HashService:
|
async def hash_service()->HashService:
|
||||||
hash_service=HashService()
|
hash_service=HashService()
|
||||||
return hash_service
|
return hash_service
|
||||||
|
|
||||||
|
@pytest_asyncio.fixture
|
||||||
|
async def crud_service():
|
||||||
|
test_engine = create_async_engine(f"postgresql+asyncpg://{env_settings.DB_USER}:{env_settings.DB_PASSWORD}@{env_settings.DB_HOST}:{env_settings.DB_PORT}/{env_settings.DB_POSTGRESS}",
|
||||||
|
pool_size=20,
|
||||||
|
max_overflow=10,
|
||||||
|
pool_timeout=30,
|
||||||
|
pool_pre_ping=True
|
||||||
|
)
|
||||||
|
crud_service = CrudService()
|
||||||
|
crud_service.crud_db_actions.Session = async_sessionmaker(bind=test_engine)
|
||||||
|
yield crud_service
|
||||||
|
await test_engine.dispose()
|
||||||
@@ -12,8 +12,10 @@ class TestAuth:
|
|||||||
|
|
||||||
response = await requests_async.post(f"{target_url}/protected/token")
|
response = await requests_async.post(f"{target_url}/protected/token")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
assert exc_info.value.response.status_code != 403
|
assert exc_info.value.response.status_code != 403
|
||||||
assert exc_info.value.response.status_code != 401
|
assert exc_info.value.response.status_code != 401
|
||||||
|
assert exc_info.value.response.status_code != 429
|
||||||
|
|
||||||
async def test_get_refresh_token_positive(self, target_url:str)->None:
|
async def test_get_refresh_token_positive(self, target_url:str)->None:
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
|
|
||||||
|
from src.cache.redis_client import RedisClient
|
||||||
from src.service.auth.auth import CurrentUserService
|
from src.service.auth.auth import CurrentUserService
|
||||||
from src.service.users_crud.users_crud import CrudService
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
async def current_user_service()->CurrentUserService:
|
async def current_user_service(monkeypatch):
|
||||||
current_user_service=CurrentUserService()
|
|
||||||
return current_user_service
|
test_redis = RedisClient()
|
||||||
|
monkeypatch.setattr("src.service.auth.auth.redis_client", test_redis)
|
||||||
|
|
||||||
|
service = CurrentUserService()
|
||||||
|
|
||||||
|
yield service
|
||||||
|
await test_redis.aclose()
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
async def requests(mocker):
|
async def requests(mocker):
|
||||||
fake_request = mocker.MagicMock(spec=Request)
|
fake_request = mocker.MagicMock(spec=Request)
|
||||||
fake_request.headers = {"user-agent": "pytest-agent", "x-forwarded-for":"127.0.0.1"}
|
fake_request.headers = {"user-agent": "pytest-agent", "x-forwarded-for":"127.0.0.1"}
|
||||||
return fake_request
|
return fake_request
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
|
||||||
async def crud_service()->CrudService:
|
|
||||||
crud_service=CrudService()
|
|
||||||
return crud_service
|
|
||||||
@@ -118,7 +118,8 @@ class TestAuth:
|
|||||||
|
|
||||||
with allure.step("create fake refresh token"):
|
with allure.step("create fake refresh token"):
|
||||||
|
|
||||||
token=await jwt_service.create_refresh_token({"sub":str(uuid4())})
|
refresh_token=await jwt_service.create_refresh_token({"sub":str(uuid4())})
|
||||||
|
access_token=await jwt_service.create_access_token({"sub":str(uuid4)})
|
||||||
|
|
||||||
with allure.step("patching db call functions"):
|
with allure.step("patching db call functions"):
|
||||||
|
|
||||||
@@ -126,7 +127,7 @@ class TestAuth:
|
|||||||
|
|
||||||
with allure.step("test logout with fake data"):
|
with allure.step("test logout with fake data"):
|
||||||
|
|
||||||
status=await current_user_service.logout(token[0])
|
status=await current_user_service.logout(refresh_token[0], access_token)
|
||||||
assert status is True
|
assert status is True
|
||||||
|
|
||||||
@pytest.mark.parametrize("jti,db_result, expected_exception, expected_status",[
|
@pytest.mark.parametrize("jti,db_result, expected_exception, expected_status",[
|
||||||
@@ -148,11 +149,11 @@ class TestAuth:
|
|||||||
|
|
||||||
with allure.step("create fake refresh token"):
|
with allure.step("create fake refresh token"):
|
||||||
|
|
||||||
token=await fake_create_refresh_token({"sub":str(uuid4()), "jti":jti, "token_type":"refresh", "exp":datetime.now(UTC)+timedelta(days=45)})
|
refresh_token=await fake_create_refresh_token({"sub":str(uuid4()), "jti":jti, "token_type":"refresh", "exp":datetime.now(UTC)+timedelta(days=45)})
|
||||||
|
access_token=await fake_create_refresh_token({"sub":str(uuid4()), "jti":jti, "token_type":"refresh", "exp":datetime.now(UTC)+timedelta(minutes=30)})
|
||||||
with allure.step("test logout with fake data"), pytest.raises(expected_exception) as exc_info:
|
with allure.step("test logout with fake data"), pytest.raises(expected_exception) as exc_info:
|
||||||
|
|
||||||
await current_user_service.logout(token)
|
await current_user_service.logout(refresh_token, access_token)
|
||||||
|
|
||||||
if expected_exception is HTTPException:
|
if expected_exception is HTTPException:
|
||||||
assert exc_info.value.status_code==expected_status
|
assert exc_info.value.status_code==expected_status
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import pytest_asyncio
|
|
||||||
|
|
||||||
from src.service.users_crud.users_crud import CrudService
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
|
||||||
async def crud_service()->CrudService:
|
|
||||||
crud_service=CrudService()
|
|
||||||
return crud_service
|
|
||||||
Reference in New Issue
Block a user