From 0aa5eb1121b6ff612d3ac189def75b97c9e3163e Mon Sep 17 00:00:00 2001 From: Sam Date: Sat, 23 Dec 2023 09:58:01 +0530 Subject: [PATCH 1/7] additions to requirements.txt --- requirements.txt | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/requirements.txt b/requirements.txt index c4f7e2d..6dafb73 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,77 @@ annotated-types==0.6.0 anyio==3.7.1 +black==23.12.1 +blinker==1.7.0 +CacheControl==0.13.1 +certifi==2023.11.17 +cfgv==3.4.0 +charset-normalizer==3.3.2 +click==8.1.7 +dep-logic==0.0.4 +Deprecated==1.2.14 +distlib==0.3.8 +dnspython==2.4.2 +email-validator==2.1.0.post1 fastapi==0.104.1 +filelock==3.13.1 +findpython==0.4.1 +greenlet==3.0.2 +h11==0.14.0 +httpcore==1.0.2 +httptools==0.6.1 +httpx==0.25.2 +identify==2.5.33 idna==3.6 +importlib-resources==6.1.1 +iniconfig==2.0.0 +installer==0.7.0 +itsdangerous==2.1.2 Jinja2==3.1.2 +limits==3.7.0 +markdown-it-py==3.0.0 MarkupSafe==2.1.3 +mdurl==0.1.2 +mock==5.1.0 +msgpack==1.0.7 +mypy-extensions==1.0.0 +nodeenv==1.8.0 +orjson==3.9.10 +packaging==23.2 +pathspec==0.12.1 +pdm==2.11.1 +platformdirs==4.1.0 +pluggy==1.3.0 +pre-commit==3.6.0 pydantic==2.5.2 +pydantic-extra-types==2.1.0 +pydantic-settings==2.1.0 pydantic_core==2.14.5 +Pygments==2.17.2 +pyproject_hooks==1.0.0 +pytest==7.4.3 +python-dotenv==1.0.0 +python-multipart==0.0.6 +PyYAML==6.0.1 +redis==4.6.0 +requests==2.31.0 +requests-toolbelt==1.0.0 +resolvelib==1.0.1 +rich==13.7.0 +ruff==0.1.9 +shellingham==1.5.4 +slowapi==0.1.8 sniffio==1.3.0 +SQLAlchemy==2.0.23 starlette==0.27.0 +tomlkit==0.12.3 +truststore==0.8.0 typing_extensions==4.9.0 +ujson==5.8.0 +unearth==0.12.1 +urllib3==2.1.0 +uvicorn==0.24.0.post1 +uvloop==0.19.0 +virtualenv==20.25.0 +watchfiles==0.21.0 +websockets==12.0 +wrapt==1.16.0 From 982e7563524014fb8257a28f1dffaabb86c49d16 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 24 Dec 2023 22:50:31 +0530 Subject: [PATCH 2/7] devcontainer setup --- .devcontainer/devcontainer.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d039ff5 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,19 @@ +{ + "name": "paste.py", + "dockerfile": "Dockerfile", + "extensions": { + "python.pythonPath": "/usr/local/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": false, + "python.linting.flake8Enabled": true, + "python.linting.flake8Args": ["--max-line-length=88"], + "python.linting.mypyEnabled": true, + "python.formatting.provider": "black" + }, + "portsAttributes": { + "9000": { + "label": "paste.py remote", + "onAutoForward": "notify" + } + }, +} \ No newline at end of file From 5be4545a69d0d3af2e48204ae916e13bf64fe7f9 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 25 Dec 2023 00:09:00 +0530 Subject: [PATCH 3/7] added docker-compose.yaml and updated README.md --- README.md | 6 ++++++ docker-compose.yaml | 10 ++++++++++ 2 files changed, 16 insertions(+) create mode 100644 docker-compose.yaml diff --git a/README.md b/README.md index cb317b2..e0194ba 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,12 @@ pyenv install 3.11.3 docker run -d -p 8080:8080 --name pastepyprod mrsunglasses/pastepy ``` +- **Using docker-compose**: + You can also use docker-compose to run the project locally by running the following command: + ```bash + docker-compose up -d + ``` + ## Local setup 🛠️ without Docker 🐳 ### Setting Up the Project with PDM diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..afc3fdf --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,10 @@ +version: '3.8' + +services: + myapp: + build: + context: . + target: builder + ports: + - "8080:8080" + command: ["pdm", "run", "start"] From 1634d785404acc91c1f3407ede1191de7a9ce2ad Mon Sep 17 00:00:00 2001 From: vivekpal1 <31vivekpal@gmail.com> Date: Mon, 25 Dec 2023 02:50:00 +0530 Subject: [PATCH 4/7] fix: devcontainer config Co-authored-by: Sam Co-authored-by: vivekpal1 <31vivekpal@gmail.com> --- .devcontainer/devcontainer.json | 52 ++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d039ff5..7e5812c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,19 +1,49 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile { - "name": "paste.py", - "dockerfile": "Dockerfile", + "name": "Existing Dockerfile", + "build": { + // Sets the run context to one level up instead of the .devcontainer folder. + "context": "..", + // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. + "dockerfile": "../Dockerfile" + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": { + // "ghcr.io/devcontainers/features/python:1": {} + // }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [9000], + + // Uncomment the next line to run commands after the container is created. + // "postCreateCommand": "cat /etc/os-release", + + // Configure tool-specific properties. + "customizations": { "extensions": { - "python.pythonPath": "/usr/local/bin/python", - "python.linting.enabled": true, - "python.linting.pylintEnabled": false, - "python.linting.flake8Enabled": true, - "python.linting.flake8Args": ["--max-line-length=88"], - "python.linting.mypyEnabled": true, - "python.formatting.provider": "black" + "python.pythonPath": "/usr/local/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": false, + "python.linting.flake8Enabled": true, + "python.linting.flake8Args": ["--max-line-length=88"], + "python.linting.mypyEnabled": true, + "python.formatting.provider": "black" }, + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash" + } + }, + // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "devcontainer", + "portsAttributes": { "9000": { "label": "paste.py remote", "onAutoForward": "notify" } - }, -} \ No newline at end of file + } + } +} From 1dde9edb14cba2ae143a4c200b40944f07c8f767 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 25 Dec 2023 14:38:15 +0530 Subject: [PATCH 5/7] update readme --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index e0194ba..4c0c58e 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,22 @@ pyenv install 3.11.3 - **Using docker-compose**: You can also use docker-compose to run the project locally by running the following command: +
+ - **Clone the repository**: + Get the project source code from GitHub: + + ```bash + git clone https://github.com/FOSS-Community/paste.py.git + ``` + + - **Navigate to the Project Directory**: + + ```bash + cd paste.py + ``` + + - **Run the project using docker-compose**: + ```bash docker-compose up -d ``` From 484f070a6054c056718a1bcf7528ab9356008cf5 Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Thu, 28 Dec 2023 20:48:57 +0530 Subject: [PATCH 6/7] :art: chore: Refactor code --- src/paste/main.py | 55 ++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/src/paste/main.py b/src/paste/main.py index 1a9b3c6..7d0f226 100644 --- a/src/paste/main.py +++ b/src/paste/main.py @@ -24,24 +24,26 @@ BASE_DIR = Path(__file__).resolve().parent -templates = Jinja2Templates(directory=str(Path(BASE_DIR, 'templates'))) +templates = Jinja2Templates(directory=str(Path(BASE_DIR, "templates"))) @app.post("/file") -def post_as_a_file(file: UploadFile = File(...)): +async def post_as_a_file(file: UploadFile = File(...)): try: uuid = generate_uuid() if uuid in large_uuid_storage: uuid = generate_uuid() path = f"data/{uuid}" - with open(path, 'wb') as f: + with open(path, "wb") as f: shutil.copyfileobj(file.file, f) large_uuid_storage.append(uuid) print(large_uuid_storage) except Exception: # return {"message": "There was an error uploading the file"} - raise HTTPException(detail="There was an error uploading the file", - status_code=status.HTTP_403_FORBIDDEN) + raise HTTPException( + detail="There was an error uploading the file", + status_code=status.HTTP_403_FORBIDDEN, + ) finally: file.file.close() @@ -49,63 +51,68 @@ def post_as_a_file(file: UploadFile = File(...)): @app.get("/paste/{uuid}") -def post_as_a_text(uuid): +async def post_as_a_text(uuid): path = f"data/{uuid}" try: - with open(path, 'rb') as f: + with open(path, "rb") as f: return PlainTextResponse(f.read()) except Exception as e: print(e) - raise HTTPException(detail="404: The Requested Resource is not found", - status_code=status.HTTP_404_NOT_FOUND) + raise HTTPException( + detail="404: The Requested Resource is not found", + status_code=status.HTTP_404_NOT_FOUND, + ) @app.get("/", response_class=HTMLResponse) -def indexpage(request: Request): +async def indexpage(request: Request): return templates.TemplateResponse("index.html", {"request": request}) @app.delete("/paste/{uuid}", response_class=PlainTextResponse) -def delete_paste(uuid): +async def delete_paste(uuid): path = f"data/{uuid}" try: os.remove(path) return PlainTextResponse(f"File successfully deleted {uuid}") except FileNotFoundError: - raise HTTPException(detail="File Not Found", - status_code=status.HTTP_404_NOT_FOUND) + raise HTTPException( + detail="File Not Found", status_code=status.HTTP_404_NOT_FOUND + ) except Exception as e: raise HTTPException( - detail=f"The exception is {e}", status_code=status.HTTP_409_CONFLICT) + detail=f"The exception is {e}", status_code=status.HTTP_409_CONFLICT + ) @app.get("/web", response_class=HTMLResponse) -def web(request: Request): +async def web(request: Request): return templates.TemplateResponse("web.html", {"request": request}) @app.post("/web", response_class=PlainTextResponse) -def web_post(content: str = Form(...)): - # print(content) - # return PlainTextResponse(content=content) +async def web_post(content: str = Form(...)): try: file_content = content.encode() uuid = generate_uuid() if uuid in large_uuid_storage: uuid = generate_uuid() path = f"data/{uuid}" - with open(path, 'wb') as f: + with open(path, "wb") as f: f.write(file_content) large_uuid_storage.append(uuid) except Exception as e: - # return {"message": "There was an error uploading the file"} print(e) - raise HTTPException(detail="There was an error uploading the file", - status_code=status.HTTP_403_FORBIDDEN) + raise HTTPException( + detail="There was an error uploading the file", + status_code=status.HTTP_403_FORBIDDEN, + ) - return RedirectResponse(f"http://paste.fosscu.org/paste/{uuid}", status_code=status.HTTP_303_SEE_OTHER) + return RedirectResponse( + f"http://paste.fosscu.org/paste/{uuid}", status_code=status.HTTP_303_SEE_OTHER + ) @app.get("/health", status_code=status.HTTP_200_OK) -def health() -> dict[str, str]: +async def health() -> dict[str, str]: return {"status": "ok"} From 34f9ea88990bf95557a41993251cc4572ec38d2d Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Thu, 28 Dec 2023 21:15:45 +0530 Subject: [PATCH 7/7] :sparkles: Feat: added in-memory rate limit --- pdm.lock | 94 ++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + src/paste/main.py | 12 +++++- 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/pdm.lock b/pdm.lock index d84f957..55c54b4 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "test", "lint", "hooks"] strategy = ["cross_platform"] lock_version = "4.4.1" -content_hash = "sha256:48678eaab95f83b6f3a832ad1941efdeca00cc3e85ba24944e151a3fc37aed2e" +content_hash = "sha256:0c4f05f64ff605af4f759447bb09fee4fe68d0e786ab2b09ce8a32680cdfbbb7" [[package]] name = "annotated-types" @@ -155,6 +155,19 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "deprecated" +version = "1.2.14" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "Python @deprecated decorator to deprecate old python classes, functions or methods." +dependencies = [ + "wrapt<2,>=1.10", +] +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + [[package]] name = "distlib" version = "0.3.8" @@ -376,6 +389,16 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] +[[package]] +name = "importlib-resources" +version = "6.1.1" +requires_python = ">=3.8" +summary = "Read resources from Python packages" +files = [ + {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"}, + {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -409,6 +432,22 @@ files = [ {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] +[[package]] +name = "limits" +version = "3.7.0" +requires_python = ">=3.7" +summary = "Rate limiting utilities" +dependencies = [ + "deprecated>=1.2", + "importlib-resources>=1.3", + "packaging<24,>=21", + "typing-extensions", +] +files = [ + {file = "limits-3.7.0-py3-none-any.whl", hash = "sha256:c528817b7fc15f3e86ad091ba3e40231f6430a91b753db864767684cda8a7f2e"}, + {file = "limits-3.7.0.tar.gz", hash = "sha256:124c6a04d2f4b20990fb1de019eec9474d6c1346c70d8fd0561609b86998b64a"}, +] + [[package]] name = "markupsafe" version = "2.1.3" @@ -808,6 +847,19 @@ files = [ {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, ] +[[package]] +name = "slowapi" +version = "0.1.8" +requires_python = ">=3.7,<4.0" +summary = "A rate limiting extension for Starlette and Fastapi" +dependencies = [ + "limits>=2.3", +] +files = [ + {file = "slowapi-0.1.8-py3-none-any.whl", hash = "sha256:629fc415575bbffcd9d8621cc3ce326a78402c5f9b7b50b127979118d485c72e"}, + {file = "slowapi-0.1.8.tar.gz", hash = "sha256:8cc268f5a7e3624efa3f7bd2859b895f9f2376c4ed4e0378dd2f7f3343ca608e"}, +] + [[package]] name = "sniffio" version = "1.3.0" @@ -1145,3 +1197,43 @@ files = [ {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, ] + +[[package]] +name = "wrapt" +version = "1.16.0" +requires_python = ">=3.6" +summary = "Module for decorators, wrappers and monkey patching." +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] diff --git a/pyproject.toml b/pyproject.toml index 4342fab..6135a8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [ "fastapi[all]>=0.104.1", "sqlalchemy>=2.0.23", "jinja2>=3.1.2", + "slowapi>=0.1.8", ] requires-python = ">=3.10" readme = "README.md" diff --git a/src/paste/main.py b/src/paste/main.py index 7d0f226..b60bb1b 100644 --- a/src/paste/main.py +++ b/src/paste/main.py @@ -6,9 +6,15 @@ from fastapi import FastAPI from fastapi.templating import Jinja2Templates from fastapi.middleware.cors import CORSMiddleware +from slowapi.errors import RateLimitExceeded +from slowapi import Limiter, _rate_limit_exceeded_handler +from slowapi.util import get_remote_address from .utils import generate_uuid +limiter = Limiter(key_func=get_remote_address) app = FastAPI(title="paste.py 🐍") +app.state.limiter = limiter +app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) origins = ["*"] @@ -28,7 +34,8 @@ @app.post("/file") -async def post_as_a_file(file: UploadFile = File(...)): +@limiter.limit("100/minute") +async def post_as_a_file(request: Request, file: UploadFile = File(...)): try: uuid = generate_uuid() if uuid in large_uuid_storage: @@ -91,7 +98,8 @@ async def web(request: Request): @app.post("/web", response_class=PlainTextResponse) -async def web_post(content: str = Form(...)): +@limiter.limit("100/minute") +async def web_post(request: Request, content: str = Form(...)): try: file_content = content.encode() uuid = generate_uuid()