From 5bb04632506738a752ee5b55830dd2d2c7ed8b69 Mon Sep 17 00:00:00 2001 From: "Patrick M. Riley" Date: Wed, 24 Sep 2025 14:28:53 -0400 Subject: [PATCH] feat: apply AsyncAuthorization, TokenVault sdk naming updates --- .vscode/settings.json | 6 +- README.md | 4 +- .../README.md | 4 +- .../langchain-examples/.env.example | 0 .../langchain-examples/LICENSE | 0 .../langchain-examples/README.md | 8 +- .../langchain-examples/langgraph.json | 0 .../langchain-examples/poetry.lock | 0 .../langchain-examples/pyproject.toml | 0 .../langchain-examples/src/__init__.py | 0 .../langchain-examples/src/agents/agent.py | 0 .../src/agents/clients/scheduler.py | 0 .../src/agents/conditional_trade.py | 2 +- .../src/agents/tools/conditional_trade.py | 0 .../src/agents/tools/trade.py | 4 +- .../langchain-examples/src/cli.py | 0 .../src/services/resumer.py | 8 +- .../src/services/scheduler.py | 0 .../llama-index-examples/.env.example | 0 .../llama-index-examples/LICENSE | 0 .../llama-index-examples/README.md | 2 +- .../llama-index-examples/poetry.lock | 0 .../llama-index-examples/pyproject.toml | 0 .../llama-index-examples/scripts/api.py | 0 .../llama-index-examples/scripts/start.py | 0 .../llama-index-examples/src/agents/agent.py | 0 .../llama-index-examples/src/agents/memory.py | 0 .../src/agents/tools/trade.py | 8 +- .../llama-index-examples/src/app/app.py | 0 .../src/app/templates/index.html | 115 ++++++++++++------ .../llama-index-examples/src/auth0/auth.py | 0 .../src/auth0/auth0_ai.py | 2 +- .../llama-index-examples/src/auth0/routes.py | 0 .../llama-index-examples/src/auth0/stores.py | 0 .../sample-api/app.py | 0 .../sample-api/poetry.lock | 0 .../sample-api/pyproject.toml | 0 .../calling-apis/langchain-examples/README.md | 3 +- .../src/agents/tools/check_user_calendar.py | 14 +-- .../src/agents/tools/list_channels.py | 8 +- .../src/agents/tools/list_repositories.py | 10 +- .../src/app/templates/index.html | 12 +- .../langchain-examples/src/auth0/auth0_ai.py | 6 +- .../llama-index-examples/README.md | 2 +- .../src/agents/tools/check_user_calendar.py | 10 +- .../src/agents/tools/list_channels.py | 8 +- .../src/agents/tools/list_repositories.py | 10 +- .../llama-index-examples/src/app/app.py | 4 +- .../src/app/templates/index.html | 12 +- .../src/auth0/auth0_ai.py | 6 +- packages/auth0-ai-langchain/README.md | 28 +++-- .../async_authorization/__init__.py | 3 + .../async_authorizer.py} | 6 +- .../graph_resumer.py | 8 +- .../auth0_ai_langchain/auth0_ai.py | 38 +++--- .../auth0_ai_langchain/ciba/__init__.py | 3 - .../federated_connections/__init__.py | 10 -- .../federated_connection_authorizer.py | 51 -------- .../token_vault/__init__.py | 10 ++ .../token_vault/token_vault_authorizer.py | 38 ++++++ packages/auth0-ai-llamaindex/README.md | 28 +++-- .../async_authorization/__init__.py | 2 + .../async_authorizer.py} | 4 +- .../auth0_ai_llamaindex/auth0_ai.py | 38 +++--- .../auth0_ai_llamaindex/ciba/__init__.py | 2 - .../federated_connections/__init__.py | 10 -- .../token_vault/__init__.py | 10 ++ .../token_vault_authorizer.py} | 10 +- packages/auth0-ai/README.md | 2 +- .../authorizers/async_auth/__init__.py | 0 .../async_authorization/__init__.py | 3 + .../async_authorization_request.py} | 2 +- .../async_authorizer_base.py} | 34 +++--- .../async_authorizer_params.py} | 4 +- .../auth0_ai/authorizers/ciba/__init__.py | 3 - ...uthorizer.py => token_vault_authorizer.py} | 50 ++++---- ...s.py => async_authorization_interrupts.py} | 56 ++++----- ..._interrupt.py => token_vault_interrupt.py} | 12 +- packages/auth0-ai/auth0_ai/stores/store.py | 4 +- 79 files changed, 382 insertions(+), 355 deletions(-) rename examples/{async-user-confirmation => async-authorization}/README.md (66%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/.env.example (100%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/LICENSE (100%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/README.md (95%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/langgraph.json (100%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/poetry.lock (100%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/pyproject.toml (100%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/src/__init__.py (100%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/src/agents/agent.py (100%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/src/agents/clients/scheduler.py (100%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/src/agents/conditional_trade.py (98%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/src/agents/tools/conditional_trade.py (100%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/src/agents/tools/trade.py (90%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/src/cli.py (100%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/src/services/resumer.py (74%) rename examples/{async-user-confirmation => async-authorization}/langchain-examples/src/services/scheduler.py (100%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/.env.example (100%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/LICENSE (100%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/README.md (98%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/poetry.lock (100%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/pyproject.toml (100%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/scripts/api.py (100%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/scripts/start.py (100%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/src/agents/agent.py (100%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/src/agents/memory.py (100%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/src/agents/tools/trade.py (77%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/src/app/app.py (100%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/src/app/templates/index.html (81%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/src/auth0/auth.py (100%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/src/auth0/auth0_ai.py (92%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/src/auth0/routes.py (100%) rename examples/{async-user-confirmation => async-authorization}/llama-index-examples/src/auth0/stores.py (100%) rename examples/{async-user-confirmation => async-authorization}/sample-api/app.py (100%) rename examples/{async-user-confirmation => async-authorization}/sample-api/poetry.lock (100%) rename examples/{async-user-confirmation => async-authorization}/sample-api/pyproject.toml (100%) create mode 100644 packages/auth0-ai-langchain/auth0_ai_langchain/async_authorization/__init__.py rename packages/auth0-ai-langchain/auth0_ai_langchain/{ciba/ciba_authorizer.py => async_authorization/async_authorizer.py} (69%) rename packages/auth0-ai-langchain/auth0_ai_langchain/{ciba => async_authorization}/graph_resumer.py (92%) delete mode 100644 packages/auth0-ai-langchain/auth0_ai_langchain/ciba/__init__.py delete mode 100644 packages/auth0-ai-langchain/auth0_ai_langchain/federated_connections/__init__.py delete mode 100644 packages/auth0-ai-langchain/auth0_ai_langchain/federated_connections/federated_connection_authorizer.py create mode 100644 packages/auth0-ai-langchain/auth0_ai_langchain/token_vault/__init__.py create mode 100644 packages/auth0-ai-langchain/auth0_ai_langchain/token_vault/token_vault_authorizer.py create mode 100644 packages/auth0-ai-llamaindex/auth0_ai_llamaindex/async_authorization/__init__.py rename packages/auth0-ai-llamaindex/auth0_ai_llamaindex/{ciba/ciba_authorizer.py => async_authorization/async_authorizer.py} (71%) delete mode 100644 packages/auth0-ai-llamaindex/auth0_ai_llamaindex/ciba/__init__.py delete mode 100644 packages/auth0-ai-llamaindex/auth0_ai_llamaindex/federated_connections/__init__.py create mode 100644 packages/auth0-ai-llamaindex/auth0_ai_llamaindex/token_vault/__init__.py rename packages/auth0-ai-llamaindex/auth0_ai_llamaindex/{federated_connections/federated_connection_authorizer.py => token_vault/token_vault_authorizer.py} (64%) create mode 100644 packages/auth0-ai/auth0_ai/authorizers/async_auth/__init__.py create mode 100644 packages/auth0-ai/auth0_ai/authorizers/async_authorization/__init__.py rename packages/auth0-ai/auth0_ai/authorizers/{ciba/ciba_authorization_request.py => async_authorization/async_authorization_request.py} (92%) rename packages/auth0-ai/auth0_ai/authorizers/{ciba/ciba_authorizer_base.py => async_authorization/async_authorizer_base.py} (88%) rename packages/auth0-ai/auth0_ai/authorizers/{ciba/ciba_authorizer_params.py => async_authorization/async_authorizer_params.py} (96%) delete mode 100644 packages/auth0-ai/auth0_ai/authorizers/ciba/__init__.py rename packages/auth0-ai/auth0_ai/authorizers/{federated_connection_authorizer.py => token_vault_authorizer.py} (87%) rename packages/auth0-ai/auth0_ai/interrupts/{ciba_interrupts.py => async_authorization_interrupts.py} (51%) rename packages/auth0-ai/auth0_ai/interrupts/{federated_connection_interrupt.py => token_vault_interrupt.py} (86%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2806898..dec5758 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,9 @@ { "poetryMonorepo.appendExtraPaths": true, "python.analysis.extraPaths": [ - "examples/async-user-confirmation/langchain-examples", - "examples/async-user-confirmation/llama-index-examples", - "examples/async-user-confirmation/sample-api", + "examples/async-authorization/langchain-examples", + "examples/async-authorization/llama-index-examples", + "examples/async-authorization/sample-api", "examples/authorization-for-tools/langchain-examples", "examples/authorization-for-tools/llama-index-examples", "examples/calling-apis/langchain-examples", diff --git a/README.md b/README.md index ebd7584..dedee62 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Developers are using LLMs to build generative AI applications that deliver power - **Authenticate users**: Easily implement login experiences tailored for AI agents and assistants. - **Call APIs on users' behalf**: Use secure standards to call APIs from tools, integrating your app with other products. - **Authorization for RAG**: Generate more relevant responses while ensuring that the agent incorporates only information the user has access to. -- **Async user confirmation**: Enable agents to operate autonomously in the background while requiring human approval when necessary. +- **Async Authorization**: Enable agents to operate autonomously in the background while requiring human approval when necessary. ## Packages @@ -24,7 +24,7 @@ Developers are using LLMs to build generative AI applications that deliver power - [Authorization for RAG](/examples/authorization-for-rag/README.md): Examples of implementing secure document retrieval with strict access control using Okta FGA. - [Authorization for Tools](/examples/authorization-for-tools/README.md): Examples of implementing secure tool calling with strict access control using Okta FGA. - [Calling APIs](/examples/calling-apis/README.md): Examples of using secure standards to call third-party APIs from tools with Auth0. -- [Async User Confirmation](/examples/async-user-confirmation/README.md): Examples of handling asynchronous user confirmation workflows. +- [Async Authorization](/examples/async-authorization/README.md): Examples of handling asynchronous user confirmation workflows. ## Recommendations for VSCode Users diff --git a/examples/async-user-confirmation/README.md b/examples/async-authorization/README.md similarity index 66% rename from examples/async-user-confirmation/README.md rename to examples/async-authorization/README.md index 69b1127..17ef739 100644 --- a/examples/async-user-confirmation/README.md +++ b/examples/async-authorization/README.md @@ -1,6 +1,6 @@ -## Async User Confirmation +## Async Authorization -Async User Confirmation enables background agents to perform tasks that require user approval before completion. This ensures that important actions are executed only with the user's consent, enhancing security and user control. For more information, refer to the [documentation](https://demo.auth0.ai/docs/async-user-confirmation). +Async Authorization enables background agents to perform tasks that require user approval before completion. This ensures that important actions are executed only with the user's consent, enhancing security and user control. For more information, refer to the [documentation](https://auth0.com/ai/docs/intro/asynchronous-authorization). ### How It Works diff --git a/examples/async-user-confirmation/langchain-examples/.env.example b/examples/async-authorization/langchain-examples/.env.example similarity index 100% rename from examples/async-user-confirmation/langchain-examples/.env.example rename to examples/async-authorization/langchain-examples/.env.example diff --git a/examples/async-user-confirmation/langchain-examples/LICENSE b/examples/async-authorization/langchain-examples/LICENSE similarity index 100% rename from examples/async-user-confirmation/langchain-examples/LICENSE rename to examples/async-authorization/langchain-examples/LICENSE diff --git a/examples/async-user-confirmation/langchain-examples/README.md b/examples/async-authorization/langchain-examples/README.md similarity index 95% rename from examples/async-user-confirmation/langchain-examples/README.md rename to examples/async-authorization/langchain-examples/README.md index 57f4df0..6517d64 100644 --- a/examples/async-user-confirmation/langchain-examples/README.md +++ b/examples/async-authorization/langchain-examples/README.md @@ -1,4 +1,4 @@ -# Async User Confirmation with LangChain +# Async Authorization Confirmation with LangChain ## Getting Started @@ -106,14 +106,14 @@ sequenceDiagram Agent->>User: "I've started a conditional trade" loop Every 10 mins Conditional Trade Agent->>Stocks API: Check P/E ratio - Stocks API-->>Conditional Trade Agent: + Stocks API-->>Conditional Trade Agent: alt P/E < 15 Conditional Trade Agent->>Auth0: Initiate CIBA request Auth0->>User's Phone: Send push notification Conditional Trade Agent->>+CIBA Agent: Monitor user response loop Every minute CIBA Agent->>Auth0: Check user approval status - Auth0-->>CIBA Agent: + Auth0-->>CIBA Agent: alt User approves User's Phone-->>Auth0: User approves Auth0-->>CIBA Agent: User approves @@ -123,7 +123,7 @@ sequenceDiagram end end Conditional Trade Agent->>Stocks API: Execute trade for 10 NVDA - Stocks API-->>Conditional Trade Agent: + Stocks API-->>Conditional Trade Agent: Conditional Trade Agent->>User's Phone: Inform user ``` diff --git a/examples/async-user-confirmation/langchain-examples/langgraph.json b/examples/async-authorization/langchain-examples/langgraph.json similarity index 100% rename from examples/async-user-confirmation/langchain-examples/langgraph.json rename to examples/async-authorization/langchain-examples/langgraph.json diff --git a/examples/async-user-confirmation/langchain-examples/poetry.lock b/examples/async-authorization/langchain-examples/poetry.lock similarity index 100% rename from examples/async-user-confirmation/langchain-examples/poetry.lock rename to examples/async-authorization/langchain-examples/poetry.lock diff --git a/examples/async-user-confirmation/langchain-examples/pyproject.toml b/examples/async-authorization/langchain-examples/pyproject.toml similarity index 100% rename from examples/async-user-confirmation/langchain-examples/pyproject.toml rename to examples/async-authorization/langchain-examples/pyproject.toml diff --git a/examples/async-user-confirmation/langchain-examples/src/__init__.py b/examples/async-authorization/langchain-examples/src/__init__.py similarity index 100% rename from examples/async-user-confirmation/langchain-examples/src/__init__.py rename to examples/async-authorization/langchain-examples/src/__init__.py diff --git a/examples/async-user-confirmation/langchain-examples/src/agents/agent.py b/examples/async-authorization/langchain-examples/src/agents/agent.py similarity index 100% rename from examples/async-user-confirmation/langchain-examples/src/agents/agent.py rename to examples/async-authorization/langchain-examples/src/agents/agent.py diff --git a/examples/async-user-confirmation/langchain-examples/src/agents/clients/scheduler.py b/examples/async-authorization/langchain-examples/src/agents/clients/scheduler.py similarity index 100% rename from examples/async-user-confirmation/langchain-examples/src/agents/clients/scheduler.py rename to examples/async-authorization/langchain-examples/src/agents/clients/scheduler.py diff --git a/examples/async-user-confirmation/langchain-examples/src/agents/conditional_trade.py b/examples/async-authorization/langchain-examples/src/agents/conditional_trade.py similarity index 98% rename from examples/async-user-confirmation/langchain-examples/src/agents/conditional_trade.py rename to examples/async-authorization/langchain-examples/src/agents/conditional_trade.py index 774c9b4..e3cf5fa 100644 --- a/examples/async-user-confirmation/langchain-examples/src/agents/conditional_trade.py +++ b/examples/async-authorization/langchain-examples/src/agents/conditional_trade.py @@ -101,7 +101,7 @@ def check_trade_status(state: State): return END auth0_ai = Auth0AI() -protect_tool = auth0_ai.with_async_user_confirmation( +protect_tool = auth0_ai.with_async_authorization( audience=os.getenv("AUDIENCE"), requested_expiry=os.getenv("REQUESTED_EXPIRY"), scopes=["stock:trade"], diff --git a/examples/async-user-confirmation/langchain-examples/src/agents/tools/conditional_trade.py b/examples/async-authorization/langchain-examples/src/agents/tools/conditional_trade.py similarity index 100% rename from examples/async-user-confirmation/langchain-examples/src/agents/tools/conditional_trade.py rename to examples/async-authorization/langchain-examples/src/agents/tools/conditional_trade.py diff --git a/examples/async-user-confirmation/langchain-examples/src/agents/tools/trade.py b/examples/async-authorization/langchain-examples/src/agents/tools/trade.py similarity index 90% rename from examples/async-user-confirmation/langchain-examples/src/agents/tools/trade.py rename to examples/async-authorization/langchain-examples/src/agents/tools/trade.py index 7378a85..9c9a8ed 100644 --- a/examples/async-user-confirmation/langchain-examples/src/agents/tools/trade.py +++ b/examples/async-authorization/langchain-examples/src/agents/tools/trade.py @@ -1,6 +1,6 @@ import os import httpx -from auth0_ai_langchain.ciba import get_ciba_credentials +from auth0_ai_langchain.async_authorization import get_async_authorization_credentials from langchain_core.tools import StructuredTool from pydantic import BaseModel @@ -9,7 +9,7 @@ class TradeSchema(BaseModel): qty: int def trade_tool_function(ticker: str, qty: int) -> str: - credentials = get_ciba_credentials() + credentials = get_async_authorization_credentials() if not credentials: raise ValueError("Access token not found") diff --git a/examples/async-user-confirmation/langchain-examples/src/cli.py b/examples/async-authorization/langchain-examples/src/cli.py similarity index 100% rename from examples/async-user-confirmation/langchain-examples/src/cli.py rename to examples/async-authorization/langchain-examples/src/cli.py diff --git a/examples/async-user-confirmation/langchain-examples/src/services/resumer.py b/examples/async-authorization/langchain-examples/src/services/resumer.py similarity index 74% rename from examples/async-user-confirmation/langchain-examples/src/services/resumer.py rename to examples/async-authorization/langchain-examples/src/services/resumer.py index 3ed87f7..f6e95e8 100644 --- a/examples/async-user-confirmation/langchain-examples/src/services/resumer.py +++ b/examples/async-authorization/langchain-examples/src/services/resumer.py @@ -1,6 +1,6 @@ import asyncio import os -from auth0_ai_langchain.ciba import GraphResumer +from auth0_ai_langchain.async_authorization import GraphResumer from langgraph_sdk import get_client async def main(): @@ -14,13 +14,13 @@ async def main(): .on_error(lambda err: print(f"Error in GraphResumer: {str(err)}")) resumer.start() - print("Started CIBA Graph Resumer.") - print("The purpose of this service is to monitor interrupted threads by Auth0AI CIBA Authorizer and resume them.") + print("Started Async Authorization Graph Resumer.") + print("The purpose of this service is to monitor interrupted threads by Auth0AI Async Authorizer and resume them.") try: await asyncio.Event().wait() except KeyboardInterrupt: - print("Stopping CIBA Graph Resumer...") + print("Stopping Async Authorization Graph Resumer...") resumer.stop() asyncio.run(main()) diff --git a/examples/async-user-confirmation/langchain-examples/src/services/scheduler.py b/examples/async-authorization/langchain-examples/src/services/scheduler.py similarity index 100% rename from examples/async-user-confirmation/langchain-examples/src/services/scheduler.py rename to examples/async-authorization/langchain-examples/src/services/scheduler.py diff --git a/examples/async-user-confirmation/llama-index-examples/.env.example b/examples/async-authorization/llama-index-examples/.env.example similarity index 100% rename from examples/async-user-confirmation/llama-index-examples/.env.example rename to examples/async-authorization/llama-index-examples/.env.example diff --git a/examples/async-user-confirmation/llama-index-examples/LICENSE b/examples/async-authorization/llama-index-examples/LICENSE similarity index 100% rename from examples/async-user-confirmation/llama-index-examples/LICENSE rename to examples/async-authorization/llama-index-examples/LICENSE diff --git a/examples/async-user-confirmation/llama-index-examples/README.md b/examples/async-authorization/llama-index-examples/README.md similarity index 98% rename from examples/async-user-confirmation/llama-index-examples/README.md rename to examples/async-authorization/llama-index-examples/README.md index 855aade..d3d6832 100644 --- a/examples/async-user-confirmation/llama-index-examples/README.md +++ b/examples/async-authorization/llama-index-examples/README.md @@ -1,4 +1,4 @@ -# Async User Confirmation with LlamaIndex +# Async Authorization with LlamaIndex ## Getting Started diff --git a/examples/async-user-confirmation/llama-index-examples/poetry.lock b/examples/async-authorization/llama-index-examples/poetry.lock similarity index 100% rename from examples/async-user-confirmation/llama-index-examples/poetry.lock rename to examples/async-authorization/llama-index-examples/poetry.lock diff --git a/examples/async-user-confirmation/llama-index-examples/pyproject.toml b/examples/async-authorization/llama-index-examples/pyproject.toml similarity index 100% rename from examples/async-user-confirmation/llama-index-examples/pyproject.toml rename to examples/async-authorization/llama-index-examples/pyproject.toml diff --git a/examples/async-user-confirmation/llama-index-examples/scripts/api.py b/examples/async-authorization/llama-index-examples/scripts/api.py similarity index 100% rename from examples/async-user-confirmation/llama-index-examples/scripts/api.py rename to examples/async-authorization/llama-index-examples/scripts/api.py diff --git a/examples/async-user-confirmation/llama-index-examples/scripts/start.py b/examples/async-authorization/llama-index-examples/scripts/start.py similarity index 100% rename from examples/async-user-confirmation/llama-index-examples/scripts/start.py rename to examples/async-authorization/llama-index-examples/scripts/start.py diff --git a/examples/async-user-confirmation/llama-index-examples/src/agents/agent.py b/examples/async-authorization/llama-index-examples/src/agents/agent.py similarity index 100% rename from examples/async-user-confirmation/llama-index-examples/src/agents/agent.py rename to examples/async-authorization/llama-index-examples/src/agents/agent.py diff --git a/examples/async-user-confirmation/llama-index-examples/src/agents/memory.py b/examples/async-authorization/llama-index-examples/src/agents/memory.py similarity index 100% rename from examples/async-user-confirmation/llama-index-examples/src/agents/memory.py rename to examples/async-authorization/llama-index-examples/src/agents/memory.py diff --git a/examples/async-user-confirmation/llama-index-examples/src/agents/tools/trade.py b/examples/async-authorization/llama-index-examples/src/agents/tools/trade.py similarity index 77% rename from examples/async-user-confirmation/llama-index-examples/src/agents/tools/trade.py rename to examples/async-authorization/llama-index-examples/src/agents/tools/trade.py index 7b9160d..84d14a6 100644 --- a/examples/async-user-confirmation/llama-index-examples/src/agents/tools/trade.py +++ b/examples/async-authorization/llama-index-examples/src/agents/tools/trade.py @@ -3,13 +3,13 @@ import httpx from llama_index.core.tools import FunctionTool -from auth0_ai_llamaindex.ciba import get_ciba_credentials -from ...auth0.auth0_ai import with_async_user_confirmation +from auth0_ai_llamaindex.async_authorization import get_async_authorization_credentials +from ...auth0.auth0_ai import with_async_authorization load_dotenv() def trade_tool_function(ticker: str, qty: int) -> str: - credentials = get_ciba_credentials() + credentials = get_async_authorization_credentials() if not credentials: raise ValueError("Access token not found") @@ -32,7 +32,7 @@ def trade_tool_function(ticker: str, qty: int) -> str: return f"HTTP request failed: {str(e)}" -trade_tool = with_async_user_confirmation(FunctionTool.from_defaults( +trade_tool = with_async_authorization(FunctionTool.from_defaults( name="trade_tool", description="Use this function to trade a stock", fn=trade_tool_function, diff --git a/examples/async-user-confirmation/llama-index-examples/src/app/app.py b/examples/async-authorization/llama-index-examples/src/app/app.py similarity index 100% rename from examples/async-user-confirmation/llama-index-examples/src/app/app.py rename to examples/async-authorization/llama-index-examples/src/app/app.py diff --git a/examples/async-user-confirmation/llama-index-examples/src/app/templates/index.html b/examples/async-authorization/llama-index-examples/src/app/templates/index.html similarity index 81% rename from examples/async-user-confirmation/llama-index-examples/src/app/templates/index.html rename to examples/async-authorization/llama-index-examples/src/app/templates/index.html index 55eee4c..1fc5a24 100644 --- a/examples/async-user-confirmation/llama-index-examples/src/app/templates/index.html +++ b/examples/async-authorization/llama-index-examples/src/app/templates/index.html @@ -165,12 +165,12 @@ @@ -178,7 +178,12 @@ import React, { useState, useContext } from "react"; const ApplicationContext = React.createContext(); - export const ContextProvider = ({ user: initialUser = {}, messages: initialMessages = [], interrupt: initialInterrupt, children }) => { + export const ContextProvider = ({ + user: initialUser = {}, + messages: initialMessages = [], + interrupt: initialInterrupt, + children, + }) => { const [user, setUser] = useState(initialUser); const [messages, setMessages] = useState(initialMessages); const [interrupt, setInterrupt] = useState(initialInterrupt); @@ -187,7 +192,7 @@ // values ...{ user, messages, interrupt, isSubmitting }, // setters - ...{ setUser, setMessages, setInterrupt, setIsSubmitting} + ...{ setUser, setMessages, setInterrupt, setIsSubmitting }, }; return ( @@ -195,13 +200,15 @@ {children} ); - } + }; window.ContextProvider = ContextProvider; window.useAppContext = () => { const context = useContext(ApplicationContext); if (!context) { - throw new Error("useAppContext must be used within a ContextProvider"); + throw new Error( + "useAppContext must be used within a ContextProvider" + ); } return context; }; @@ -233,20 +240,22 @@

Auth0 AI | Demo

const classNames = { ai: { container: "flex justify-start gap-2", - content: "bg-gray-200 text-black px-4 py-2 rounded-xl w-fit max-w-lg h-fit first:rounded-full first:p-0 first:h-10 first:w-10 first:content-center first:text-center" + content: + "bg-gray-200 text-black px-4 py-2 rounded-xl w-fit max-w-lg h-fit first:rounded-full first:p-0 first:h-10 first:w-10 first:content-center first:text-center", }, human: { container: "flex justify-end", - content: "bg-blue-500 text-white px-4 py-2 rounded-xl w-fit max-w-lg" - } + content: + "bg-blue-500 text-white px-4 py-2 rounded-xl w-fit max-w-lg", + }, }; return (
- {type === "ai" ?
AI
: null} -
- {content} -
+ {type === "ai" ? ( +
AI
+ ) : null} +
{content}
); } @@ -258,11 +267,14 @@

Auth0 AI | Demo

const { connection, required_scopes: requiredScopes } = interrupt; return ( - ); } @@ -273,15 +285,25 @@

Auth0 AI | Demo

useEffect(() => { if (scrollContainerRef.current) { - scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight; + scrollContainerRef.current.scrollTop = + scrollContainerRef.current.scrollHeight; } }, [messages, interrupt, isSubmitting]); return ( -
- {messages.filter(({ type }) => ["ai", "human"].includes(type)).map((message, index) => ( - - ))} +
+ {messages + .filter(({ type }) => ["ai", "human"].includes(type)) + .map((message, index) => ( + + ))} {isSubmitting && }
@@ -297,7 +319,6 @@

Auth0 AI | Demo

const { isSubmitting } = useAppContext(); const inputRef = useRef(null); - const onSubmit = (e) => { e.preventDefault(); if (!value) return; @@ -313,7 +334,10 @@

Auth0 AI | Demo

return (
-
+
Auth0 AI | Demo const { setMessages, setInterrupt, setIsSubmitting } = useAppContext(); const handleSubmit = async (message) => { setIsSubmitting(true); - setMessages((prevMessages) => [...prevMessages, { type: "human", content: message }]); + setMessages((prevMessages) => [ + ...prevMessages, + { type: "human", content: message }, + ]); try { const res = await fetch("/api/chat", { @@ -356,18 +383,25 @@

Auth0 AI | Demo

throw new Error(data.error || "Failed to fetch response"); } - if (FederatedConnectionInterrupt.isInterrupt(data?.response)) { + if (TokenVaultInterrupt.isInterrupt(data?.response)) { return setInterrupt(data.response); } setMessages((prevMessages) => [ ...prevMessages, - { type: "ai", content: data.response || `Failed to fetch response: ${data.error}` } + { + type: "ai", + content: + data.response || `Failed to fetch response: ${data.error}`, + }, ]); } catch (err) { setMessages((prevMessages) => [ ...prevMessages, - { type: "ai", content: `Failed to fetch response: ${err.message}` } + { + type: "ai", + content: `Failed to fetch response: ${err.message}`, + }, ]); } finally { setIsSubmitting(false); @@ -393,19 +427,22 @@

Auth0 AI | Demo

const rolesMapping = { user: "human", - assistant: "ai" + assistant: "ai", }; const user = JSON.parse(document.getElementById("user").textContent); - const messages = JSON.parse(document.getElementById("messages").textContent) - .map(({ role, content }) => ({ - type: rolesMapping[role] || role, - content - })); - - const interrupt = JSON.parse(document.getElementById("interrupt").textContent); + const messages = JSON.parse( + document.getElementById("messages").textContent + ).map(({ role, content }) => ({ + type: rolesMapping[role] || role, + content, + })); + + const interrupt = JSON.parse( + document.getElementById("interrupt").textContent + ); - const root = createRoot(document.getElementById('app')); + const root = createRoot(document.getElementById("app")); root.render( diff --git a/examples/async-user-confirmation/llama-index-examples/src/auth0/auth.py b/examples/async-authorization/llama-index-examples/src/auth0/auth.py similarity index 100% rename from examples/async-user-confirmation/llama-index-examples/src/auth0/auth.py rename to examples/async-authorization/llama-index-examples/src/auth0/auth.py diff --git a/examples/async-user-confirmation/llama-index-examples/src/auth0/auth0_ai.py b/examples/async-authorization/llama-index-examples/src/auth0/auth0_ai.py similarity index 92% rename from examples/async-user-confirmation/llama-index-examples/src/auth0/auth0_ai.py rename to examples/async-authorization/llama-index-examples/src/auth0/auth0_ai.py index b916c60..9bdc86b 100644 --- a/examples/async-user-confirmation/llama-index-examples/src/auth0/auth0_ai.py +++ b/examples/async-authorization/llama-index-examples/src/auth0/auth0_ai.py @@ -14,7 +14,7 @@ async def user_id(**_kwargs): user = await auth0.get_user(store_options=store_options) return user.get("sub") -with_async_user_confirmation = auth0_ai.with_async_user_confirmation( +with_async_authorization = auth0_ai.with_async_authorization( scopes=["stock:trade"], audience=os.getenv("AUDIENCE"), requested_expiry=os.getenv("REQUESTED_EXPIRY"), diff --git a/examples/async-user-confirmation/llama-index-examples/src/auth0/routes.py b/examples/async-authorization/llama-index-examples/src/auth0/routes.py similarity index 100% rename from examples/async-user-confirmation/llama-index-examples/src/auth0/routes.py rename to examples/async-authorization/llama-index-examples/src/auth0/routes.py diff --git a/examples/async-user-confirmation/llama-index-examples/src/auth0/stores.py b/examples/async-authorization/llama-index-examples/src/auth0/stores.py similarity index 100% rename from examples/async-user-confirmation/llama-index-examples/src/auth0/stores.py rename to examples/async-authorization/llama-index-examples/src/auth0/stores.py diff --git a/examples/async-user-confirmation/sample-api/app.py b/examples/async-authorization/sample-api/app.py similarity index 100% rename from examples/async-user-confirmation/sample-api/app.py rename to examples/async-authorization/sample-api/app.py diff --git a/examples/async-user-confirmation/sample-api/poetry.lock b/examples/async-authorization/sample-api/poetry.lock similarity index 100% rename from examples/async-user-confirmation/sample-api/poetry.lock rename to examples/async-authorization/sample-api/poetry.lock diff --git a/examples/async-user-confirmation/sample-api/pyproject.toml b/examples/async-authorization/sample-api/pyproject.toml similarity index 100% rename from examples/async-user-confirmation/sample-api/pyproject.toml rename to examples/async-authorization/sample-api/pyproject.toml diff --git a/examples/calling-apis/langchain-examples/README.md b/examples/calling-apis/langchain-examples/README.md index ec4332a..8956b53 100644 --- a/examples/calling-apis/langchain-examples/README.md +++ b/examples/calling-apis/langchain-examples/README.md @@ -12,7 +12,7 @@ - **Application Type**: `Regular Web Application` - **Allowed Callback URLs**: `http://localhost:3000/auth/callback` - **Allowed Logout URLs**: `http://localhost:3000` - - **Advanced Settings -> Grant Types**: `Refresh Token` and `Token Exchange (Federated Connection)` (or `urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token`) + - **Advanced Settings -> Grant Types**: `Refresh Token` and `Token Vault` (or `urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token`) - Either **Google**, **Slack** or **Github** social connections enabled for the application: - **Google connection** set up instructions: - Create a [Google OAuth 2.0 Client](https://console.cloud.google.com/apis/credentials) configured with access to the `https://www.googleapis.com/auth/calendar.freebusy` scope (Google Calendar API). @@ -28,7 +28,6 @@ - Register a new app in [GitHub Developer Settings: OAuth Apps](https://github.com/settings/developers#oauth-apps) and follow the [Auth0's Github social connection](https://marketplace.auth0.com/integrations/github-social-connection) `installation` instructions to set up the connection. - On Auth0 Dashboard, set up the client ID and secret from the previously created Github App. - ### Setup Copy the file `.env.example` to `.env` and fill in the required values: diff --git a/examples/calling-apis/langchain-examples/src/agents/tools/check_user_calendar.py b/examples/calling-apis/langchain-examples/src/agents/tools/check_user_calendar.py index 19837b8..206d2c4 100644 --- a/examples/calling-apis/langchain-examples/src/agents/tools/check_user_calendar.py +++ b/examples/calling-apis/langchain-examples/src/agents/tools/check_user_calendar.py @@ -1,9 +1,9 @@ from datetime import datetime, timedelta import requests -from auth0_ai_langchain.federated_connections import ( - FederatedConnectionError, - get_credentials_for_connection, +from auth0_ai_langchain.token_vault import ( + TokenVaultError, + get_credentials_from_token_vault, ) from langchain_core.tools import StructuredTool from pydantic import BaseModel @@ -20,10 +20,10 @@ def add_hours(dt: datetime, hours: int) -> str: def check_user_calendar_tool_function(date: datetime): - credentials = get_credentials_for_connection() + credentials = get_credentials_from_token_vault() if not credentials: raise ValueError( - "Authorization required to access the Federated Connection API") + "Authorization required to access the Token Vault connection") url = "https://www.googleapis.com/calendar/v3/freeBusy" body = { @@ -42,8 +42,8 @@ def check_user_calendar_tool_function(date: datetime): if response.status_code != 200: if response.status_code == 401: - raise FederatedConnectionError( - "Authorization required to access the Federated Connection API") + raise TokenVaultError( + "Authorization required to access the Token Vault connection") raise ValueError( f"Invalid response from Google Calendar API: {response.status_code} - {response.text}") diff --git a/examples/calling-apis/langchain-examples/src/agents/tools/list_channels.py b/examples/calling-apis/langchain-examples/src/agents/tools/list_channels.py index 46025ab..e5cae95 100644 --- a/examples/calling-apis/langchain-examples/src/agents/tools/list_channels.py +++ b/examples/calling-apis/langchain-examples/src/agents/tools/list_channels.py @@ -2,7 +2,7 @@ from slack_sdk.errors import SlackApiError from pydantic import BaseModel from langchain_core.tools import StructuredTool -from auth0_ai_langchain.federated_connections import get_credentials_for_connection, FederatedConnectionError +from auth0_ai_langchain.token_vault import get_credentials_from_token_vault, TokenVaultError from src.auth0.auth0_ai import with_slack_access @@ -12,7 +12,7 @@ class EmptySchema(BaseModel): def list_channels_tool_function(): # Get the access token from Auth0 AI - credentials = get_credentials_for_connection() + credentials = get_credentials_from_token_vault() # Slack SDK try: @@ -27,8 +27,8 @@ def list_channels_tool_function(): return channel_names except SlackApiError as e: if e.response['error'] == 'not_authed': - raise FederatedConnectionError( - "Authorization required to access the Federated Connection API") + raise TokenVaultError( + "Authorization required to access the Token Vault connection") raise ValueError(f"An error occurred: {e.response['error']}") diff --git a/examples/calling-apis/langchain-examples/src/agents/tools/list_repositories.py b/examples/calling-apis/langchain-examples/src/agents/tools/list_repositories.py index ca3da80..7b93eb5 100644 --- a/examples/calling-apis/langchain-examples/src/agents/tools/list_repositories.py +++ b/examples/calling-apis/langchain-examples/src/agents/tools/list_repositories.py @@ -3,7 +3,7 @@ from src.auth0.auth0_ai import with_github_access from pydantic import BaseModel from langchain_core.tools import StructuredTool -from auth0_ai_langchain.federated_connections import FederatedConnectionError, get_credentials_for_connection +from auth0_ai_langchain.token_vault import TokenVaultError, get_credentials_from_token_vault class ListRepositoriesSchema(BaseModel): @@ -11,10 +11,10 @@ class ListRepositoriesSchema(BaseModel): def list_repositories_tool_function(): - credentials = get_credentials_for_connection() + credentials = get_credentials_from_token_vault() if not credentials: raise ValueError( - "Authorization required to access the Federated Connection API") + "Authorization required to access the Token Vault connection") # GitHub SDK try: @@ -24,8 +24,8 @@ def list_repositories_tool_function(): repo_names = [repo.name for repo in repos] return repo_names except BadCredentialsException: - raise FederatedConnectionError( - "Authorization required to access the Federated Connection API") + raise TokenVaultError( + "Authorization required to access the Token Vault connection") list_github_repositories_tool = with_github_access(StructuredTool( diff --git a/examples/calling-apis/langchain-examples/src/app/templates/index.html b/examples/calling-apis/langchain-examples/src/app/templates/index.html index 7432751..abcf0f2 100644 --- a/examples/calling-apis/langchain-examples/src/app/templates/index.html +++ b/examples/calling-apis/langchain-examples/src/app/templates/index.html @@ -165,12 +165,12 @@ @@ -258,7 +258,7 @@

Auth0 AI | Demo

const { connection, required_scopes: requiredScopes } = interrupt.value; return ( - Auth0 AI | Demo throw new Error(data.error || "Failed to fetch response"); } - if (FederatedConnectionInterrupt.isInterrupt(data?.response?.value)) { + if (TokenVaultInterrupt.isInterrupt(data?.response?.value)) { return setInterrupt(data.response); } diff --git a/examples/calling-apis/langchain-examples/src/auth0/auth0_ai.py b/examples/calling-apis/langchain-examples/src/auth0/auth0_ai.py index 0877c4e..491c9fd 100644 --- a/examples/calling-apis/langchain-examples/src/auth0/auth0_ai.py +++ b/examples/calling-apis/langchain-examples/src/auth0/auth0_ai.py @@ -6,17 +6,17 @@ auth0_ai = Auth0AI() -with_calendar_free_busy_access = auth0_ai.with_federated_connection( +with_calendar_free_busy_access = auth0_ai.with_token_vault( connection="google-oauth2", scopes=["https://www.googleapis.com/auth/calendar.freebusy"] ) -with_slack_access = auth0_ai.with_federated_connection( +with_slack_access = auth0_ai.with_token_vault( connection="sign-in-with-slack", scopes=["channels:read"] ) -with_github_access = auth0_ai.with_federated_connection( +with_github_access = auth0_ai.with_token_vault( connection="github", scopes=["repo"], ) diff --git a/examples/calling-apis/llama-index-examples/README.md b/examples/calling-apis/llama-index-examples/README.md index 0915efe..96c41fc 100644 --- a/examples/calling-apis/llama-index-examples/README.md +++ b/examples/calling-apis/llama-index-examples/README.md @@ -11,7 +11,7 @@ - **Application Type**: `Regular Web Application` - **Allowed Callback URLs**: `http://localhost:3000/auth/callback` - **Allowed Logout URLs**: `http://localhost:3000` - - **Advanced Settings -> Grant Types**: `Refresh Token` and `Token Exchange (Federated Connection)` (or `urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token`) + - **Advanced Settings -> Grant Types**: `Refresh Token` and `Token Vault` (or `urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token`) - Either **Google**, **Slack** or **Github** social connections enabled for the application: - **Google connection** set up instructions: - Create a [Google OAuth 2.0 Client](https://console.cloud.google.com/apis/credentials) configured with access to the `https://www.googleapis.com/auth/calendar.freebusy` scope (Google Calendar API). diff --git a/examples/calling-apis/llama-index-examples/src/agents/tools/check_user_calendar.py b/examples/calling-apis/llama-index-examples/src/agents/tools/check_user_calendar.py index d8c29da..02d04f9 100644 --- a/examples/calling-apis/llama-index-examples/src/agents/tools/check_user_calendar.py +++ b/examples/calling-apis/llama-index-examples/src/agents/tools/check_user_calendar.py @@ -3,7 +3,7 @@ from typing import Annotated from llama_index.core.tools import FunctionTool -from auth0_ai_llamaindex.federated_connections import FederatedConnectionError, get_credentials_for_connection +from auth0_ai_llamaindex.token_vault import TokenVaultError, get_credentials_from_token_vault from src.auth0.auth0_ai import with_calendar_free_busy_access @@ -14,10 +14,10 @@ def add_hours(dt: datetime, hours: int) -> str: def check_user_calendar_tool_function( dateTime: Annotated[str, "Date and time in ISO 8601 format."] ): - credentials = get_credentials_for_connection() + credentials = get_credentials_from_token_vault() if not credentials: raise ValueError( - "Authorization required to access the Federated Connection API") + "Authorization required to access the Token Vault connection") url = "https://www.googleapis.com/calendar/v3/freeBusy" body = { @@ -36,8 +36,8 @@ def check_user_calendar_tool_function( if response.status_code != 200: if response.status_code == 401: - raise FederatedConnectionError( - "Authorization required to access the Federated Connection API") + raise TokenVaultError( + "Authorization required to access the Token Vault connection") raise ValueError( f"Invalid response from Google Calendar API: {response.status_code} - {response.text}") diff --git a/examples/calling-apis/llama-index-examples/src/agents/tools/list_channels.py b/examples/calling-apis/llama-index-examples/src/agents/tools/list_channels.py index 4a13185..f2448b8 100644 --- a/examples/calling-apis/llama-index-examples/src/agents/tools/list_channels.py +++ b/examples/calling-apis/llama-index-examples/src/agents/tools/list_channels.py @@ -2,13 +2,13 @@ from slack_sdk.errors import SlackApiError from llama_index.core.tools import FunctionTool -from auth0_ai_llamaindex.federated_connections import get_credentials_for_connection, FederatedConnectionError +from auth0_ai_llamaindex.token_vault import get_credentials_from_token_vault, TokenVaultError from src.auth0.auth0_ai import with_slack_access def list_channels_tool_function(): # Get the access token from Auth0 AI - credentials = get_credentials_for_connection() + credentials = get_credentials_from_token_vault() # Slack SDK try: @@ -23,8 +23,8 @@ def list_channels_tool_function(): return channel_names except SlackApiError as e: if e.response['error'] == 'not_authed': - raise FederatedConnectionError( - "Authorization required to access the Federated Connection API") + raise TokenVaultError( + "Authorization required to access the Token Vault connection") raise ValueError(f"An error occurred: {e.response['error']}") diff --git a/examples/calling-apis/llama-index-examples/src/agents/tools/list_repositories.py b/examples/calling-apis/llama-index-examples/src/agents/tools/list_repositories.py index 8b641e2..4fe7c0d 100644 --- a/examples/calling-apis/llama-index-examples/src/agents/tools/list_repositories.py +++ b/examples/calling-apis/llama-index-examples/src/agents/tools/list_repositories.py @@ -2,15 +2,15 @@ from github.GithubException import BadCredentialsException from llama_index.core.tools import FunctionTool -from auth0_ai_llamaindex.federated_connections import FederatedConnectionError, get_access_token_for_connection +from auth0_ai_llamaindex.token_vault import TokenVaultError, get_access_token_from_token_vault from src.auth0.auth0_ai import with_github_access def list_repositories_tool_function(): - access_token = get_access_token_for_connection() + access_token = get_access_token_from_token_vault() if not access_token: raise ValueError( - "Authorization required to access the Federated Connection API") + "Authorization required to access the Token Vault connection") # GitHub SDK try: @@ -20,8 +20,8 @@ def list_repositories_tool_function(): repo_names = [repo.name for repo in repos] return repo_names except BadCredentialsException: - raise FederatedConnectionError( - "Authorization required to access the Federated Connection API") + raise TokenVaultError( + "Authorization required to access the Token Vault connection") list_github_repositories_tool = with_github_access(FunctionTool.from_defaults( diff --git a/examples/calling-apis/llama-index-examples/src/app/app.py b/examples/calling-apis/llama-index-examples/src/app/app.py index c847a08..19e6f75 100644 --- a/examples/calling-apis/llama-index-examples/src/app/app.py +++ b/examples/calling-apis/llama-index-examples/src/app/app.py @@ -4,7 +4,7 @@ from flask import Flask, jsonify, redirect, render_template, request, session, url_for from auth0_ai_llamaindex.auth0_ai import set_ai_context -from auth0_ai_llamaindex.federated_connections import FederatedConnectionInterrupt +from auth0_ai_llamaindex.token_vault import TokenVaultInterrupt from ..agents.agent import agent from ..agents.memory import get_memory @@ -84,7 +84,7 @@ async def api_chat(): memory = await get_memory(user_id, thread_id) response = await agent.run(user_msg=message, memory=memory) return jsonify({"response": str(response)}) - except FederatedConnectionInterrupt as e: + except TokenVaultInterrupt as e: session["interrupt"] = { "value": e.to_json(), "last_message": message diff --git a/examples/calling-apis/llama-index-examples/src/app/templates/index.html b/examples/calling-apis/llama-index-examples/src/app/templates/index.html index 55eee4c..0fb6158 100644 --- a/examples/calling-apis/llama-index-examples/src/app/templates/index.html +++ b/examples/calling-apis/llama-index-examples/src/app/templates/index.html @@ -165,12 +165,12 @@ @@ -258,7 +258,7 @@

Auth0 AI | Demo

const { connection, required_scopes: requiredScopes } = interrupt; return ( - Auth0 AI | Demo throw new Error(data.error || "Failed to fetch response"); } - if (FederatedConnectionInterrupt.isInterrupt(data?.response)) { + if (TokenVaultInterrupt.isInterrupt(data?.response)) { return setInterrupt(data.response); } diff --git a/examples/calling-apis/llama-index-examples/src/auth0/auth0_ai.py b/examples/calling-apis/llama-index-examples/src/auth0/auth0_ai.py index 5336586..c465927 100644 --- a/examples/calling-apis/llama-index-examples/src/auth0/auth0_ai.py +++ b/examples/calling-apis/llama-index-examples/src/auth0/auth0_ai.py @@ -13,18 +13,18 @@ async def refresh_token(*args, **kwargs): auth_session = await auth0.get_session(store_options=store_options) return auth_session.get("refresh_token") -with_calendar_free_busy_access = auth0_ai.with_federated_connection( +with_calendar_free_busy_access = auth0_ai.with_token_vault( connection="google-oauth2", scopes=["https://www.googleapis.com/auth/calendar.freebusy"], refresh_token=refresh_token, ) -with_slack_access = auth0_ai.with_federated_connection( +with_slack_access = auth0_ai.with_token_vault( connection="sign-in-with-slack", scopes=["channels:read"], refresh_token=refresh_token, ) -with_github_access = auth0_ai.with_federated_connection( +with_github_access = auth0_ai.with_token_vault( connection="github", scopes=["repo"], refresh_token=refresh_token, diff --git a/packages/auth0-ai-langchain/README.md b/packages/auth0-ai-langchain/README.md index 015047a..7e19f67 100644 --- a/packages/auth0-ai-langchain/README.md +++ b/packages/auth0-ai-langchain/README.md @@ -12,24 +12,24 @@ pip install auth0-ai-langchain ``` -## Async User Confirmation +## Async Authorization `Auth0AI` uses CIBA (Client-Initiated Backchannel Authentication) to handle user confirmation asynchronously. This is useful when you need to confirm a user action before proceeding with a tool execution. -Full Example of [Async User Confirmation](https://github.com/auth0-lab/auth0-ai-python/tree/main/examples/async-user-confirmation/langchain-examples). +Full Example of [Async Authorization](https://github.com/auth0-lab/auth0-ai-python/tree/main/examples/async-authorization/langchain-examples). 1. Define a tool with the proper authorizer specifying a function to resolve the user id: ```python from auth0_ai_langchain.auth0_ai import Auth0AI -from auth0_ai_langchain.ciba import get_ciba_credentials +from auth0_ai_langchain.async_authorization import get_async_authorization_credentials from langchain_core.runnables import ensure_config from langchain_core.tools import StructuredTool # If not provided, Auth0 settings will be read from env variables: `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET` auth0_ai = Auth0AI() -with_async_user_confirmation = auth0_ai.with_async_user_confirmation( +with_async_authorization = auth0_ai.with_async_authorization( scopes=["stock:trade"], audience=os.getenv("AUDIENCE"), requested_expiry=os.getenv("REQUESTED_EXPIRY"), @@ -40,14 +40,14 @@ with_async_user_confirmation = auth0_ai.with_async_user_confirmation( ) def tool_function(ticker: str, qty: int) -> str: - credentials = get_ciba_credentials() + credentials = get_async_authorization_credentials() headers = { "Authorization": f"{credentials["token_type"]} {credentials["access_token"]}", # ... } # Call API -trade_tool = with_async_user_confirmation( +trade_tool = with_async_authorization( StructuredTool( name="trade_tool", description="Use this function to trade a stock", @@ -59,13 +59,14 @@ trade_tool = with_async_user_confirmation( 2. Handle interruptions properly. For example, if user is not enrolled to MFA, it will throw an interruption. See [Handling Interrupts](#handling-interrupts) section. -### CIBA with RAR (Rich Authorization Requests) +### Async Authorization with RAR (Rich Authorization Requests) + `Auth0AI` supports RAR (Rich Authorization Requests) for CIBA. This allows you to provide additional authorization parameters to be displayed during the user confirmation request. When defining the tool authorizer, you can specify the `authorization_details` parameter to include detailed information about the authorization being requested: ```python -with_async_user_confirmation = auth0_ai.with_async_user_confirmation( +with_async_authorization = auth0_ai.with_async_authorization( scopes=["stock:trade"], audience=os.getenv("AUDIENCE"), requested_expiry=os.getenv("REQUESTED_EXPIRY"), @@ -87,6 +88,7 @@ with_async_user_confirmation = auth0_ai.with_async_user_confirmation( To use RAR with CIBA, you need to [set up authorization details](https://auth0.com/docs/get-started/apis/configure-rich-authorization-requests) in your Auth0 tenant. This includes defining the authorization request parameters and their types. Additionally, the [Guardian SDK](https://auth0.com/docs/secure/multi-factor-authentication/auth0-guardian) is required to handle these authorization details in your authorizer app. For more information on setting up RAR with CIBA, refer to: + - [Configure Rich Authorization Requests (RAR)](https://auth0.com/docs/get-started/apis/configure-rich-authorization-requests) - [User Authorization with CIBA](https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-initiated-backchannel-authentication-flow/user-authorization-with-ciba) @@ -151,7 +153,7 @@ buy_tool = StructuredTool( ## Calling APIs On User's Behalf -The `Auth0AI.with_federated_connection` function exchanges user's refresh token taken, by default, from the runnable configuration (`config.configurable._credentials.refresh_token`) for a Federated Connection API token. +The `Auth0AI.with_token_vault` function exchanges user's refresh token taken, by default, from the runnable configuration (`config.configurable._credentials.refresh_token`) for a Token Vault access token that is valid to call a third-party API. Full Example of [Calling APIs On User's Behalf](https://github.com/auth0-lab/auth0-ai-python/tree/main/examples/calling-apis/langchain-examples). @@ -159,13 +161,13 @@ Full Example of [Calling APIs On User's Behalf](https://github.com/auth0-lab/aut ```python from auth0_ai_langchain.auth0_ai import Auth0AI -from auth0_ai_langchain.federated_connections import get_credentials_for_connection +from auth0_ai_langchain.token_vault import get_credentials_from_token_vault from langchain_core.tools import StructuredTool # If not provided, Auth0 settings will be read from env variables: `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET` auth0_ai = Auth0AI() -with_google_calendar_access = auth0_ai.with_federated_connection( +with_google_calendar_access = auth0_ai.with_token_vault( connection="google-oauth2", scopes=["https://www.googleapis.com/auth/calendar.freebusy"], # Optional: @@ -174,7 +176,7 @@ with_google_calendar_access = auth0_ai.with_federated_connection( ) def tool_function(date: datetime): - credentials = get_credentials_for_connection() + credentials = get_credentials_from_token_vault() # Call Google API using credentials["access_token"] check_calendar_tool = with_google_calendar_access( @@ -301,7 +303,7 @@ For the specific case of **CIBA (Client-Initiated Backchannel Authorization)** y ```python import os -from auth0_ai_langchain.ciba import GraphResumer +from auth0_ai_langchain.async_authorization import GraphResumer from langgraph_sdk import get_client resumer = GraphResumer( diff --git a/packages/auth0-ai-langchain/auth0_ai_langchain/async_authorization/__init__.py b/packages/auth0-ai-langchain/auth0_ai_langchain/async_authorization/__init__.py new file mode 100644 index 0000000..af62cf2 --- /dev/null +++ b/packages/auth0-ai-langchain/auth0_ai_langchain/async_authorization/__init__.py @@ -0,0 +1,3 @@ +from auth0_ai.authorizers.async_authorization.async_authorizer_base import get_async_authorization_credentials as get_async_authorization_credentials +from auth0_ai_langchain.async_authorization.async_authorizer import AsyncAuthorizer as AsyncAuthorizer +from auth0_ai_langchain.async_authorization.graph_resumer import GraphResumer as GraphResumer \ No newline at end of file diff --git a/packages/auth0-ai-langchain/auth0_ai_langchain/ciba/ciba_authorizer.py b/packages/auth0-ai-langchain/auth0_ai_langchain/async_authorization/async_authorizer.py similarity index 69% rename from packages/auth0-ai-langchain/auth0_ai_langchain/ciba/ciba_authorizer.py rename to packages/auth0-ai-langchain/auth0_ai_langchain/async_authorization/async_authorizer.py index 0450254..e1f23f6 100644 --- a/packages/auth0-ai-langchain/auth0_ai_langchain/ciba/ciba_authorizer.py +++ b/packages/auth0-ai-langchain/auth0_ai_langchain/async_authorization/async_authorizer.py @@ -1,12 +1,12 @@ from abc import ABC from typing import Union -from auth0_ai.authorizers.ciba import CIBAAuthorizerBase -from auth0_ai.interrupts.ciba_interrupts import AuthorizationPendingInterrupt, AuthorizationPollingInterrupt +from auth0_ai.authorizers.async_authorization import AsyncAuthorizerBase +from auth0_ai.interrupts.async_authorization_interrupts import AuthorizationPendingInterrupt, AuthorizationPollingInterrupt from auth0_ai_langchain.utils.interrupt import to_graph_interrupt from auth0_ai_langchain.utils.tool_wrapper import tool_wrapper from langchain_core.tools import BaseTool -class CIBAAuthorizer(CIBAAuthorizerBase, ABC): +class AsyncAuthorizer(AsyncAuthorizerBase, ABC): def _handle_authorization_interrupts(self, err: Union[AuthorizationPendingInterrupt, AuthorizationPollingInterrupt]) -> None: raise to_graph_interrupt(err) diff --git a/packages/auth0-ai-langchain/auth0_ai_langchain/ciba/graph_resumer.py b/packages/auth0-ai-langchain/auth0_ai_langchain/async_authorization/graph_resumer.py similarity index 92% rename from packages/auth0-ai-langchain/auth0_ai_langchain/ciba/graph_resumer.py rename to packages/auth0-ai-langchain/auth0_ai_langchain/async_authorization/graph_resumer.py index 68f6a28..8ac4e46 100644 --- a/packages/auth0-ai-langchain/auth0_ai_langchain/ciba/graph_resumer.py +++ b/packages/auth0-ai-langchain/auth0_ai_langchain/async_authorization/graph_resumer.py @@ -1,8 +1,8 @@ import asyncio from threading import Event from typing import Callable, Optional, Dict, Any, List, TypedDict -from auth0_ai.authorizers.ciba import CIBAAuthorizationRequest -from auth0_ai.interrupts.ciba_interrupts import CIBAInterrupt, AuthorizationPendingInterrupt, AuthorizationPollingInterrupt +from auth0_ai.authorizers.async_authorization import AsyncAuthorizationRequest +from auth0_ai.interrupts.async_authorization_interrupts import AsyncAuthorizationInterrupt, AuthorizationPendingInterrupt, AuthorizationPollingInterrupt from auth0_ai_langchain.utils.interrupt import get_auth0_interrupts from langgraph_sdk.client import LangGraphClient from langgraph_sdk.schema import Thread, Interrupt @@ -11,7 +11,7 @@ class WatchedThread(TypedDict): thread_id: str assistant_id: str interruption_id: str - auth_request: CIBAAuthorizationRequest + auth_request: AsyncAuthorizationRequest config: Dict[str, Any] last_run: float @@ -64,7 +64,7 @@ async def _get_all_interrupted_threads(self) -> List[Thread]: for t in page: interrupt = self._get_first_interrupt(t) - if interrupt and CIBAInterrupt.is_interrupt(interrupt["value"]) and CIBAInterrupt.has_request_data(interrupt["value"]): + if interrupt and AsyncAuthorizationInterrupt.is_interrupt(interrupt["value"]) and AsyncAuthorizationInterrupt.has_request_data(interrupt["value"]): interrupted_threads.append(t) offset += len(page) diff --git a/packages/auth0-ai-langchain/auth0_ai_langchain/auth0_ai.py b/packages/auth0-ai-langchain/auth0_ai_langchain/auth0_ai.py index c5a0300..d772e6c 100644 --- a/packages/auth0-ai-langchain/auth0_ai_langchain/auth0_ai.py +++ b/packages/auth0-ai-langchain/auth0_ai_langchain/auth0_ai.py @@ -1,10 +1,10 @@ from typing import Callable, Optional from langchain_core.tools import BaseTool -from auth0_ai.authorizers.ciba import CIBAAuthorizerParams -from auth0_ai.authorizers.federated_connection_authorizer import FederatedConnectionAuthorizerParams +from auth0_ai.authorizers.async_authorization import AsyncAuthorizerParams +from auth0_ai.authorizers.token_vault_authorizer import TokenVaultAuthorizerParams from auth0_ai.authorizers.types import Auth0ClientParams -from auth0_ai_langchain.ciba.ciba_authorizer import CIBAAuthorizer -from auth0_ai_langchain.federated_connections.federated_connection_authorizer import FederatedConnectionAuthorizer +from auth0_ai_langchain.async_authorization.async_authorizer import AsyncAuthorizer +from auth0_ai_langchain.token_vault.token_vault_authorizer import TokenVaultAuthorizer class Auth0AI: @@ -21,14 +21,14 @@ def __init__(self, auth0: Optional[Auth0ClientParams] = None): """ self.auth0 = auth0 - def with_async_user_confirmation(self, **params: CIBAAuthorizerParams) -> Callable[[BaseTool], BaseTool]: + def with_async_authorization(self, **params: AsyncAuthorizerParams) -> Callable[[BaseTool], BaseTool]: """Protects a tool with the CIBA (Client-Initiated Backchannel Authentication) flow. Requires user confirmation via a second device (e.g., phone) before allowing the tool to execute. Args: - **params: Parameters defined in `CIBAAuthorizerParams`. + **params: Parameters defined in `AsyncAuthorizerParams`. Returns: Callable[[BaseTool], BaseTool]: A decorator to wrap a LangChain tool. @@ -37,13 +37,13 @@ def with_async_user_confirmation(self, **params: CIBAAuthorizerParams) -> Callab ```python import os from auth0_ai_langchain.auth0_ai import Auth0AI - from auth0_ai_langchain.ciba import get_ciba_credentials + from auth0_ai_langchain.async_authorization import get_async_authorization_credentials from langchain_core.runnables import ensure_config from langchain_core.tools import StructuredTool auth0_ai = Auth0AI() - with_async_user_confirmation = auth0_ai.with_async_user_confirmation( + with_async_authorization = auth0_ai.with_async_authorization( scopes=["stock:trade"], audience=os.getenv("AUDIENCE"), requested_expiry=os.getenv("REQUESTED_EXPIRY"), @@ -52,14 +52,14 @@ def with_async_user_confirmation(self, **params: CIBAAuthorizerParams) -> Callab ) def tool_function(ticker: str, qty: int) -> str: - credentials = get_ciba_credentials() + credentials = get_async_authorization_credentials() headers = { "Authorization": f"{credentials['token_type']} {credentials['access_token']}", # ... } # Call API - trade_tool = with_async_user_confirmation( + trade_tool = with_async_authorization( StructuredTool( name="trade_tool", description="Use this function to trade a stock", @@ -68,16 +68,16 @@ def tool_function(ticker: str, qty: int) -> str: ) ``` """ - authorizer = CIBAAuthorizer(CIBAAuthorizerParams(**params), self.auth0) + authorizer = AsyncAuthorizer(AsyncAuthorizerParams(**params), self.auth0) return authorizer.authorizer() - def with_federated_connection(self, **params: FederatedConnectionAuthorizerParams) -> Callable[[BaseTool], BaseTool]: - """Enables a tool to obtain an access token from a federated identity provider (e.g., Google, Azure AD). + def with_token_vault(self, **params: TokenVaultAuthorizerParams) -> Callable[[BaseTool], BaseTool]: + """Enables a tool to obtain an access token from a Token Vault identity provider (e.g., Google, Azure AD). The token can then be used within the tool to call third-party APIs on behalf of the user. Args: - **params: Parameters defined in `FederatedConnectionAuthorizerParams`. + **params: Parameters defined in `TokenVaultAuthorizerParams`. Returns: Callable[[BaseTool], BaseTool]: A decorator to wrap a LangChain tool. @@ -85,19 +85,19 @@ def with_federated_connection(self, **params: FederatedConnectionAuthorizerParam Example: ```python from auth0_ai_langchain.auth0_ai import Auth0AI - from auth0_ai_langchain.federated_connections import get_credentials_for_connection + from auth0_ai_langchain.token_vault import get_credentials_from_token_vault from langchain_core.tools import StructuredTool from datetime import datetime auth0_ai = Auth0AI() - with_google_calendar_access = auth0_ai.with_federated_connection( + with_google_calendar_access = auth0_ai.with_token_vault( connection="google-oauth2", scopes=["https://www.googleapis.com/auth/calendar.freebusy"] ) def tool_function(date: datetime): - credentials = get_credentials_for_connection() + credentials = get_credentials_from_token_vault() # Call Google API using credentials["access_token"] check_calendar_tool = with_google_calendar_access( @@ -109,6 +109,6 @@ def tool_function(date: datetime): ) ``` """ - authorizer = FederatedConnectionAuthorizer( - FederatedConnectionAuthorizerParams(**params), self.auth0) + authorizer = TokenVaultAuthorizer( + TokenVaultAuthorizerParams(**params), self.auth0) return authorizer.authorizer() diff --git a/packages/auth0-ai-langchain/auth0_ai_langchain/ciba/__init__.py b/packages/auth0-ai-langchain/auth0_ai_langchain/ciba/__init__.py deleted file mode 100644 index f19e38c..0000000 --- a/packages/auth0-ai-langchain/auth0_ai_langchain/ciba/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from auth0_ai.authorizers.ciba.ciba_authorizer_base import get_ciba_credentials as get_ciba_credentials -from auth0_ai_langchain.ciba.ciba_authorizer import CIBAAuthorizer as CIBAAuthorizer -from auth0_ai_langchain.ciba.graph_resumer import GraphResumer as GraphResumer diff --git a/packages/auth0-ai-langchain/auth0_ai_langchain/federated_connections/__init__.py b/packages/auth0-ai-langchain/auth0_ai_langchain/federated_connections/__init__.py deleted file mode 100644 index 211ae64..0000000 --- a/packages/auth0-ai-langchain/auth0_ai_langchain/federated_connections/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from auth0_ai.interrupts.federated_connection_interrupt import ( - FederatedConnectionError as FederatedConnectionError, - FederatedConnectionInterrupt as FederatedConnectionInterrupt -) - -from auth0_ai.authorizers.federated_connection_authorizer import ( - get_credentials_for_connection as get_credentials_for_connection, - get_access_token_for_connection as get_access_token_for_connection -) -from .federated_connection_authorizer import FederatedConnectionAuthorizer as FederatedConnectionAuthorizer diff --git a/packages/auth0-ai-langchain/auth0_ai_langchain/federated_connections/federated_connection_authorizer.py b/packages/auth0-ai-langchain/auth0_ai_langchain/federated_connections/federated_connection_authorizer.py deleted file mode 100644 index 335b22c..0000000 --- a/packages/auth0-ai-langchain/auth0_ai_langchain/federated_connections/federated_connection_authorizer.py +++ /dev/null @@ -1,51 +0,0 @@ -import copy -from abc import ABC -from auth0_ai.authorizers.federated_connection_authorizer import FederatedConnectionAuthorizerBase, \ - FederatedConnectionAuthorizerParams -from auth0_ai.authorizers.types import Auth0ClientParams -from auth0_ai.interrupts.federated_connection_interrupt import FederatedConnectionInterrupt -from auth0_ai_langchain.utils.interrupt import to_graph_interrupt -from auth0_ai_langchain.utils.tool_wrapper import tool_wrapper -from langchain_core.tools import BaseTool -from langchain_core.runnables import ensure_config - - -async def default_get_refresh_token(*_, **__) -> str | None: - return ensure_config().get("configurable", {}).get("_credentials", {}).get("refresh_token") - -async def default_get_subject_access_token(*_, **__) -> str | None: - """ - Returns the Auth0 *user* access token (from the LC graph config) to be used - as the subject token in Token Vault exchange. - """ - return ensure_config().get("configurable", {}).get("_credentials", {}).get("access_token") - - -class FederatedConnectionAuthorizer(FederatedConnectionAuthorizerBase, ABC): - def __init__( - self, - params: FederatedConnectionAuthorizerParams, - auth0: Auth0ClientParams = None, - ): - missing_refresh = params.refresh_token.value is None - missing_subject_at = getattr(params, "subject_access_token", - None) is None or params.subject_access_token.value is None - - if missing_refresh and missing_subject_at: - params = copy.copy(params) - params.subject_access_token.value = default_get_subject_access_token - elif not missing_refresh and callable(default_get_refresh_token): - if params.refresh_token.value is None: - params = copy.copy(params) - params.refresh_token.value = default_get_refresh_token - - super().__init__(params, auth0) - - def _handle_authorization_interrupts(self, err: FederatedConnectionInterrupt) -> None: - raise to_graph_interrupt(err) - - def authorizer(self): - def wrap_tool(tool: BaseTool) -> BaseTool: - return tool_wrapper(tool, self.protect) - - return wrap_tool diff --git a/packages/auth0-ai-langchain/auth0_ai_langchain/token_vault/__init__.py b/packages/auth0-ai-langchain/auth0_ai_langchain/token_vault/__init__.py new file mode 100644 index 0000000..36c3814 --- /dev/null +++ b/packages/auth0-ai-langchain/auth0_ai_langchain/token_vault/__init__.py @@ -0,0 +1,10 @@ +from auth0_ai.interrupts.token_vault_interrupt import ( + TokenVaultError as TokenVaultError, + TokenVaultInterrupt as TokenVaultInterrupt +) + +from auth0_ai.authorizers.token_vault_authorizer import ( + get_credentials_from_token_vault as get_credentials_from_token_vault, + get_access_token_from_token_vault as get_access_token_from_token_vault +) +from .token_vault_authorizer import TokenVaultAuthorizer as TokenVaultAuthorizer diff --git a/packages/auth0-ai-langchain/auth0_ai_langchain/token_vault/token_vault_authorizer.py b/packages/auth0-ai-langchain/auth0_ai_langchain/token_vault/token_vault_authorizer.py new file mode 100644 index 0000000..c71f093 --- /dev/null +++ b/packages/auth0-ai-langchain/auth0_ai_langchain/token_vault/token_vault_authorizer.py @@ -0,0 +1,38 @@ +import copy +from abc import ABC +from auth0_ai.authorizers.token_vault_authorizer import TokenVaultAuthorizerBase, \ + TokenVaultAuthorizerParams +from auth0_ai.authorizers.types import Auth0ClientParams +from auth0_ai.interrupts.token_vault_interrupt import TokenVaultInterrupt +from auth0_ai_langchain.utils.interrupt import to_graph_interrupt +from auth0_ai_langchain.utils.tool_wrapper import tool_wrapper +from langchain_core.tools import BaseTool +from langchain_core.runnables import ensure_config + + +async def default_get_refresh_token(*_, **__) -> str | None: + return ensure_config().get("configurable", {}).get("_credentials", {}).get("refresh_token") + +class TokenVaultAuthorizer(TokenVaultAuthorizerBase, ABC): + def __init__( + self, + params: TokenVaultAuthorizerParams, + auth0: Auth0ClientParams = None, + ): + missing_refresh = params.refresh_token.value is None + missing_access_token = params.access_token.value is None + + if missing_refresh and missing_access_token and callable(default_get_refresh_token): + params = copy.copy(params) + params.refresh_token.value = default_get_refresh_token + + super().__init__(params, auth0) + + def _handle_authorization_interrupts(self, err: TokenVaultInterrupt) -> None: + raise to_graph_interrupt(err) + + def authorizer(self): + def wrap_tool(tool: BaseTool) -> BaseTool: + return tool_wrapper(tool, self.protect) + + return wrap_tool diff --git a/packages/auth0-ai-llamaindex/README.md b/packages/auth0-ai-llamaindex/README.md index bde8992..33ce59f 100644 --- a/packages/auth0-ai-llamaindex/README.md +++ b/packages/auth0-ai-llamaindex/README.md @@ -12,23 +12,23 @@ pip install auth0-ai-llamaindex ``` -## Async User Confirmation +## Async Authorization `Auth0AI` uses CIBA (Client Initiated Backchannel Authentication) to handle user confirmation asynchronously. This is useful when you need to confirm a user action before proceeding with a tool execution. -Full Example of [Async User Confirmation](https://github.com/auth0-lab/auth0-ai-python/tree/main/examples/async-user-confirmation/llama-index-examples). +Full Example of [Async Authorization](https://github.com/auth0-lab/auth0-ai-python/tree/main/examples/async-authorization/llama-index-examples). Define a tool with the proper authorizer specifying a function to resolve the user id: ```python from auth0_ai_llamaindex.auth0_ai import Auth0AI, set_ai_context -from auth0_ai_llamaindex.ciba import get_ciba_credentials +from auth0_ai_llamaindex.async_authorization import get_async_authorization_credentials from llama_index.core.tools import FunctionTool # If not provided, Auth0 settings will be read from env variables: `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET` auth0_ai = Auth0AI() -with_async_user_confirmation = auth0_ai.with_async_user_confirmation( +with_async_authorization = auth0_ai.with_async_authorization( scopes=["stock:trade"], audience=os.getenv("AUDIENCE"), requested_expiry=os.getenv("REQUESTED_EXPIRY"), @@ -39,14 +39,14 @@ with_async_user_confirmation = auth0_ai.with_async_user_confirmation( ) def tool_function(ticker: str, qty: int) -> str: - credentials = get_ciba_credentials() + credentials = get_async_authorization_credentials() headers = { "Authorization": f"{credentials["token_type"]} {credentials["access_token"]}", # ... } # Call API -trade_tool = with_async_user_confirmation( +trade_tool = with_async_authorization( FunctionTool.from_defaults( name="trade_tool", description="Use this function to trade a stock", @@ -59,13 +59,14 @@ trade_tool = with_async_user_confirmation( set_ai_context("") ``` -### CIBA with RAR (Rich Authorization Requests) -`Auth0AI` supports RAR (Rich Authorization Requests) for CIBA. This allows you to provide additional authorization parameters to be displayed during the user confirmation request. +### Async Authorization with RAR (Rich Authorization Requests) + +`Auth0AI` supports RAR (Rich Authorization Requests) for Async Authorization. This allows you to provide additional authorization parameters to be displayed during the user confirmation request. When defining the tool authorizer, you can specify the `authorization_details` parameter to include detailed information about the authorization being requested: ```python -with_async_user_confirmation = auth0_ai.with_async_user_confirmation( +with_async_authorization = auth0_ai.with_async_authorization( scopes=["stock:trade"], audience=os.getenv("AUDIENCE"), requested_expiry=os.getenv("REQUESTED_EXPIRY"), @@ -87,6 +88,7 @@ with_async_user_confirmation = auth0_ai.with_async_user_confirmation( To use RAR with CIBA, you need to [set up authorization details](https://auth0.com/docs/get-started/apis/configure-rich-authorization-requests) in your Auth0 tenant. This includes defining the authorization request parameters and their types. Additionally, the [Guardian SDK](https://auth0.com/docs/secure/multi-factor-authentication/auth0-guardian) is required to handle these authorization details in your authorizer app. For more information on setting up RAR with CIBA, refer to: + - [Configure Rich Authorization Requests (RAR)](https://auth0.com/docs/get-started/apis/configure-rich-authorization-requests) - [User Authorization with CIBA](https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-initiated-backchannel-authentication-flow/user-authorization-with-ciba) @@ -148,7 +150,7 @@ return FunctionTool.from_defaults( ## Calling APIs On User's Behalf -The `Auth0AI.with_federated_connection` function exchanges user's refresh token for a Federated Connection API access token. +The `Auth0AI.with_token_vault` function exchanges user's refresh token for a Token Vault access token with the third-party. Full Example of [Calling APIs On User's Behalf](https://github.com/auth0-lab/auth0-ai-python/tree/main/examples/calling-apis/llama-index-examples). @@ -156,13 +158,13 @@ Define a tool with the proper authorizer specifying a function to resolve the us ```python from auth0_ai_llamaindex.auth0_ai import Auth0AI, set_ai_context -from auth0_ai_llamaindex.federated_connections import get_credentials_for_connection +from auth0_ai_llamaindex.token_vault import get_credentials_from_token_vault from llama_index.core.tools import FunctionTool # If not provided, Auth0 settings will be read from env variables: `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET` auth0_ai = Auth0AI() -with_google_calendar_access = auth0_ai.with_federated_connection( +with_google_calendar_access = auth0_ai.with_token_vault( connection="google-oauth2", scopes=["https://www.googleapis.com/auth/calendar.freebusy"], refresh_token=lambda *_args, **_kwargs: session["user"]["refresh_token"], @@ -171,7 +173,7 @@ with_google_calendar_access = auth0_ai.with_federated_connection( ) def tool_function(date: datetime): - credentials = get_credentials_for_connection() + credentials = get_credentials_from_token_vault() # Call Google API using credentials["access_token"] check_calendar_tool = with_google_calendar_access( diff --git a/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/async_authorization/__init__.py b/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/async_authorization/__init__.py new file mode 100644 index 0000000..be51c4c --- /dev/null +++ b/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/async_authorization/__init__.py @@ -0,0 +1,2 @@ +from auth0_ai.authorizers.async_authorization.async_authorizer_base import get_async_authorization_credentials as get_async_authorization_credentials +from auth0_ai_llamaindex.async_authorization.async_authorizer import AsyncAuthorizer as AsyncAuthorizer \ No newline at end of file diff --git a/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/ciba/ciba_authorizer.py b/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/async_authorization/async_authorizer.py similarity index 71% rename from packages/auth0-ai-llamaindex/auth0_ai_llamaindex/ciba/ciba_authorizer.py rename to packages/auth0-ai-llamaindex/auth0_ai_llamaindex/async_authorization/async_authorizer.py index 71122bd..0aad299 100644 --- a/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/ciba/ciba_authorizer.py +++ b/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/async_authorization/async_authorizer.py @@ -1,9 +1,9 @@ from abc import ABC -from auth0_ai.authorizers.ciba import CIBAAuthorizerBase +from auth0_ai.authorizers.async_authorization import AsyncAuthorizerBase from auth0_ai_llamaindex.utils.tool_wrapper import tool_wrapper from llama_index.core.tools import FunctionTool -class CIBAAuthorizer(CIBAAuthorizerBase, ABC): +class AsyncAuthorizer(AsyncAuthorizerBase, ABC): def authorizer(self): def wrap_tool(tool: FunctionTool) -> FunctionTool: return tool_wrapper(tool, self.protect) diff --git a/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/auth0_ai.py b/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/auth0_ai.py index 36c3d92..4c7451f 100644 --- a/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/auth0_ai.py +++ b/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/auth0_ai.py @@ -1,10 +1,10 @@ from typing import Callable, Optional from llama_index.core.tools import FunctionTool -from auth0_ai.authorizers.ciba import CIBAAuthorizerParams -from auth0_ai.authorizers.federated_connection_authorizer import FederatedConnectionAuthorizerParams +from auth0_ai.authorizers.async_authorization import AsyncAuthorizerParams +from auth0_ai.authorizers.token_vault_authorizer import TokenVaultAuthorizerParams from auth0_ai.authorizers.types import Auth0ClientParams -from auth0_ai_llamaindex.ciba.ciba_authorizer import CIBAAuthorizer -from auth0_ai_llamaindex.federated_connections.federated_connection_authorizer import FederatedConnectionAuthorizer +from auth0_ai_llamaindex.async_authorization.async_authorizer import AsyncAuthorizer +from auth0_ai_llamaindex.token_vault.token_vault_authorizer import TokenVaultAuthorizer from auth0_ai_llamaindex.context import set_ai_context @@ -22,13 +22,13 @@ def __init__(self, auth0: Optional[Auth0ClientParams] = None): """ self.auth0 = auth0 - def with_federated_connection(self, **params: FederatedConnectionAuthorizerParams) -> Callable[[FunctionTool], FunctionTool]: - """Enables a tool to obtain an access token from a federated identity provider (e.g., Google, Azure AD). + def with_token_vault(self, **params: TokenVaultAuthorizerParams) -> Callable[[FunctionTool], FunctionTool]: + """Enables a tool to obtain an access token from a Token Vault identity provider (e.g., Google, Azure AD). The token can then be used within the tool to call third-party APIs on behalf of the user. Args: - **params: Parameters defined in `FederatedConnectionAuthorizerParams`. + **params: Parameters defined in `TokenVaultAuthorizerParams`. Returns: Callable[[FunctionTool], FunctionTool]: A decorator to wrap a LlamaIndex tool. @@ -36,20 +36,20 @@ def with_federated_connection(self, **params: FederatedConnectionAuthorizerParam Example: ```python from auth0_ai_llamaindex.auth0_ai import Auth0AI - from auth0_ai_llamaindex.federated_connections import get_credentials_for_connection + from auth0_ai_llamaindex.token_vault import get_credentials_from_token_vault from llama_index.core.tools import FunctionTool from datetime import datetime auth0_ai = Auth0AI() - with_google_calendar_access = auth0_ai.with_federated_connection( + with_google_calendar_access = auth0_ai.with_token_vault( connection="google-oauth2", scopes=["https://www.googleapis.com/auth/calendar.freebusy"], refresh_token=lambda *_args, **_kwargs: session["user"]["refresh_token"], ) def tool_function(date: datetime): - credentials = get_credentials_for_connection() + credentials = get_credentials_from_token_vault() # Call Google API using credentials["access_token"] check_calendar_tool = with_google_calendar_access( @@ -61,18 +61,18 @@ def tool_function(date: datetime): ) ``` """ - authorizer = FederatedConnectionAuthorizer( - FederatedConnectionAuthorizerParams(**params), self.auth0) + authorizer = TokenVaultAuthorizer( + TokenVaultAuthorizerParams(**params), self.auth0) return authorizer.authorizer() - def with_async_user_confirmation(self, **params: CIBAAuthorizerParams) -> Callable[[FunctionTool], FunctionTool]: + def with_async_authorization(self, **params: AsyncAuthorizerParams) -> Callable[[FunctionTool], FunctionTool]: """Protects a tool with the CIBA (Client-Initiated Backchannel Authentication) flow. Requires user confirmation via a second device (e.g., phone) before allowing the tool to execute. Args: - **params: Parameters defined in `CIBAAuthorizerParams`. + **params: Parameters defined in `AsyncAuthorizerParams`. Returns: Callable[[FunctionTool], FunctionTool]: A decorator to wrap a LlamaIndex tool. @@ -81,12 +81,12 @@ def with_async_user_confirmation(self, **params: CIBAAuthorizerParams) -> Callab ```python import os from auth0_ai_llamaindex.auth0_ai import Auth0AI - from auth0_ai_llamaindex.ciba import get_ciba_credentials + from auth0_ai_llamaindex.async_authorization import get_async_authorization_credentials from llama_index.core.tools import FunctionTool auth0_ai = Auth0AI() - with_async_user_confirmation = auth0_ai.with_async_user_confirmation( + with_async_authorization = auth0_ai.with_async_authorization( scopes=["stock:trade"], audience=os.getenv("AUDIENCE"), requested_expiry=os.getenv("REQUESTED_EXPIRY"), @@ -95,14 +95,14 @@ def with_async_user_confirmation(self, **params: CIBAAuthorizerParams) -> Callab ) def tool_function(ticker: str, qty: int) -> str: - credentials = get_ciba_credentials() + credentials = get_async_authorization_credentials() headers = { "Authorization": f"{credentials['token_type']} {credentials['access_token']}", # ... } # Call API - trade_tool = with_async_user_confirmation( + trade_tool = with_async_authorization( FunctionTool.from_defaults( name="trade_tool", description="Use this function to trade a stock", @@ -111,7 +111,7 @@ def tool_function(ticker: str, qty: int) -> str: ) ``` """ - authorizer = CIBAAuthorizer(CIBAAuthorizerParams(**params), self.auth0) + authorizer = AsyncAuthorizer(AsyncAuthorizerParams(**params), self.auth0) return authorizer.authorizer() diff --git a/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/ciba/__init__.py b/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/ciba/__init__.py deleted file mode 100644 index b72e543..0000000 --- a/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/ciba/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from auth0_ai.authorizers.ciba.ciba_authorizer_base import get_ciba_credentials as get_ciba_credentials -from auth0_ai_llamaindex.ciba.ciba_authorizer import CIBAAuthorizer as CIBAAuthorizer diff --git a/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/federated_connections/__init__.py b/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/federated_connections/__init__.py deleted file mode 100644 index 01c9073..0000000 --- a/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/federated_connections/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from auth0_ai.interrupts.federated_connection_interrupt import ( - FederatedConnectionError as FederatedConnectionError, - FederatedConnectionInterrupt as FederatedConnectionInterrupt -) - -from auth0_ai.authorizers.federated_connection_authorizer import ( - get_credentials_for_connection as get_credentials_for_connection, - get_access_token_for_connection as get_access_token_for_connection -) -from auth0_ai_llamaindex.federated_connections.federated_connection_authorizer import FederatedConnectionAuthorizer as FederatedConnectionAuthorizer diff --git a/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/token_vault/__init__.py b/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/token_vault/__init__.py new file mode 100644 index 0000000..7ef88ef --- /dev/null +++ b/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/token_vault/__init__.py @@ -0,0 +1,10 @@ +from auth0_ai.interrupts.token_vault_interrupt import ( + TokenVaultError as TokenVaultError, + TokenVaultInterrupt as TokenVaultInterrupt +) + +from auth0_ai.authorizers.token_vault_authorizer import ( + get_credentials_from_token_vault as get_credentials_from_token_vault, + get_access_token_from_token_vault as get_access_token_from_token_vault +) +from auth0_ai_llamaindex.token_vault.token_vault_authorizer import TokenVaultAuthorizer as TokenVaultAuthorizer diff --git a/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/federated_connections/federated_connection_authorizer.py b/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/token_vault/token_vault_authorizer.py similarity index 64% rename from packages/auth0-ai-llamaindex/auth0_ai_llamaindex/federated_connections/federated_connection_authorizer.py rename to packages/auth0-ai-llamaindex/auth0_ai_llamaindex/token_vault/token_vault_authorizer.py index aae98e0..0bec862 100644 --- a/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/federated_connections/federated_connection_authorizer.py +++ b/packages/auth0-ai-llamaindex/auth0_ai_llamaindex/token_vault/token_vault_authorizer.py @@ -2,19 +2,19 @@ from abc import ABC -from auth0_ai.authorizers.federated_connection_authorizer import ( - FederatedConnectionAuthorizerBase, - FederatedConnectionAuthorizerParams, +from auth0_ai.authorizers.token_vault_authorizer import ( + TokenVaultAuthorizerBase, + TokenVaultAuthorizerParams, ) from auth0_ai.authorizers.types import Auth0ClientParams from auth0_ai_llamaindex.utils.tool_wrapper import tool_wrapper from llama_index.core.tools import FunctionTool -class FederatedConnectionAuthorizer(FederatedConnectionAuthorizerBase, ABC): +class TokenVaultAuthorizer(TokenVaultAuthorizerBase, ABC): def __init__( self, - params: FederatedConnectionAuthorizerParams, + params: TokenVaultAuthorizerParams, auth0: Auth0ClientParams = None, ): super().__init__(params, auth0) diff --git a/packages/auth0-ai/README.md b/packages/auth0-ai/README.md index 9fc4cb3..cdfeb0c 100644 --- a/packages/auth0-ai/README.md +++ b/packages/auth0-ai/README.md @@ -2,7 +2,7 @@ `auth0-ai` is an SDK for building secure AI-powered applications using [Auth0](https://www.auth0.ai/). -This SDK provides base abstractions for authentication and authorization in AI applications including a set of tools for implementing [asynchronous user authentication](https://demo.auth0.ai/docs/async-user-confirmation) using the [Client Initiated Backchannel Authentication (CIBA)](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html) protocol. +This SDK provides base abstractions for authentication and authorization in AI applications including a set of tools for implementing [asynchronous authorization](https://auth0.com/ai/docs/intro/asynchronous-authorization) using the [Client Initiated Backchannel Authentication (CIBA)](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html) protocol. ![Release](https://img.shields.io/pypi/v/auth0-ai) ![Downloads](https://img.shields.io/pypi/dw/auth0-ai) [![License](https://img.shields.io/:license-APACHE%202.0-blue.svg?style=flat)](https://opensource.org/license/apache-2-0) diff --git a/packages/auth0-ai/auth0_ai/authorizers/async_auth/__init__.py b/packages/auth0-ai/auth0_ai/authorizers/async_auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/auth0-ai/auth0_ai/authorizers/async_authorization/__init__.py b/packages/auth0-ai/auth0_ai/authorizers/async_authorization/__init__.py new file mode 100644 index 0000000..8404171 --- /dev/null +++ b/packages/auth0-ai/auth0_ai/authorizers/async_authorization/__init__.py @@ -0,0 +1,3 @@ +from .async_authorization_request import AsyncAuthorizationRequest as AsyncAuthorizationRequest +from .async_authorizer_params import AsyncAuthorizerParams as AsyncAuthorizerParams +from .async_authorizer_base import AsyncAuthorizerBase as AsyncAuthorizerBase \ No newline at end of file diff --git a/packages/auth0-ai/auth0_ai/authorizers/ciba/ciba_authorization_request.py b/packages/auth0-ai/auth0_ai/authorizers/async_authorization/async_authorization_request.py similarity index 92% rename from packages/auth0-ai/auth0_ai/authorizers/ciba/ciba_authorization_request.py rename to packages/auth0-ai/auth0_ai/authorizers/async_authorization/async_authorization_request.py index a115c76..1507581 100644 --- a/packages/auth0-ai/auth0_ai/authorizers/ciba/ciba_authorization_request.py +++ b/packages/auth0-ai/auth0_ai/authorizers/async_authorization/async_authorization_request.py @@ -1,6 +1,6 @@ from typing import TypedDict -class CIBAAuthorizationRequest(TypedDict): +class AsyncAuthorizationRequest(TypedDict): """ Attributes: id (str): The authorization request ID. Use this ID to check the status of the authorization request. diff --git a/packages/auth0-ai/auth0_ai/authorizers/ciba/ciba_authorizer_base.py b/packages/auth0-ai/auth0_ai/authorizers/async_authorization/async_authorizer_base.py similarity index 88% rename from packages/auth0-ai/auth0_ai/authorizers/ciba/ciba_authorizer_base.py rename to packages/auth0-ai/auth0_ai/authorizers/async_authorization/async_authorizer_base.py index 39c5a09..673dca8 100644 --- a/packages/auth0-ai/auth0_ai/authorizers/ciba/ciba_authorizer_base.py +++ b/packages/auth0-ai/auth0_ai/authorizers/async_authorization/async_authorizer_base.py @@ -12,10 +12,10 @@ from auth0.authentication.back_channel_login import BackChannelLogin from auth0.authentication.get_token import GetToken from auth0_ai.credentials import TokenResponse -from auth0_ai.authorizers.ciba.ciba_authorizer_params import CIBAAuthorizerParams -from auth0_ai.authorizers.ciba.ciba_authorization_request import CIBAAuthorizationRequest +from auth0_ai.authorizers.async_authorization.async_authorizer_params import AsyncAuthorizerParams +from auth0_ai.authorizers.async_authorization.async_authorization_request import AsyncAuthorizationRequest from auth0_ai.authorizers.types import Auth0ClientParams, ToolInput -from auth0_ai.interrupts.ciba_interrupts import AccessDeniedInterrupt, AuthorizationPendingInterrupt, AuthorizationPollingInterrupt, AuthorizationRequestExpiredInterrupt, InvalidGrantInterrupt, UserDoesNotHavePushNotificationsInterrupt +from auth0_ai.interrupts.async_authorization_interrupts import AccessDeniedInterrupt, AuthorizationPendingInterrupt, AuthorizationPollingInterrupt, AuthorizationRequestExpiredInterrupt, InvalidGrantInterrupt, UserDoesNotHavePushNotificationsInterrupt from auth0_ai.stores import SubStore, InMemoryStore from auth0_ai.authorizers.context import ns_from_context, ContextGetter from auth0_ai.utils import omit @@ -23,7 +23,7 @@ class AsyncStorageValue(TypedDict): context: Any credentials: Optional[TokenResponse] - # The namespace in the Store for the CIBA authorization response. + # The namespace in the Store for the Async Auth authorization response. auth_request_ns: Sequence[str]; _local_storage: contextvars.ContextVar[Optional[AsyncStorageValue]] = contextvars.ContextVar("local_storage", default=None) @@ -31,7 +31,7 @@ class AsyncStorageValue(TypedDict): def _get_local_storage() -> AsyncStorageValue: store = _local_storage.get() if store is None: - raise RuntimeError("The tool must be wrapped with the with_async_user_confirmation function.") + raise RuntimeError("The tool must be wrapped with the with_async_authorization function.") return store def _update_local_storage(data: AsyncStorageValue) -> None: @@ -43,14 +43,14 @@ def _update_local_storage(data: AsyncStorageValue) -> None: @asynccontextmanager async def _run_with_local_storage(data: AsyncStorageValue): if _local_storage.get() is not None: - raise RuntimeError("Cannot nest tool calls that require CIBA authorization.") + raise RuntimeError("Cannot nest tool calls that require Async Authorization.") token = _local_storage.set(data) try: yield finally: _local_storage.reset(token) -def get_ciba_credentials() -> TokenResponse | None: +def get_async_authorization_credentials() -> TokenResponse | None: local_store = _get_local_storage() return local_store.get("credentials") @@ -59,8 +59,8 @@ def _ensure_openid_scope(scopes: list[str]) -> str: scopes.insert(0, "openid") return " ".join(scopes) -class CIBAAuthorizerBase(Generic[ToolInput]): - def __init__(self, params: CIBAAuthorizerParams[ToolInput], auth0: Auth0ClientParams = None): +class AsyncAuthorizerBase(Generic[ToolInput]): + def __init__(self, params: AsyncAuthorizerParams[ToolInput], auth0: Auth0ClientParams = None): auth0 = { "domain": (auth0 or {}).get("domain", os.getenv("AUTH0_DOMAIN")), "client_id": (auth0 or {}).get("client_id", os.getenv("AUTH0_CLIENT_ID")), @@ -81,13 +81,13 @@ def __init__(self, params: CIBAAuthorizerParams[ToolInput], auth0: Auth0ClientPa self.params = params # TODO: consider moving this to Auth0AI classes - ciba_store = SubStore(params["store"] if "store" in params else InMemoryStore()).create_sub_store("AUTH0_AI_CIBA") + async_authorization_store = SubStore(params["store"] if "store" in params else InMemoryStore()).create_sub_store("AUTH0_AI_ASYNC_AUTHORIZATION") - self.auth_request_store = SubStore[CIBAAuthorizationRequest](ciba_store, { + self.auth_request_store = SubStore[AsyncAuthorizationRequest](async_authorization_store, { "get_ttl": lambda auth_request: auth_request["expires_in"] * 1000 if "expires_in" in auth_request else None }) - self.credentials_store = SubStore[TokenResponse](ciba_store, { + self.credentials_store = SubStore[TokenResponse](async_authorization_store, { "get_ttl": lambda credential: credential["expires_in"] * 1000 if "expires_in" in credential else None }) @@ -137,12 +137,12 @@ async def _get_authorize_params(self, *args: ToolInput.args, **kwargs: ToolInput return authorize_params - async def _start(self, authorize_params) -> CIBAAuthorizationRequest: + async def _start(self, authorize_params) -> AsyncAuthorizationRequest: requested_at = time.time() try: response = self.back_channel_login.back_channel_login(**authorize_params) - return CIBAAuthorizationRequest( + return AsyncAuthorizationRequest( id=response["auth_req_id"], requested_at=requested_at, expires_in=response["expires_in"], @@ -179,7 +179,7 @@ def _extract_retry_after_header(self, error: Auth0Error) -> Optional[int]: # If the retry-after value is not a valid integer, return None return None - def _get_credentials_internal(self, auth_request: CIBAAuthorizationRequest) -> TokenResponse | None: + def _get_credentials_internal(self, auth_request: AsyncAuthorizationRequest) -> TokenResponse | None: try: # Calculate elapsed time in seconds elapsed_seconds = datetime.now().timestamp() - auth_request["requested_at"] @@ -219,10 +219,10 @@ def _get_credentials_internal(self, auth_request: CIBAAuthorizationRequest) -> T raise - def _get_credentials(self, auth_request: CIBAAuthorizationRequest) -> TokenResponse | None: + def _get_credentials(self, auth_request: AsyncAuthorizationRequest) -> TokenResponse | None: return self._get_credentials_internal(auth_request) - async def get_credentials_polling(self, auth_request: CIBAAuthorizationRequest) -> TokenResponse | None: + async def get_credentials_polling(self, auth_request: AsyncAuthorizationRequest) -> TokenResponse | None: credentials: TokenResponse | None = None while not credentials: diff --git a/packages/auth0-ai/auth0_ai/authorizers/ciba/ciba_authorizer_params.py b/packages/auth0-ai/auth0_ai/authorizers/async_authorization/async_authorizer_params.py similarity index 96% rename from packages/auth0-ai/auth0_ai/authorizers/ciba/ciba_authorizer_params.py rename to packages/auth0-ai/auth0_ai/authorizers/async_authorization/async_authorizer_params.py index 564bdd8..d3488df 100644 --- a/packages/auth0-ai/auth0_ai/authorizers/ciba/ciba_authorizer_params.py +++ b/packages/auth0-ai/auth0_ai/authorizers/async_authorization/async_authorizer_params.py @@ -5,9 +5,9 @@ from auth0_ai.stores import Store -class CIBAAuthorizerParams(TypedDict, Generic[ToolInput]): +class AsyncAuthorizerParams(TypedDict, Generic[ToolInput]): """ - Authorize Options to start CIBA flow. + Authorize Options to start Async Authorization flow. Attributes: scopes (list[str]): The scopes to request authorization for. diff --git a/packages/auth0-ai/auth0_ai/authorizers/ciba/__init__.py b/packages/auth0-ai/auth0_ai/authorizers/ciba/__init__.py deleted file mode 100644 index 7811570..0000000 --- a/packages/auth0-ai/auth0_ai/authorizers/ciba/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .ciba_authorization_request import CIBAAuthorizationRequest as CIBAAuthorizationRequest -from .ciba_authorizer_params import CIBAAuthorizerParams as CIBAAuthorizerParams -from .ciba_authorizer_base import CIBAAuthorizerBase as CIBAAuthorizerBase diff --git a/packages/auth0-ai/auth0_ai/authorizers/federated_connection_authorizer.py b/packages/auth0-ai/auth0_ai/authorizers/token_vault_authorizer.py similarity index 87% rename from packages/auth0-ai/auth0_ai/authorizers/federated_connection_authorizer.py rename to packages/auth0-ai/auth0_ai/authorizers/token_vault_authorizer.py index c592abe..90c37f9 100644 --- a/packages/auth0-ai/auth0_ai/authorizers/federated_connection_authorizer.py +++ b/packages/auth0-ai/auth0_ai/authorizers/token_vault_authorizer.py @@ -11,14 +11,14 @@ from auth0_ai.authorizers.types import Auth0ClientParams, AuthorizerToolParameter, ToolInput from auth0_ai.credentials import TokenResponse from auth0_ai.interrupts.auth0_interrupt import Auth0Interrupt -from auth0_ai.interrupts.federated_connection_interrupt import FederatedConnectionError, FederatedConnectionInterrupt +from auth0_ai.interrupts.token_vault_interrupt import TokenVaultError, TokenVaultInterrupt from auth0_ai.stores import Store, SubStore, InMemoryStore from auth0_ai.utils import omit # Subject / requested token type constants SUBJECT_TYPE_REFRESH_TOKEN = "urn:ietf:params:oauth:token-type:refresh_token" SUBJECT_TYPE_ACCESS_TOKEN = "urn:ietf:params:oauth:token-type:access_token" -REQUESTED_TOKEN_TYPE_FEDERATED_CONNECTION_ACCESS_TOKEN = "http://auth0.com/oauth/token-type/federated-connection-access-token" +REQUESTED_TOKEN_TYPE_TOKEN_VAULT_ACCESS_TOKEN = "http://auth0.com/oauth/token-type/federated-connection-access-token" class AsyncStorageValue(TypedDict): context: Any @@ -32,7 +32,7 @@ class AsyncStorageValue(TypedDict): def _get_local_storage() -> AsyncStorageValue: store = _local_storage.get() if store is None: - raise RuntimeError("The tool must be wrapped with the with_federated_connection function.") + raise RuntimeError("The tool must be wrapped with the with_token_vault function.") return store def _update_local_storage(data: AsyncStorageValue) -> None: @@ -44,22 +44,22 @@ def _update_local_storage(data: AsyncStorageValue) -> None: @asynccontextmanager async def _run_with_local_storage(data: AsyncStorageValue): if _local_storage.get() is not None: - raise RuntimeError("Cannot nest tool calls that require federated connection authorization.") + raise RuntimeError("Cannot nest tool calls that require Token Vault authorization.") token = _local_storage.set(data) try: yield finally: _local_storage.reset(token) -def get_credentials_for_connection() -> TokenResponse | None: +def get_credentials_from_token_vault() -> TokenResponse | None: store = _get_local_storage() return store.get("credentials") -def get_access_token_for_connection() -> str | None: +def get_access_token_from_token_vault() -> str | None: store = _get_local_storage() return store.get("credentials", {}).get("access_token") -class FederatedConnectionAuthorizerParams(Generic[ToolInput]): +class TokenVaultAuthorizerParams(Generic[ToolInput]): def __init__( self, scopes: list[str], @@ -79,12 +79,12 @@ def __init__( credentials_context: Optional[AuthContext] = "thread" ): """ - Parameters for the federated connection authorizer. + Parameters for the Token Vault authorizer. Args: - scopes: The scopes required in the access token of the federated connection provider. - connection: The connection name of the federated connection provider. - refresh_token: Optional. The Auth0 refresh token to exchange for a federated connection access token. Can be: + scopes: The scopes required in the access token of the Token Vault provider. + connection: The connection name of the Token Vault provider. + refresh_token: Optional. The Auth0 refresh token to exchange for a Token Vault access token. Can be: - A string or None - A callable that receives the tool input and returns the user refresh token (sync or async) access_token: Optional. The Auth0 user access token (subject token) to exchange instead of a refresh token. Can be: @@ -110,10 +110,10 @@ def wrap(val, result_type): self.store = store self.credentials_context = credentials_context -class FederatedConnectionAuthorizerBase(Generic[ToolInput]): +class TokenVaultAuthorizerBase(Generic[ToolInput]): def __init__( self, - params: FederatedConnectionAuthorizerParams[ToolInput], + params: TokenVaultAuthorizerParams[ToolInput], config: Auth0ClientParams = None, ): self.params = params @@ -133,7 +133,7 @@ def __init__( self.get_token = GetToken(**self.auth0) # TODO: consider moving this to Auth0AI classes - sub_store = SubStore(params.store or InMemoryStore()).create_sub_store("AUTH0_AI_FEDERATED_CONNECTION") + sub_store = SubStore(params.store or InMemoryStore()).create_sub_store("AUTH0_AI_TOKEN_VAULT") instance_id = self._get_instance_id() self.credentials_store = SubStore[TokenResponse](sub_store, { @@ -174,8 +174,8 @@ def validate_token(self, token_response: Optional[TokenResponse] = None): connection = store["connection"] if token_response is None: - raise FederatedConnectionInterrupt( - f"Authorization required to access the Federated Connection API: {connection}", + raise TokenVaultInterrupt( + f"Authorization required to access the Token Vault connection: {connection}", connection, scopes, scopes @@ -187,8 +187,8 @@ def validate_token(self, token_response: Optional[TokenResponse] = None): if missing_scopes: granted_union = sorted(set(current_scopes) | set(scopes)) - raise FederatedConnectionInterrupt( - f"Authorization required to access the Federated Connection API: {connection}. Missing scopes: {', '.join(missing_scopes)}", + raise TokenVaultInterrupt( + f"Authorization required to access the Token Vault connection: {connection}. Missing scopes: {', '.join(missing_scopes)}", connection, scopes, granted_union @@ -199,7 +199,6 @@ async def get_access_token_impl(self, *args: ToolInput.args, **kwargs: ToolInput connection = store["connection"] refresh_supplied = self.params.refresh_token.value is not None - access_supplied = self.params.access_token.value is not None subject_token_type: str if refresh_supplied: @@ -218,7 +217,7 @@ async def get_access_token_impl(self, *args: ToolInput.args, **kwargs: ToolInput request_kwargs = dict( subject_token_type=subject_token_type, subject_token=subject_token, - requested_token_type=REQUESTED_TOKEN_TYPE_FEDERATED_CONNECTION_ACCESS_TOKEN, + requested_token_type=REQUESTED_TOKEN_TYPE_TOKEN_VAULT_ACCESS_TOKEN, connection=connection, ) if login_hint: @@ -233,7 +232,7 @@ async def get_access_token_impl(self, *args: ToolInput.args, **kwargs: ToolInput refresh_token=response.get("refresh_token"), ) except Auth0Error as err: - raise FederatedConnectionError(err.message) if 400 <= err.status_code <= 499 else err + raise TokenVaultError(err.message) if 400 <= err.status_code <= 499 else err async def get_refresh_token(self, *args: ToolInput.args, **kwargs: ToolInput.kwargs): token = await self.params.refresh_token.resolve(*args, **kwargs) @@ -267,18 +266,19 @@ async def wrapped_execute(*args: ToolInput.args, **kwargs: ToolInput.kwargs): credentials = await self.credentials_store.get(credentials_ns, "credential") if not credentials: - credentials = await self.get_access_token(*args, **kwargs) + credentials = await self.get_access_token_impl(*args, **kwargs) + self.validate_token(credentials) await self.credentials_store.put(credentials_ns, "credential", credentials) - + _update_local_storage({"credentials": credentials}) if inspect.iscoroutinefunction(execute): return await execute(*args, **kwargs) else: return execute(*args, **kwargs) - except FederatedConnectionError as err: + except TokenVaultError as err: self.credentials_store.delete(credentials_ns, "credential") - interrupt = FederatedConnectionInterrupt( + interrupt = TokenVaultInterrupt( str(err), local_store["connection"], local_store["scopes"], diff --git a/packages/auth0-ai/auth0_ai/interrupts/ciba_interrupts.py b/packages/auth0-ai/auth0_ai/interrupts/async_authorization_interrupts.py similarity index 51% rename from packages/auth0-ai/auth0_ai/interrupts/ciba_interrupts.py rename to packages/auth0-ai/auth0_ai/interrupts/async_authorization_interrupts.py index 7277dbb..752a40f 100644 --- a/packages/auth0-ai/auth0_ai/interrupts/ciba_interrupts.py +++ b/packages/auth0-ai/auth0_ai/interrupts/async_authorization_interrupts.py @@ -1,24 +1,24 @@ from abc import ABC from typing import Any, Type, TypeVar, get_type_hints from auth0_ai.interrupts.auth0_interrupt import Auth0Interrupt -from auth0_ai.authorizers.ciba import CIBAAuthorizationRequest +from auth0_ai.authorizers.async_authorization import AsyncAuthorizationRequest class WithRequestData: - def __init__(self, request: CIBAAuthorizationRequest): + def __init__(self, request: AsyncAuthorizationRequest): self._request = request @property - def request(self) -> CIBAAuthorizationRequest: + def request(self) -> AsyncAuthorizationRequest: return self._request -CIBAInterruptType = TypeVar("T", bound="CIBAInterrupt") +AsyncAuthorizationInterruptType = TypeVar("T", bound="AsyncAuthorizationInterrupt") -class CIBAInterrupt(Auth0Interrupt, ABC): +class AsyncAuthorizationInterrupt(Auth0Interrupt, ABC): def __init__(self, message: str, code: str): super().__init__(message, code) @classmethod - def is_interrupt(cls: Type[CIBAInterruptType], interrupt: Any) -> bool: + def is_interrupt(cls: Type[AsyncAuthorizationInterruptType], interrupt: Any) -> bool: return ( interrupt and Auth0Interrupt.is_interrupt(interrupt) @@ -26,7 +26,7 @@ def is_interrupt(cls: Type[CIBAInterruptType], interrupt: Any) -> bool: and ( (hasattr(cls, "code") and getattr(cls, "code") == interrupt["code"]) or - (not hasattr(cls, "code") and interrupt["code"].startswith("CIBA_")) + (not hasattr(cls, "code") and interrupt["code"].startswith("ASYNC_AUTHORIZATION_")) ) ) @@ -42,38 +42,38 @@ def has_request_data(cls, interrupt: Any) -> bool: if not isinstance(request, dict): return False - required_keys = set(get_type_hints(CIBAAuthorizationRequest).keys()) + required_keys = set(get_type_hints(AsyncAuthorizationRequest).keys()) return required_keys <= request.keys() -class AccessDeniedInterrupt(CIBAInterrupt, WithRequestData): - code: str = "CIBA_ACCESS_DENIED" +class AccessDeniedInterrupt(AsyncAuthorizationInterrupt, WithRequestData): + code: str = "ASYNC_AUTHORIZATION_ACCESS_DENIED" - def __init__(self, message: str, request: CIBAAuthorizationRequest): - CIBAInterrupt.__init__(self, message, AccessDeniedInterrupt.code) + def __init__(self, message: str, request: AsyncAuthorizationRequest): + AsyncAuthorizationInterrupt.__init__(self, message, AccessDeniedInterrupt.code) WithRequestData.__init__(self, request) -class UserDoesNotHavePushNotificationsInterrupt(CIBAInterrupt): - code: str = "CIBA_USER_DOES_NOT_HAVE_PUSH_NOTIFICATIONS" +class UserDoesNotHavePushNotificationsInterrupt(AsyncAuthorizationInterrupt): + code: str = "ASYNC_AUTHORIZATION_USER_DOES_NOT_HAVE_PUSH_NOTIFICATIONS" def __init__(self, message: str): super().__init__(message, UserDoesNotHavePushNotificationsInterrupt.code) -class AuthorizationRequestExpiredInterrupt(CIBAInterrupt, WithRequestData): - code: str = "CIBA_AUTHORIZATION_REQUEST_EXPIRED" +class AuthorizationRequestExpiredInterrupt(AsyncAuthorizationInterrupt, WithRequestData): + code: str = "ASYNC_AUTHORIZATION_REQUEST_EXPIRED" - def __init__(self, message: str, request: CIBAAuthorizationRequest): - CIBAInterrupt.__init__(self, message, AuthorizationRequestExpiredInterrupt.code) + def __init__(self, message: str, request: AsyncAuthorizationRequest): + AsyncAuthorizationInterrupt.__init__(self, message, AuthorizationRequestExpiredInterrupt.code) WithRequestData.__init__(self, request) -class AuthorizationPendingInterrupt(CIBAInterrupt, WithRequestData): - code: str = "CIBA_AUTHORIZATION_PENDING" +class AuthorizationPendingInterrupt(AsyncAuthorizationInterrupt, WithRequestData): + code: str = "ASYNC_AUTHORIZATION_PENDING" - def __init__(self, message: str, request: CIBAAuthorizationRequest): - CIBAInterrupt.__init__(self, message, AuthorizationPendingInterrupt.code) + def __init__(self, message: str, request: AsyncAuthorizationRequest): + AsyncAuthorizationInterrupt.__init__(self, message, AuthorizationPendingInterrupt.code) WithRequestData.__init__(self, request) def next_retry_interval(self) -> int: @@ -81,10 +81,10 @@ def next_retry_interval(self) -> int: return self.request["interval"] -class AuthorizationPollingInterrupt(CIBAInterrupt, WithRequestData): - code: str = "CIBA_AUTHORIZATION_POLLING_ERROR" +class AuthorizationPollingInterrupt(AsyncAuthorizationInterrupt, WithRequestData): + code: str = "ASYNC_AUTHORIZATION_POLLING_ERROR" - def __init__(self, message: str, request: CIBAAuthorizationRequest, retry_after: int = None): + def __init__(self, message: str, request: AsyncAuthorizationRequest, retry_after: int = None): Auth0Interrupt.__init__(self, message, AuthorizationPollingInterrupt.code) WithRequestData.__init__(self, request) self.retry_after = retry_after @@ -96,9 +96,9 @@ def next_retry_interval(self) -> int: return self.retry_after if self.retry_after is not None else self.request["interval"] -class InvalidGrantInterrupt(CIBAInterrupt, WithRequestData): - code: str = "CIBA_INVALID_GRANT" +class InvalidGrantInterrupt(AsyncAuthorizationInterrupt, WithRequestData): + code: str = "ASYNC_AUTHORIZATION_INVALID_GRANT" - def __init__(self, message: str, request: CIBAAuthorizationRequest): + def __init__(self, message: str, request: AsyncAuthorizationRequest): Auth0Interrupt.__init__(self, message, InvalidGrantInterrupt.code) WithRequestData.__init__(self, request) diff --git a/packages/auth0-ai/auth0_ai/interrupts/federated_connection_interrupt.py b/packages/auth0-ai/auth0_ai/interrupts/token_vault_interrupt.py similarity index 86% rename from packages/auth0-ai/auth0_ai/interrupts/federated_connection_interrupt.py rename to packages/auth0-ai/auth0_ai/interrupts/token_vault_interrupt.py index 7dbcbde..1e16db1 100644 --- a/packages/auth0-ai/auth0_ai/interrupts/federated_connection_interrupt.py +++ b/packages/auth0-ai/auth0_ai/interrupts/token_vault_interrupt.py @@ -1,18 +1,18 @@ from typing import Final from auth0_ai.interrupts.auth0_interrupt import Auth0Interrupt -class FederatedConnectionInterrupt(Auth0Interrupt): +class TokenVaultInterrupt(Auth0Interrupt): """ Error thrown when a tool call requires an access token for an external service. Throw this error if the service returns Unauthorized for the current access token. """ - code: Final[str] = "FEDERATED_CONNECTION_ERROR" + code: Final[str] = "TOKEN_VAULT_ERROR" def __init__(self, message: str, connection: str, scopes: list[str], required_scopes: list[str]): """ - Initializes a FederatedConnectionInterrupt instance. + Initializes a TokenVaultInterrupt instance. Args: message (str): Error message describing the reason for the interrupt. @@ -44,16 +44,16 @@ def __deepcopy__(self, memo): ) -class FederatedConnectionError(Exception): +class TokenVaultError(Exception): """ Error thrown when a tool call requires an access token for an external service. - The authorizer will automatically convert this class of error to FederatedConnectionInterrupt. + The authorizer will automatically convert this class of error to TokenVaultInterrupt. """ def __init__(self, message: str): """ - Initializes a FederatedConnectionError instance. + Initializes a TokenVaultError instance. Args: message (str): Error message describing the reason for the error. diff --git a/packages/auth0-ai/auth0_ai/stores/store.py b/packages/auth0-ai/auth0_ai/stores/store.py index 44138af..9e0de65 100644 --- a/packages/auth0-ai/auth0_ai/stores/store.py +++ b/packages/auth0-ai/auth0_ai/stores/store.py @@ -17,8 +17,8 @@ class Store(ABC, Generic[T]): A key-value store interface. Auth0AI uses this store in different stages: - - To store the authorization request when an AI agent is interrupted (CIBA). - - To store user credentials associated with threads to avoid re-authentication (CIBA, Federated Connection). + - To store the authorization request when an AI agent is interrupted (Async Authorization). + - To store user credentials associated with threads to avoid re-authentication (Async Authorization, Token Vault). """ @abstractmethod