From b23bd9c4793a977bbca6c98e75aa9c9e9215c8a4 Mon Sep 17 00:00:00 2001 From: Aarushi <50577581+aarushik93@users.noreply.github.com> Date: Thu, 1 Aug 2024 11:11:40 +0100 Subject: [PATCH] feat(server): Add JWT validation (#7642) * add auth middleware * update readnme * Update rnd/autogpt_server/pyproject.toml Co-authored-by: Krzysztof Czerwinski <34861343+kcze@users.noreply.github.com> * address newline feedback * update poetry lock --------- Co-authored-by: Krzysztof Czerwinski <34861343+kcze@users.noreply.github.com> --- rnd/__init__.py | 0 rnd/autogpt_libs/README.md | 3 ++ rnd/autogpt_libs/autogpt_libs/__init__.py | 0 .../autogpt_libs/auth/__init__.py | 0 rnd/autogpt_libs/autogpt_libs/auth/config.py | 16 +++++++ .../autogpt_libs/auth/jwt_utils.py | 20 +++++++++ .../autogpt_libs/auth/middleware.py | 26 +++++++++++ rnd/autogpt_libs/poetry.lock | 37 ++++++++++++++++ rnd/autogpt_libs/pyproject.toml | 17 ++++++++ rnd/autogpt_server/.env.example | 1 + .../autogpt_server/server/server.py | 4 ++ rnd/autogpt_server/poetry.lock | 43 +++++++++++++++++-- rnd/autogpt_server/pyproject.toml | 1 + 13 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 rnd/__init__.py create mode 100644 rnd/autogpt_libs/README.md create mode 100644 rnd/autogpt_libs/autogpt_libs/__init__.py create mode 100644 rnd/autogpt_libs/autogpt_libs/auth/__init__.py create mode 100644 rnd/autogpt_libs/autogpt_libs/auth/config.py create mode 100644 rnd/autogpt_libs/autogpt_libs/auth/jwt_utils.py create mode 100644 rnd/autogpt_libs/autogpt_libs/auth/middleware.py create mode 100644 rnd/autogpt_libs/poetry.lock create mode 100644 rnd/autogpt_libs/pyproject.toml diff --git a/rnd/__init__.py b/rnd/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rnd/autogpt_libs/README.md b/rnd/autogpt_libs/README.md new file mode 100644 index 000000000..e2d6a5e67 --- /dev/null +++ b/rnd/autogpt_libs/README.md @@ -0,0 +1,3 @@ +# AutoGPT Libs + +This is a new project to store shared functionality across different services in NextGen AutoGPT (e.g. authentication) diff --git a/rnd/autogpt_libs/autogpt_libs/__init__.py b/rnd/autogpt_libs/autogpt_libs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rnd/autogpt_libs/autogpt_libs/auth/__init__.py b/rnd/autogpt_libs/autogpt_libs/auth/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rnd/autogpt_libs/autogpt_libs/auth/config.py b/rnd/autogpt_libs/autogpt_libs/auth/config.py new file mode 100644 index 000000000..71e298451 --- /dev/null +++ b/rnd/autogpt_libs/autogpt_libs/auth/config.py @@ -0,0 +1,16 @@ +import os +from dotenv import load_dotenv + +load_dotenv() + +class Settings: + JWT_SECRET_KEY: str = os.getenv("SUPABASE_JWT_SECRET", "") + ENABLE_AUTH: bool = os.getenv("ENABLE_AUTH", "false").lower() == "true" + JWT_ALGORITHM: str = "HS256" + + @property + def is_configured(self) -> bool: + return bool(self.JWT_SECRET_KEY) + + +settings = Settings() diff --git a/rnd/autogpt_libs/autogpt_libs/auth/jwt_utils.py b/rnd/autogpt_libs/autogpt_libs/auth/jwt_utils.py new file mode 100644 index 000000000..ae414ae80 --- /dev/null +++ b/rnd/autogpt_libs/autogpt_libs/auth/jwt_utils.py @@ -0,0 +1,20 @@ +import jwt +from typing import Dict, Any +from .config import settings + + +def parse_jwt_token(token: str) -> Dict[str, Any]: + """ + Parse and validate a JWT token. + + :param token: The token to parse + :return: The decoded payload + :raises ValueError: If the token is invalid or expired + """ + try: + payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM]) + return payload + except jwt.ExpiredSignatureError: + raise ValueError("Token has expired") + except jwt.InvalidTokenError as e: + raise ValueError(f"Invalid token: {str(e)}") diff --git a/rnd/autogpt_libs/autogpt_libs/auth/middleware.py b/rnd/autogpt_libs/autogpt_libs/auth/middleware.py new file mode 100644 index 000000000..f474d5527 --- /dev/null +++ b/rnd/autogpt_libs/autogpt_libs/auth/middleware.py @@ -0,0 +1,26 @@ +import logging + +from fastapi import Request, HTTPException, Depends +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from .jwt_utils import parse_jwt_token +from .config import settings + +security = HTTPBearer() +async def auth_middleware(request: Request): + if not settings.ENABLE_AUTH: + # If authentication is disabled, allow the request to proceed + return {} + + security = HTTPBearer() + credentials = await security(request) + + if not credentials: + raise HTTPException(status_code=401, detail="Authorization header is missing") + + try: + payload = parse_jwt_token(credentials.credentials) + request.state.user = payload + logging.info("Token decoded successfully") + except ValueError as e: + raise HTTPException(status_code=401, detail=str(e)) + return payload diff --git a/rnd/autogpt_libs/poetry.lock b/rnd/autogpt_libs/poetry.lock new file mode 100644 index 000000000..4138164a9 --- /dev/null +++ b/rnd/autogpt_libs/poetry.lock @@ -0,0 +1,37 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.10,<4.0" +content-hash = "7030c59d6f7c40f49ee64eb60dccc8640b35a276617f9351fb2b93d382c7113d" diff --git a/rnd/autogpt_libs/pyproject.toml b/rnd/autogpt_libs/pyproject.toml new file mode 100644 index 000000000..1bc9b9ce0 --- /dev/null +++ b/rnd/autogpt_libs/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "autogpt-libs" +version = "0.1.0" +description = "Shared libraries across NextGen AutoGPT" +authors = ["Aarushi "] +readme = "README.md" +packages = [{ include = "autogpt_libs" }] + +[tool.poetry.dependencies] +python = ">=3.10,<4.0" +pyjwt = "^2.8.0" +python-dotenv = "^1.0.1" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/rnd/autogpt_server/.env.example b/rnd/autogpt_server/.env.example index d8b201297..a2db41209 100644 --- a/rnd/autogpt_server/.env.example +++ b/rnd/autogpt_server/.env.example @@ -4,3 +4,4 @@ DB_NAME=agpt_local DB_PORT=5432 DATABASE_URL="postgresql://${DB_USER}:${DB_PASS}@localhost:${DB_PORT}/${DB_NAME}" PRISMA_SCHEMA="postgres/schema.prisma" +ENABLE_AUTH="false" diff --git a/rnd/autogpt_server/autogpt_server/server/server.py b/rnd/autogpt_server/autogpt_server/server/server.py index 31d5afaaa..06fd7f072 100644 --- a/rnd/autogpt_server/autogpt_server/server/server.py +++ b/rnd/autogpt_server/autogpt_server/server/server.py @@ -5,9 +5,11 @@ from contextlib import asynccontextmanager from typing import Annotated, Any, Dict import uvicorn +from autogpt_libs.auth.middleware import auth_middleware from fastapi import ( APIRouter, Body, + Depends, FastAPI, HTTPException, WebSocket, @@ -80,6 +82,8 @@ class AgentServer(AppService): # Define the API routes router = APIRouter(prefix="/api") + router.dependencies.append(Depends(auth_middleware)) + router.add_api_route( path="/blocks", endpoint=self.get_graph_blocks, # type: ignore diff --git a/rnd/autogpt_server/poetry.lock b/rnd/autogpt_server/poetry.lock index 92818096b..3df327fdf 100644 --- a/rnd/autogpt_server/poetry.lock +++ b/rnd/autogpt_server/poetry.lock @@ -25,7 +25,7 @@ requests = "*" sentry-sdk = "^1.40.4" [package.extras] -benchmark = ["agbenchmark"] +benchmark = ["agbenchmark @ file:///Users/aarushi/autogpt/AutoGPT/benchmark"] [package.source] type = "directory" @@ -329,12 +329,29 @@ watchdog = "4.0.0" webdriver-manager = "^4.0.1" [package.extras] -benchmark = ["agbenchmark"] +benchmark = ["agbenchmark @ file:///Users/aarushi/autogpt/AutoGPT/benchmark"] [package.source] type = "directory" url = "../../forge" +[[package]] +name = "autogpt-libs" +version = "0.1.0" +description = "Shared libraries across NextGen AutoGPT" +optional = false +python-versions = ">=3.10,<4.0" +files = [] +develop = true + +[package.dependencies] +pyjwt = "^2.8.0" +python-dotenv = "^1.0.1" + +[package.source] +type = "directory" +url = "../autogpt_libs" + [[package]] name = "backoff" version = "2.2.1" @@ -2383,6 +2400,9 @@ files = [ {file = "lief-0.14.1-cp312-cp312-manylinux_2_28_x86_64.manylinux_2_27_x86_64.whl", hash = "sha256:497b88f9c9aaae999766ba188744ee35c5f38b4b64016f7dbb7037e9bf325382"}, {file = "lief-0.14.1-cp312-cp312-win32.whl", hash = "sha256:08bad88083f696915f8dcda4042a3bfc514e17462924ec8984085838b2261921"}, {file = "lief-0.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:e131d6158a085f8a72124136816fefc29405c725cd3695ce22a904e471f0f815"}, + {file = "lief-0.14.1-cp313-cp313-manylinux_2_28_x86_64.manylinux_2_27_x86_64.whl", hash = "sha256:f9ff9a6959fb6d0e553cca41cd1027b609d27c5073e98d9fad8b774fbb5746c2"}, + {file = "lief-0.14.1-cp313-cp313-win32.whl", hash = "sha256:95f295a7cc68f4e14ce7ea4ff8082a04f5313c2e5e63cc2bbe9d059190b7e4d5"}, + {file = "lief-0.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:cdc1123c2e27970f8c8353505fd578e634ab33193c8d1dff36dc159e25599a40"}, {file = "lief-0.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:df650fa05ca131e4dfeb42c77985e1eb239730af9944bc0aadb1dfac8576e0e8"}, {file = "lief-0.14.1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:b4e76eeb48ca2925c6ca6034d408582615f2faa855f9bb11482e7acbdecc4803"}, {file = "lief-0.14.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:016e4fac91303466024154dd3c4b599e8b7c52882f72038b62a2be386d98c8f9"}, @@ -4170,6 +4190,23 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + [[package]] name = "pylatexenc" version = "2.10" @@ -6362,4 +6399,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "9013ff78344cb68878809bd7220453879c32c9291e39d99321dbcc9a7359855c" +content-hash = "b9a68db94e5721bfc916e583626e6da66a3469451d179bf9ee117d0a31b865f2" diff --git a/rnd/autogpt_server/pyproject.toml b/rnd/autogpt_server/pyproject.toml index 26f6f22cf..f996cc5c8 100644 --- a/rnd/autogpt_server/pyproject.toml +++ b/rnd/autogpt_server/pyproject.toml @@ -40,6 +40,7 @@ feedparser = "^6.0.11" python-dotenv = "^1.0.1" expiringdict = "^1.2.2" +autogpt-libs = { path = "../autogpt_libs", develop = true } [tool.poetry.group.dev.dependencies] cx-freeze = { git = "https://github.com/ntindle/cx_Freeze.git", rev = "main", develop = true } poethepoet = "^0.26.1"