agno 2.0.1__py3-none-any.whl → 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agno/agent/agent.py +6015 -2823
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +68 -1
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +594 -186
- agno/knowledge/reader/base.py +9 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +2 -8
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +72 -0
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +999 -519
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +103 -31
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +139 -0
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +59 -5
- agno/models/openai/chat.py +69 -29
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +77 -1
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -178
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +248 -94
- agno/run/base.py +44 -5
- agno/run/team.py +238 -97
- agno/run/workflow.py +144 -33
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1610
- agno/tools/dalle.py +2 -4
- agno/tools/decorator.py +4 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +14 -7
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +250 -30
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +217 -2
- agno/utils/gemini.py +180 -22
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +124 -133
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +638 -129
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +61 -23
- agno/workflow/workflow.py +2085 -272
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
- agno-2.3.0.dist-info/RECORD +577 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.1.dist-info/RECORD +0 -515
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import fnmatch
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from os import getenv
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
import jwt
|
|
7
|
+
from fastapi import Request, Response
|
|
8
|
+
from fastapi.responses import JSONResponse
|
|
9
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
10
|
+
|
|
11
|
+
from agno.utils.log import log_debug
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TokenSource(str, Enum):
|
|
15
|
+
"""Enum for JWT token source options."""
|
|
16
|
+
|
|
17
|
+
HEADER = "header"
|
|
18
|
+
COOKIE = "cookie"
|
|
19
|
+
BOTH = "both" # Try header first, then cookie
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class JWTMiddleware(BaseHTTPMiddleware):
|
|
23
|
+
"""
|
|
24
|
+
JWT Middleware for validating tokens and storing JWT claims in request state.
|
|
25
|
+
|
|
26
|
+
This middleware:
|
|
27
|
+
1. Extracts JWT token from Authorization header, cookies, or both
|
|
28
|
+
2. Decodes and validates the token
|
|
29
|
+
3. Stores JWT claims in request.state for easy access in endpoints
|
|
30
|
+
|
|
31
|
+
Token Sources:
|
|
32
|
+
- "header": Extract from Authorization header (default)
|
|
33
|
+
- "cookie": Extract from HTTP cookie
|
|
34
|
+
- "both": Try header first, then cookie as fallback
|
|
35
|
+
|
|
36
|
+
Claims are stored as:
|
|
37
|
+
- request.state.user_id: User ID from configured claim
|
|
38
|
+
- request.state.session_id: Session ID from configured claim
|
|
39
|
+
- request.state.dependencies: Dictionary of dependency claims
|
|
40
|
+
- request.state.session_state: Dictionary of session state claims
|
|
41
|
+
- request.state.authenticated: Boolean authentication status
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
app,
|
|
48
|
+
secret_key: Optional[str] = None,
|
|
49
|
+
algorithm: str = "HS256",
|
|
50
|
+
token_source: TokenSource = TokenSource.HEADER,
|
|
51
|
+
token_header_key: str = "Authorization",
|
|
52
|
+
cookie_name: str = "access_token",
|
|
53
|
+
validate: bool = True,
|
|
54
|
+
excluded_route_paths: Optional[List[str]] = None,
|
|
55
|
+
scopes_claim: Optional[str] = None,
|
|
56
|
+
user_id_claim: str = "sub",
|
|
57
|
+
session_id_claim: str = "session_id",
|
|
58
|
+
dependencies_claims: Optional[List[str]] = None,
|
|
59
|
+
session_state_claims: Optional[List[str]] = None,
|
|
60
|
+
):
|
|
61
|
+
"""
|
|
62
|
+
Initialize the JWT middleware.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
app: The FastAPI app instance
|
|
66
|
+
secret_key: The secret key to use for JWT validation (optional, will use JWT_SECRET_KEY environment variable if not provided)
|
|
67
|
+
algorithm: The algorithm to use for JWT validation
|
|
68
|
+
token_header_key: The key to use for the Authorization header (only used when token_source is header)
|
|
69
|
+
token_source: Where to extract the JWT token from (header, cookie, or both)
|
|
70
|
+
cookie_name: The name of the cookie containing the JWT token (only used when token_source is cookie/both)
|
|
71
|
+
validate: Whether to validate the JWT token
|
|
72
|
+
excluded_route_paths: A list of route paths to exclude from JWT validation
|
|
73
|
+
scopes_claim: The claim to use for scopes extraction
|
|
74
|
+
user_id_claim: The claim to use for user ID extraction
|
|
75
|
+
session_id_claim: The claim to use for session ID extraction
|
|
76
|
+
dependencies_claims: A list of claims to extract from the JWT token for dependencies
|
|
77
|
+
session_state_claims: A list of claims to extract from the JWT token for session state
|
|
78
|
+
"""
|
|
79
|
+
super().__init__(app)
|
|
80
|
+
self.secret_key = secret_key or getenv("JWT_SECRET_KEY")
|
|
81
|
+
if not self.secret_key:
|
|
82
|
+
raise ValueError("Secret key is required")
|
|
83
|
+
self.algorithm = algorithm
|
|
84
|
+
self.token_header_key = token_header_key
|
|
85
|
+
self.token_source = token_source
|
|
86
|
+
self.cookie_name = cookie_name
|
|
87
|
+
self.validate = validate
|
|
88
|
+
self.excluded_route_paths = excluded_route_paths
|
|
89
|
+
self.scopes_claim = scopes_claim
|
|
90
|
+
self.user_id_claim = user_id_claim
|
|
91
|
+
self.session_id_claim = session_id_claim
|
|
92
|
+
self.dependencies_claims = dependencies_claims or []
|
|
93
|
+
self.session_state_claims = session_state_claims or []
|
|
94
|
+
|
|
95
|
+
def _extract_token_from_header(self, request: Request) -> Optional[str]:
|
|
96
|
+
"""Extract JWT token from Authorization header."""
|
|
97
|
+
authorization = request.headers.get(self.token_header_key, "")
|
|
98
|
+
if not authorization:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
# Remove the "Bearer " prefix (if present)
|
|
103
|
+
_, token = authorization.split(" ", 1)
|
|
104
|
+
return token
|
|
105
|
+
except ValueError:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
def _extract_token_from_cookie(self, request: Request) -> Optional[str]:
|
|
109
|
+
"""Extract JWT token from cookie."""
|
|
110
|
+
return request.cookies.get(self.cookie_name)
|
|
111
|
+
|
|
112
|
+
def _extract_token(self, request: Request) -> Optional[str]:
|
|
113
|
+
"""Extract JWT token based on configured token source."""
|
|
114
|
+
if self.token_source == TokenSource.HEADER:
|
|
115
|
+
return self._extract_token_from_header(request)
|
|
116
|
+
elif self.token_source == TokenSource.COOKIE:
|
|
117
|
+
return self._extract_token_from_cookie(request)
|
|
118
|
+
elif self.token_source == TokenSource.BOTH:
|
|
119
|
+
# Try header first, then cookie
|
|
120
|
+
token = self._extract_token_from_header(request)
|
|
121
|
+
if token is None:
|
|
122
|
+
token = self._extract_token_from_cookie(request)
|
|
123
|
+
return token
|
|
124
|
+
else:
|
|
125
|
+
log_debug(f"Unknown token source: {self.token_source}")
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
def _get_missing_token_error_message(self) -> str:
|
|
129
|
+
"""Get appropriate error message for missing token based on token source."""
|
|
130
|
+
if self.token_source == TokenSource.HEADER:
|
|
131
|
+
return "Authorization header missing"
|
|
132
|
+
elif self.token_source == TokenSource.COOKIE:
|
|
133
|
+
return f"JWT cookie '{self.cookie_name}' missing"
|
|
134
|
+
elif self.token_source == TokenSource.BOTH:
|
|
135
|
+
return f"JWT token missing from both Authorization header and '{self.cookie_name}' cookie"
|
|
136
|
+
else:
|
|
137
|
+
return "JWT token missing"
|
|
138
|
+
|
|
139
|
+
def _is_route_excluded(self, path: str) -> bool:
|
|
140
|
+
"""Check if a route path matches any of the excluded patterns."""
|
|
141
|
+
if not self.excluded_route_paths:
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
for excluded_path in self.excluded_route_paths:
|
|
145
|
+
# Support both exact matches and wildcard patterns
|
|
146
|
+
if fnmatch.fnmatch(path, excluded_path):
|
|
147
|
+
return True
|
|
148
|
+
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
async def dispatch(self, request: Request, call_next) -> Response:
|
|
152
|
+
if self._is_route_excluded(request.url.path):
|
|
153
|
+
return await call_next(request)
|
|
154
|
+
|
|
155
|
+
# Extract JWT token from configured source (header, cookie, or both)
|
|
156
|
+
token = self._extract_token(request)
|
|
157
|
+
|
|
158
|
+
if not token:
|
|
159
|
+
if self.validate:
|
|
160
|
+
error_msg = self._get_missing_token_error_message()
|
|
161
|
+
return JSONResponse(status_code=401, content={"detail": error_msg})
|
|
162
|
+
return await call_next(request)
|
|
163
|
+
|
|
164
|
+
# Decode JWT token
|
|
165
|
+
try:
|
|
166
|
+
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm]) # type: ignore
|
|
167
|
+
|
|
168
|
+
# Extract scopes claims
|
|
169
|
+
scopes = []
|
|
170
|
+
if self.scopes_claim in payload:
|
|
171
|
+
extracted_scopes = payload[self.scopes_claim]
|
|
172
|
+
if isinstance(extracted_scopes, str):
|
|
173
|
+
scopes = extracted_scopes.split(" ")
|
|
174
|
+
else:
|
|
175
|
+
scopes = extracted_scopes
|
|
176
|
+
if scopes:
|
|
177
|
+
request.state.scopes = scopes
|
|
178
|
+
|
|
179
|
+
# Extract user information
|
|
180
|
+
if self.user_id_claim in payload:
|
|
181
|
+
user_id = payload[self.user_id_claim]
|
|
182
|
+
request.state.user_id = user_id
|
|
183
|
+
if self.session_id_claim in payload:
|
|
184
|
+
session_id = payload[self.session_id_claim]
|
|
185
|
+
request.state.session_id = session_id
|
|
186
|
+
else:
|
|
187
|
+
session_id = None
|
|
188
|
+
|
|
189
|
+
# Extract dependency claims
|
|
190
|
+
dependencies = {}
|
|
191
|
+
for claim in self.dependencies_claims:
|
|
192
|
+
if claim in payload:
|
|
193
|
+
dependencies[claim] = payload[claim]
|
|
194
|
+
|
|
195
|
+
if dependencies:
|
|
196
|
+
request.state.dependencies = dependencies
|
|
197
|
+
|
|
198
|
+
# Extract session state claims
|
|
199
|
+
session_state = {}
|
|
200
|
+
for claim in self.session_state_claims:
|
|
201
|
+
if claim in payload:
|
|
202
|
+
session_state[claim] = payload[claim]
|
|
203
|
+
|
|
204
|
+
if session_state:
|
|
205
|
+
request.state.session_state = session_state
|
|
206
|
+
|
|
207
|
+
request.state.token = token
|
|
208
|
+
request.state.authenticated = True
|
|
209
|
+
|
|
210
|
+
log_debug(f"JWT decoded successfully for user: {user_id}")
|
|
211
|
+
if dependencies:
|
|
212
|
+
log_debug(f"Extracted dependencies: {dependencies}")
|
|
213
|
+
if session_state:
|
|
214
|
+
log_debug(f"Extracted session state: {session_state}")
|
|
215
|
+
|
|
216
|
+
except jwt.ExpiredSignatureError:
|
|
217
|
+
if self.validate:
|
|
218
|
+
return JSONResponse(status_code=401, content={"detail": "Token has expired"})
|
|
219
|
+
request.state.authenticated = False
|
|
220
|
+
request.state.token = token
|
|
221
|
+
|
|
222
|
+
except jwt.InvalidTokenError as e:
|
|
223
|
+
if self.validate:
|
|
224
|
+
return JSONResponse(status_code=401, content={"detail": f"Invalid token: {str(e)}"})
|
|
225
|
+
request.state.authenticated = False
|
|
226
|
+
request.state.token = token
|
|
227
|
+
except Exception as e:
|
|
228
|
+
if self.validate:
|
|
229
|
+
return JSONResponse(status_code=401, content={"detail": f"Error decoding token: {str(e)}"})
|
|
230
|
+
request.state.authenticated = False
|
|
231
|
+
request.state.token = token
|
|
232
|
+
|
|
233
|
+
return await call_next(request)
|