From f74f6b6b4c74f8a9ecfe8c984af6d53ea2e4be16 Mon Sep 17 00:00:00 2001 From: TAMDeveloper13 Date: Mon, 16 Mar 2026 10:13:40 +0000 Subject: [PATCH] Update: Add rate limiter storage uri and admin adjustment --- poetry.lock | 42 +++++++++++++++++++++++++++++++++++++++--- pyproject.toml | 1 + src/auth/service.py | 38 ++++++++++++++++++++++---------------- src/config.py | 3 ++- src/enums.py | 7 ++++++- src/exceptions.py | 1 + src/rate_limiter.py | 6 ++++-- 7 files changed, 75 insertions(+), 23 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6dd16be..da06f7a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "absl-py" @@ -200,6 +200,19 @@ doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21.0b1) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] trio = ["trio (>=0.26.1)"] +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" and python_full_version < \"3.11.3\"" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] + [[package]] name = "asyncpg" version = "0.30.0" @@ -725,7 +738,7 @@ requests = ">=2.18.0,<3.0.0" [package.extras] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] +grpc = ["grpcio (>=1.33.2,<2.0.dev0)", "grpcio (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] @@ -2205,6 +2218,29 @@ files = [ {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +[[package]] +name = "redis" +version = "7.3.0" +description = "Python client for Redis database and key-value store" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "redis-7.3.0-py3-none-any.whl", hash = "sha256:9d4fcb002a12a5e3c3fbe005d59c48a2cc231f87fbb2f6b70c2d89bb64fec364"}, + {file = "redis-7.3.0.tar.gz", hash = "sha256:4d1b768aafcf41b01022410b3cc4f15a07d9b3d6fe0c66fc967da2c88e551034"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} + +[package.extras] +circuit-breaker = ["pybreaker (>=1.4.0)"] +hiredis = ["hiredis (>=3.2.0)"] +jwt = ["pyjwt (>=2.9.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (>=20.0.1)", "requests (>=2.31.0)"] +otel = ["opentelemetry-api (>=1.39.1)", "opentelemetry-exporter-otlp-proto-http (>=1.39.1)", "opentelemetry-sdk (>=1.39.1)"] +xxhash = ["xxhash (>=3.6.0,<3.7.0)"] + [[package]] name = "requests" version = "2.32.3" @@ -3046,4 +3082,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = "^3.11" -content-hash = "256c8104c6eeb5b288dd0cdf02fe7cbad4f75aa93fc71f8d44da8b605d72f886" +content-hash = "99817513416e8b654de085dac8ffa606f822d7f03cd12d553c0511298e823986" diff --git a/pyproject.toml b/pyproject.toml index 762975c..4132110 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ google-auth-httplib2 = "^0.2.0" google-auth-oauthlib = "^1.2.2" aiohttp = "^3.12.14" ortools = "^9.14.6206" +redis = "^7.3.0" [build-system] diff --git a/src/auth/service.py b/src/auth/service.py index 0311db4..f69f078 100644 --- a/src/auth/service.py +++ b/src/auth/service.py @@ -61,8 +61,9 @@ class JWTBearer(HTTPBearer): def verify_jwt(self, jwtoken: str, method: str, endpoint: str): try: + url_to_verify = f"{config.AUTH_SERVICE_API}/verify-token" response = requests.get( - f"{config.AUTH_SERVICE_API}/verify-token", + url_to_verify, headers={"Authorization": f"Bearer {jwtoken}"}, ) @@ -127,8 +128,9 @@ async def internal_key(request: Request): raise Exception(str(e)) else: try: + verify_url = f"{config.AUTH_SERVICE_API}/verify-token" response = requests.get( - f"{config.AUTH_SERVICE_API}/verify-token", + verify_url, headers={"Authorization": f"{token}"}, ) @@ -147,7 +149,7 @@ async def internal_key(request: Request): -import httpx +import asyncio import logging from typing import Dict, Any import src.config as config @@ -162,6 +164,7 @@ AUTH_NOTIFY_ENDPOINT = f"{config.AUTH_SERVICE_API}/admin/notify-limit" async def notify_admin_on_rate_limit( endpoint_name: str, ip_address: str, + request: Request, method: str = "POST", cooldown: int = 900, timeout: int = 5 @@ -172,21 +175,23 @@ async def notify_admin_on_rate_limit( Async version - gunakan di async context. """ payload = { - "endpoint_name": endpoint_name, + "endpoint_name": f"oh/{endpoint_name}".replace("//", ""), "ip_address": ip_address, "method": method, "cooldown": cooldown, } - + token = request.headers.get("Authorization") + headers = {"Authorization": token} if token else {} try: - async with httpx.AsyncClient(timeout=timeout) as client: - response = await client.post(AUTH_NOTIFY_ENDPOINT, json=payload) - response.raise_for_status() - result = response.json() - log.info(f"Notifikasi admin sent | Endpoint: {endpoint_name}") - return result - + response = await asyncio.to_thread( + requests.post, AUTH_NOTIFY_ENDPOINT, + json=payload, headers=headers, timeout=timeout + ) + response.raise_for_status() + result = response.json() + log.info(f"Notifikasi admin sent | Endpoint: {endpoint_name}") + return result except Exception as e: log.error(f"Error notifying admin: {str(e)}") @@ -198,6 +203,7 @@ async def notify_admin_on_rate_limit( def notify_admin_on_rate_limit_sync( endpoint_name: str, ip_address: str, + request: Request, method: str = "POST", cooldown: int = 900, timeout: int = 5 @@ -209,21 +215,21 @@ def notify_admin_on_rate_limit_sync( RECOMMENDED untuk use case ini. """ payload = { - "endpoint_name": endpoint_name, + "endpoint_name": f"oh/{endpoint_name}".replace("//", "/"), "ip_address": ip_address, "method": method, "cooldown": cooldown, } - + token = request.headers.get("Authorization") + headers = {"Authorization": token} if token else {} try: - response = httpx.post(AUTH_NOTIFY_ENDPOINT, json=payload, timeout=timeout) + response = requests.post(AUTH_NOTIFY_ENDPOINT, json=payload, headers=headers, timeout=timeout) response.raise_for_status() result = response.json() log.info(f"Notifikasi admin sent | Endpoint: {endpoint_name}") return result - except Exception as e: log.error(f"Error notifying admin: {str(e)}") return {"status": False, "message": str(e), "data": payload} diff --git a/src/config.py b/src/config.py index a71c4f7..f8a7896 100644 --- a/src/config.py +++ b/src/config.py @@ -92,4 +92,5 @@ API_KEY = config("API_KEY") TR_RBD_ID = config("TR_RBD_ID") TC_RBD_ID = config("TC_RBD_ID") -DEFAULT_TC_ID = config("DEFAULT_TC_ID") \ No newline at end of file +DEFAULT_TC_ID = config("DEFAULT_TC_ID") +RATELIMIT_STORAGE_URI = f"redis://{config('REDIS_HOST')}:{config('REDIS_PORT')}" \ No newline at end of file diff --git a/src/enums.py b/src/enums.py index 5a13be1..acd5551 100644 --- a/src/enums.py +++ b/src/enums.py @@ -1,4 +1,9 @@ -from enum import StrEnum +import sys + +if sys.version_info >= (3, 11): + from enum import StrEnum +else: + from strenum import StrEnum class OptimumOHEnum(StrEnum): diff --git a/src/exceptions.py b/src/exceptions.py index 2207b4b..c99b8c8 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -119,6 +119,7 @@ def handle_exception(request: Request, exc: Exception): endpoint_name=request_info["endpoint"], ip_address=request_info["remote_addr"], method=request_info["method"], + request=request, ) log.warning( diff --git a/src/rate_limiter.py b/src/rate_limiter.py index fb8c85c..669520e 100644 --- a/src/rate_limiter.py +++ b/src/rate_limiter.py @@ -1,8 +1,10 @@ from slowapi import Limiter from slowapi.util import get_remote_address +from src.config import RATELIMIT_STORAGE_URI limiter = Limiter( key_func=get_remote_address, - default_limits=["120 per hour", "2880 per day"], + default_limits=["1 per hour", "2880 per day"], strategy="fixed-window", -) + storage_uri=RATELIMIT_STORAGE_URI, +) \ No newline at end of file