From c3f753a4f0f86d52cb03d749c601f75b9561a6b9 Mon Sep 17 00:00:00 2001 From: codey Date: Tue, 1 Jul 2025 11:38:38 -0400 Subject: [PATCH] add basic rate limiting --- base.py | 8 +++++++- endpoints/lastfm.py | 7 +++++-- endpoints/lyric_search.py | 4 +++- endpoints/meme.py | 13 +++++++++++-- endpoints/misc.py | 13 ++++++++++--- endpoints/radio.py | 17 ++++++++++++----- endpoints/rand_msg.py | 8 ++++++-- endpoints/transcriptions.py | 7 +++++-- endpoints/yt.py | 7 +++++-- 9 files changed, 64 insertions(+), 20 deletions(-) diff --git a/base.py b/base.py index c8c6dbc..ceeb62d 100644 --- a/base.py +++ b/base.py @@ -26,7 +26,13 @@ app = FastAPI( constants = importlib.import_module("constants").Constants() util = importlib.import_module("util").Utilities(app, constants) -origins = ["https://codey.lol", "https://old.codey.lol", "https://api.codey.lol", "https://_new.codey.lol", "http://localhost:4321"] +origins = [ + "https://codey.lol", + "https://old.codey.lol", + "https://api.codey.lol", + "https://_new.codey.lol", + "http://localhost:4321", +] app.add_middleware( CORSMiddleware, # type: ignore diff --git a/endpoints/lastfm.py b/endpoints/lastfm.py index 0fe3c56..46a20db 100644 --- a/endpoints/lastfm.py +++ b/endpoints/lastfm.py @@ -2,7 +2,8 @@ import importlib import logging import traceback from typing import Optional, Union -from fastapi import FastAPI +from fastapi import FastAPI, Depends +from fastapi_throttle import RateLimiter from fastapi.responses import JSONResponse from .constructors import ( ValidArtistSearchRequest, @@ -32,7 +33,9 @@ class LastFM(FastAPI): for endpoint, handler in self.endpoints.items(): app.add_api_route( - f"/{endpoint}", handler, methods=["POST"], include_in_schema=True + f"/{endpoint}", handler, + methods=["POST"], include_in_schema=True, + dependencies=[Depends(RateLimiter(times=2, seconds=2))] ) async def artist_by_name_handler( diff --git a/endpoints/lyric_search.py b/endpoints/lyric_search.py index 8f6bb39..9d9078c 100644 --- a/endpoints/lyric_search.py +++ b/endpoints/lyric_search.py @@ -3,7 +3,8 @@ import os import urllib.parse import regex import aiosqlite as sqlite3 -from fastapi import FastAPI, HTTPException +from fastapi import FastAPI, HTTPException, Depends +from fastapi_throttle import RateLimiter from fastapi.responses import JSONResponse from typing import LiteralString, Optional, Union, Iterable from regex import Pattern @@ -76,6 +77,7 @@ class LyricSearch(FastAPI): handler, methods=["POST"], include_in_schema=_schema_include, + dependencies=[Depends(RateLimiter(times=2, seconds=3))] ) async def typeahead_handler(self, data: ValidTypeAheadRequest) -> JSONResponse: diff --git a/endpoints/meme.py b/endpoints/meme.py index 184e0ce..27d0ae0 100644 --- a/endpoints/meme.py +++ b/endpoints/meme.py @@ -1,5 +1,10 @@ import logging -from fastapi import FastAPI, Request, Response +from fastapi import ( + FastAPI, + Request, + Response, + Depends) +from fastapi_throttle import RateLimiter from fastapi.responses import JSONResponse from utils.meme_util import MemeUtil @@ -22,7 +27,11 @@ class Meme(FastAPI): for endpoint, handler in self.endpoints.items(): app.add_api_route( - f"/{endpoint}", handler, methods=["GET"], include_in_schema=True + f"/{endpoint}", handler, + methods=["GET"], include_in_schema=True, + dependencies=[Depends( + RateLimiter(times=2, seconds=2) + )] ) async def get_meme_by_id(self, id: int, request: Request) -> Response: diff --git a/endpoints/misc.py b/endpoints/misc.py index 304c894..50dfb56 100644 --- a/endpoints/misc.py +++ b/endpoints/misc.py @@ -4,7 +4,8 @@ import os import json import random from typing import Optional, Annotated -from fastapi import FastAPI, Request, UploadFile, Response, HTTPException, Form +from fastapi import FastAPI, Request, UploadFile, Response, HTTPException, Form, Depends +from fastapi_throttle import RateLimiter from fastapi.responses import JSONResponse import redis.asyncio as redis from lyric_search.sources import private, cache as LyricsCache, redis_cache @@ -40,11 +41,16 @@ class Misc(FastAPI): for endpoint, handler in self.endpoints.items(): app.add_api_route( - f"/{endpoint}", handler, methods=["GET"], include_in_schema=True + f"/{endpoint}", + handler, + methods=["GET"], + include_in_schema=True, + dependencies=[Depends(RateLimiter(times=2, seconds=5))], ) app.add_api_route( - "/misc/upload_activity_image", self.upload_activity_image, methods=["POST"] + "/misc/upload_activity_image", self.upload_activity_image, methods=["POST"], + dependencies=[Depends(RateLimiter(times=2, seconds=5))], ) logging.debug("Loading NaaS reasons") @@ -66,6 +72,7 @@ class Misc(FastAPI): except Exception as e: logging.debug("Exception: %s", str(e)) return "No." + async def no(self) -> JSONResponse: """NaaS""" diff --git a/endpoints/radio.py b/endpoints/radio.py index 171d501..b8185ff 100644 --- a/endpoints/radio.py +++ b/endpoints/radio.py @@ -13,8 +13,14 @@ from .constructors import ( ) from utils import radio_util from typing import Optional -from fastapi import FastAPI, BackgroundTasks, Request, Response, HTTPException -from starlette.concurrency import run_in_threadpool +from fastapi import ( + FastAPI, + BackgroundTasks, + Request, + Response, + HTTPException, + Depends) +from fastapi_throttle import RateLimiter from fastapi.responses import RedirectResponse, JSONResponse @@ -43,7 +49,8 @@ class Radio(FastAPI): for endpoint, handler in self.endpoints.items(): app.add_api_route( - f"/{endpoint}", handler, methods=["POST"], include_in_schema=True + f"/{endpoint}", handler, methods=["POST"], include_in_schema=True, + dependencies=[Depends(RateLimiter(times=10, seconds=5))], ) # NOTE: Not in loop because method is GET for this endpoint @@ -58,7 +65,7 @@ class Radio(FastAPI): async def on_start(self) -> None: logging.info("radio: Initializing") - await run_in_threadpool(self.radio_util.load_playlist) + self.loop.run_in_executor(None, self.radio_util.load_playlist) async def radio_skip( self, data: ValidRadioNextRequest, request: Request @@ -317,7 +324,7 @@ class Radio(FastAPI): if len(self.radio_util.active_playlist) > 1: self.radio_util.active_playlist.append(next) # Push to end of playlist else: - await run_in_threadpool(self.radio_util.load_playlist) + self.loop.run_in_executor(None, self.radio_util.load_playlist) self.radio_util.now_playing = next next["start"] = time_started diff --git a/endpoints/rand_msg.py b/endpoints/rand_msg.py index 64387a9..15a729f 100644 --- a/endpoints/rand_msg.py +++ b/endpoints/rand_msg.py @@ -2,7 +2,8 @@ import os import random from typing import LiteralString, Optional, Union import aiosqlite as sqlite3 -from fastapi import FastAPI +from fastapi import FastAPI, Depends +from fastapi_throttle import RateLimiter from fastapi.responses import JSONResponse from .constructors import RandMsgRequest @@ -19,7 +20,10 @@ class RandMsg(FastAPI): self.endpoint_name = "randmsg" app.add_api_route( - f"/{self.endpoint_name}", self.randmsg_handler, methods=["POST"] + f"/{self.endpoint_name}", self.randmsg_handler, + methods=["POST"], dependencies=[Depends( + RateLimiter(times=5, seconds=2) + )] ) async def randmsg_handler( diff --git a/endpoints/transcriptions.py b/endpoints/transcriptions.py index 86e9574..9c740d8 100644 --- a/endpoints/transcriptions.py +++ b/endpoints/transcriptions.py @@ -1,6 +1,7 @@ import os import aiosqlite as sqlite3 -from fastapi import FastAPI +from fastapi import FastAPI, Depends +from fastapi_throttle import RateLimiter from fastapi.responses import JSONResponse from typing import Optional, LiteralString, Union from .constructors import ValidShowEpisodeLineRequest, ValidShowEpisodeListRequest @@ -24,7 +25,9 @@ class Transcriptions(FastAPI): for endpoint, handler in self.endpoints.items(): app.add_api_route( - f"/{endpoint}", handler, methods=["POST"], include_in_schema=True + f"/{endpoint}", handler, + methods=["POST"], include_in_schema=True, + dependencies=[Depends(RateLimiter(times=2, seconds=2))] ) async def get_episodes_handler( diff --git a/endpoints/yt.py b/endpoints/yt.py index bb25dc0..a4b870e 100644 --- a/endpoints/yt.py +++ b/endpoints/yt.py @@ -1,6 +1,7 @@ import importlib -from fastapi import FastAPI +from fastapi import FastAPI, Depends from fastapi.responses import JSONResponse +from fastapi_throttle import RateLimiter from typing import Optional, Union from .constructors import ValidYTSearchRequest @@ -22,7 +23,9 @@ class YT(FastAPI): for endpoint, handler in self.endpoints.items(): app.add_api_route( - f"/{endpoint}", handler, methods=["POST"], include_in_schema=True + f"/{endpoint}", handler, + methods=["POST"], include_in_schema=True, + dependencies=[Depends(RateLimiter(times=2, seconds=2))] ) async def yt_video_search_handler(self, data: ValidYTSearchRequest) -> JSONResponse: