agno 2.0.11__py3-none-any.whl → 2.1.1__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 +607 -176
- agno/db/in_memory/in_memory_db.py +42 -29
- agno/db/mongo/mongo.py +65 -66
- agno/db/postgres/postgres.py +6 -4
- agno/db/utils.py +50 -22
- agno/exceptions.py +62 -1
- 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 +51 -0
- 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/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/ollama.py +5 -0
- agno/knowledge/embedder/openai.py +18 -54
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +11 -4
- agno/knowledge/reader/pdf_reader.py +4 -3
- agno/knowledge/reader/website_reader.py +3 -2
- agno/models/base.py +125 -32
- agno/models/cerebras/cerebras.py +1 -0
- agno/models/cerebras/cerebras_openai.py +1 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/google/gemini.py +27 -5
- agno/models/openai/chat.py +13 -4
- agno/models/openai/responses.py +1 -1
- agno/models/perplexity/perplexity.py +2 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +49 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +1 -0
- agno/os/app.py +98 -126
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/agui/agui.py +21 -5
- agno/os/interfaces/base.py +4 -2
- agno/os/interfaces/slack/slack.py +13 -8
- agno/os/interfaces/whatsapp/router.py +2 -0
- agno/os/interfaces/whatsapp/whatsapp.py +12 -5
- agno/os/mcp.py +2 -2
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +182 -46
- agno/os/routers/home.py +2 -2
- agno/os/routers/memory/memory.py +23 -1
- agno/os/routers/memory/schemas.py +1 -1
- agno/os/routers/session/session.py +20 -3
- agno/os/utils.py +74 -8
- agno/run/agent.py +120 -77
- agno/run/base.py +2 -13
- agno/run/team.py +115 -72
- agno/run/workflow.py +5 -15
- agno/session/summary.py +9 -10
- agno/session/team.py +2 -1
- agno/team/team.py +721 -169
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +42 -2
- agno/tools/knowledge.py +3 -3
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/spider.py +2 -2
- agno/tools/workflow.py +4 -5
- agno/utils/events.py +66 -1
- agno/utils/hooks.py +57 -0
- agno/utils/media.py +11 -9
- agno/utils/print_response/agent.py +43 -5
- agno/utils/print_response/team.py +48 -12
- agno/utils/serialize.py +32 -0
- agno/vectordb/cassandra/cassandra.py +44 -4
- agno/vectordb/chroma/chromadb.py +79 -8
- agno/vectordb/clickhouse/clickhousedb.py +43 -6
- agno/vectordb/couchbase/couchbase.py +76 -5
- agno/vectordb/lancedb/lance_db.py +38 -3
- agno/vectordb/milvus/milvus.py +76 -4
- agno/vectordb/mongodb/mongodb.py +76 -4
- agno/vectordb/pgvector/pgvector.py +50 -6
- agno/vectordb/pineconedb/pineconedb.py +39 -2
- agno/vectordb/qdrant/qdrant.py +76 -26
- agno/vectordb/singlestore/singlestore.py +77 -4
- agno/vectordb/upstashdb/upstashdb.py +42 -2
- agno/vectordb/weaviate/weaviate.py +39 -3
- agno/workflow/types.py +5 -6
- agno/workflow/workflow.py +58 -2
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/METADATA +4 -3
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/RECORD +93 -82
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/WHEEL +0 -0
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/top_level.txt +0 -0
agno/os/interfaces/agui/agui.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Main class for the AG-UI app, used to expose an Agno Agent or Team in an AG-UI compatible format."""
|
|
2
2
|
|
|
3
|
-
from typing import Optional
|
|
3
|
+
from typing import List, Optional
|
|
4
4
|
|
|
5
5
|
from fastapi.routing import APIRouter
|
|
6
6
|
|
|
@@ -15,16 +15,32 @@ class AGUI(BaseInterface):
|
|
|
15
15
|
|
|
16
16
|
router: APIRouter
|
|
17
17
|
|
|
18
|
-
def __init__(
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
agent: Optional[Agent] = None,
|
|
21
|
+
team: Optional[Team] = None,
|
|
22
|
+
prefix: str = "",
|
|
23
|
+
tags: Optional[List[str]] = None,
|
|
24
|
+
):
|
|
25
|
+
"""
|
|
26
|
+
Initialize the AGUI interface.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
agent: The agent to expose via AG-UI
|
|
30
|
+
team: The team to expose via AG-UI
|
|
31
|
+
prefix: Custom prefix for the router (e.g., "/agui/v1", "/chat/public")
|
|
32
|
+
tags: Custom tags for the router (e.g., ["AGUI", "Chat"], defaults to ["AGUI"])
|
|
33
|
+
"""
|
|
19
34
|
self.agent = agent
|
|
20
35
|
self.team = team
|
|
36
|
+
self.prefix = prefix
|
|
37
|
+
self.tags = tags or ["AGUI"]
|
|
21
38
|
|
|
22
39
|
if not (self.agent or self.team):
|
|
23
40
|
raise ValueError("AGUI requires an agent or a team")
|
|
24
41
|
|
|
25
|
-
def get_router(self
|
|
26
|
-
#
|
|
27
|
-
self.router = APIRouter(tags=["AGUI"])
|
|
42
|
+
def get_router(self) -> APIRouter:
|
|
43
|
+
self.router = APIRouter(prefix=self.prefix, tags=self.tags) # type: ignore
|
|
28
44
|
|
|
29
45
|
self.router = attach_routes(router=self.router, agent=self.agent, team=self.team)
|
|
30
46
|
|
agno/os/interfaces/base.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import List, Optional
|
|
3
3
|
|
|
4
4
|
from fastapi import APIRouter
|
|
5
5
|
|
|
@@ -11,11 +11,13 @@ from agno.workflow.workflow import Workflow
|
|
|
11
11
|
class BaseInterface(ABC):
|
|
12
12
|
type: str
|
|
13
13
|
version: str = "1.0"
|
|
14
|
-
router_prefix: str = ""
|
|
15
14
|
agent: Optional[Agent] = None
|
|
16
15
|
team: Optional[Team] = None
|
|
17
16
|
workflow: Optional[Workflow] = None
|
|
18
17
|
|
|
18
|
+
prefix: str
|
|
19
|
+
tags: List[str]
|
|
20
|
+
|
|
19
21
|
router: APIRouter
|
|
20
22
|
|
|
21
23
|
@abstractmethod
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
from typing import Optional
|
|
1
|
+
from typing import List, Optional
|
|
3
2
|
|
|
4
3
|
from fastapi.routing import APIRouter
|
|
5
4
|
|
|
@@ -9,25 +8,31 @@ from agno.os.interfaces.slack.router import attach_routes
|
|
|
9
8
|
from agno.team.team import Team
|
|
10
9
|
from agno.workflow.workflow import Workflow
|
|
11
10
|
|
|
12
|
-
logger = logging.getLogger(__name__)
|
|
13
|
-
|
|
14
11
|
|
|
15
12
|
class Slack(BaseInterface):
|
|
16
13
|
type = "slack"
|
|
17
14
|
|
|
18
15
|
router: APIRouter
|
|
19
16
|
|
|
20
|
-
def __init__(
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
agent: Optional[Agent] = None,
|
|
20
|
+
team: Optional[Team] = None,
|
|
21
|
+
workflow: Optional[Workflow] = None,
|
|
22
|
+
prefix: str = "/slack",
|
|
23
|
+
tags: Optional[List[str]] = None,
|
|
24
|
+
):
|
|
21
25
|
self.agent = agent
|
|
22
26
|
self.team = team
|
|
23
27
|
self.workflow = workflow
|
|
28
|
+
self.prefix = prefix
|
|
29
|
+
self.tags = tags or ["Slack"]
|
|
24
30
|
|
|
25
31
|
if not (self.agent or self.team or self.workflow):
|
|
26
32
|
raise ValueError("Slack requires an agent, team or workflow")
|
|
27
33
|
|
|
28
|
-
def get_router(self
|
|
29
|
-
#
|
|
30
|
-
self.router = APIRouter(prefix="/slack", tags=["Slack"])
|
|
34
|
+
def get_router(self) -> APIRouter:
|
|
35
|
+
self.router = APIRouter(prefix=self.prefix, tags=self.tags) # type: ignore
|
|
31
36
|
|
|
32
37
|
self.router = attach_routes(router=self.router, agent=self.agent, team=self.team, workflow=self.workflow)
|
|
33
38
|
|
|
@@ -123,6 +123,7 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
|
|
|
123
123
|
response = await agent.arun(
|
|
124
124
|
message_text,
|
|
125
125
|
user_id=phone_number,
|
|
126
|
+
session_id=f"wa:{phone_number}",
|
|
126
127
|
images=[Image(content=await get_media_async(message_image))] if message_image else None,
|
|
127
128
|
files=[File(content=await get_media_async(message_doc))] if message_doc else None,
|
|
128
129
|
videos=[Video(content=await get_media_async(message_video))] if message_video else None,
|
|
@@ -132,6 +133,7 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
|
|
|
132
133
|
response = await team.arun( # type: ignore
|
|
133
134
|
message_text,
|
|
134
135
|
user_id=phone_number,
|
|
136
|
+
session_id=f"wa:{phone_number}",
|
|
135
137
|
files=[File(content=await get_media_async(message_doc))] if message_doc else None,
|
|
136
138
|
images=[Image(content=await get_media_async(message_image))] if message_image else None,
|
|
137
139
|
videos=[Video(content=await get_media_async(message_video))] if message_video else None,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import List, Optional
|
|
2
2
|
|
|
3
3
|
from fastapi.routing import APIRouter
|
|
4
4
|
|
|
@@ -13,16 +13,23 @@ class Whatsapp(BaseInterface):
|
|
|
13
13
|
|
|
14
14
|
router: APIRouter
|
|
15
15
|
|
|
16
|
-
def __init__(
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
agent: Optional[Agent] = None,
|
|
19
|
+
team: Optional[Team] = None,
|
|
20
|
+
prefix: str = "/whatsapp",
|
|
21
|
+
tags: Optional[List[str]] = None,
|
|
22
|
+
):
|
|
17
23
|
self.agent = agent
|
|
18
24
|
self.team = team
|
|
25
|
+
self.prefix = prefix
|
|
26
|
+
self.tags = tags or ["Whatsapp"]
|
|
19
27
|
|
|
20
28
|
if not (self.agent or self.team):
|
|
21
29
|
raise ValueError("Whatsapp requires an agent or a team")
|
|
22
30
|
|
|
23
|
-
def get_router(self
|
|
24
|
-
#
|
|
25
|
-
self.router = APIRouter(prefix="/whatsapp", tags=["Whatsapp"])
|
|
31
|
+
def get_router(self) -> APIRouter:
|
|
32
|
+
self.router = APIRouter(prefix=self.prefix, tags=self.tags) # type: ignore
|
|
26
33
|
|
|
27
34
|
self.router = attach_routes(router=self.router, agent=self.agent, team=self.team)
|
|
28
35
|
|
agno/os/mcp.py
CHANGED
|
@@ -54,7 +54,7 @@ def get_mcp_server(
|
|
|
54
54
|
) # type: ignore
|
|
55
55
|
async def config() -> ConfigResponse:
|
|
56
56
|
return ConfigResponse(
|
|
57
|
-
os_id=os.
|
|
57
|
+
os_id=os.id or "AgentOS",
|
|
58
58
|
description=os.description,
|
|
59
59
|
available_models=os.config.available_models if os.config else [],
|
|
60
60
|
databases=[db.id for db in os.dbs.values()],
|
|
@@ -68,7 +68,7 @@ def get_mcp_server(
|
|
|
68
68
|
teams=[TeamSummaryResponse.from_team(team) for team in os.teams] if os.teams else [],
|
|
69
69
|
workflows=[WorkflowSummaryResponse.from_workflow(w) for w in os.workflows] if os.workflows else [],
|
|
70
70
|
interfaces=[
|
|
71
|
-
InterfaceResponse(type=interface.type, version=interface.version, route=interface.
|
|
71
|
+
InterfaceResponse(type=interface.type, version=interface.version, route=interface.prefix)
|
|
72
72
|
for interface in os.interfaces
|
|
73
73
|
],
|
|
74
74
|
)
|
|
@@ -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)
|