botrun-flow-lang 5.12.263__py3-none-any.whl → 6.2.21__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- botrun_flow_lang/api/auth_api.py +39 -39
- botrun_flow_lang/api/auth_utils.py +183 -183
- botrun_flow_lang/api/botrun_back_api.py +65 -65
- botrun_flow_lang/api/flow_api.py +3 -3
- botrun_flow_lang/api/hatch_api.py +508 -508
- botrun_flow_lang/api/langgraph_api.py +816 -811
- botrun_flow_lang/api/langgraph_constants.py +11 -0
- botrun_flow_lang/api/line_bot_api.py +1484 -1484
- botrun_flow_lang/api/model_api.py +300 -300
- botrun_flow_lang/api/rate_limit_api.py +32 -32
- botrun_flow_lang/api/routes.py +79 -79
- botrun_flow_lang/api/search_api.py +53 -53
- botrun_flow_lang/api/storage_api.py +395 -395
- botrun_flow_lang/api/subsidy_api.py +290 -290
- botrun_flow_lang/api/subsidy_api_system_prompt.txt +109 -109
- botrun_flow_lang/api/user_setting_api.py +70 -70
- botrun_flow_lang/api/version_api.py +31 -31
- botrun_flow_lang/api/youtube_api.py +26 -26
- botrun_flow_lang/constants.py +13 -13
- botrun_flow_lang/langgraph_agents/agents/agent_runner.py +178 -178
- botrun_flow_lang/langgraph_agents/agents/agent_tools/step_planner.py +77 -77
- botrun_flow_lang/langgraph_agents/agents/checkpointer/firestore_checkpointer.py +666 -666
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/GOV_RESEARCHER_PRD.md +192 -192
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/gemini_subsidy_graph.py +460 -460
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_2_graph.py +1002 -1002
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_graph.py +822 -822
- botrun_flow_lang/langgraph_agents/agents/langgraph_react_agent.py +730 -723
- botrun_flow_lang/langgraph_agents/agents/search_agent_graph.py +864 -864
- botrun_flow_lang/langgraph_agents/agents/tools/__init__.py +4 -4
- botrun_flow_lang/langgraph_agents/agents/tools/gemini_code_execution.py +376 -376
- botrun_flow_lang/langgraph_agents/agents/util/gemini_grounding.py +66 -66
- botrun_flow_lang/langgraph_agents/agents/util/html_util.py +316 -316
- botrun_flow_lang/langgraph_agents/agents/util/img_util.py +336 -294
- botrun_flow_lang/langgraph_agents/agents/util/local_files.py +419 -419
- botrun_flow_lang/langgraph_agents/agents/util/mermaid_util.py +86 -86
- botrun_flow_lang/langgraph_agents/agents/util/model_utils.py +143 -143
- botrun_flow_lang/langgraph_agents/agents/util/pdf_analyzer.py +562 -486
- botrun_flow_lang/langgraph_agents/agents/util/pdf_cache.py +250 -250
- botrun_flow_lang/langgraph_agents/agents/util/pdf_processor.py +204 -204
- botrun_flow_lang/langgraph_agents/agents/util/perplexity_search.py +464 -464
- botrun_flow_lang/langgraph_agents/agents/util/plotly_util.py +59 -59
- botrun_flow_lang/langgraph_agents/agents/util/tavily_search.py +199 -199
- botrun_flow_lang/langgraph_agents/agents/util/usage_metadata.py +34 -0
- botrun_flow_lang/langgraph_agents/agents/util/youtube_util.py +90 -90
- botrun_flow_lang/langgraph_agents/cache/langgraph_botrun_cache.py +197 -197
- botrun_flow_lang/llm_agent/llm_agent.py +19 -19
- botrun_flow_lang/llm_agent/llm_agent_util.py +83 -83
- botrun_flow_lang/log/.gitignore +2 -2
- botrun_flow_lang/main.py +61 -61
- botrun_flow_lang/main_fast.py +51 -51
- botrun_flow_lang/mcp_server/__init__.py +10 -10
- botrun_flow_lang/mcp_server/default_mcp.py +854 -744
- botrun_flow_lang/models/nodes/utils.py +205 -205
- botrun_flow_lang/models/token_usage.py +34 -34
- botrun_flow_lang/requirements.txt +21 -21
- botrun_flow_lang/services/base/firestore_base.py +30 -30
- botrun_flow_lang/services/hatch/hatch_factory.py +11 -11
- botrun_flow_lang/services/hatch/hatch_fs_store.py +419 -419
- botrun_flow_lang/services/storage/storage_cs_store.py +206 -206
- botrun_flow_lang/services/storage/storage_factory.py +12 -12
- botrun_flow_lang/services/storage/storage_store.py +65 -65
- botrun_flow_lang/services/user_setting/user_setting_factory.py +9 -9
- botrun_flow_lang/services/user_setting/user_setting_fs_store.py +66 -66
- botrun_flow_lang/static/docs/tools/index.html +926 -926
- botrun_flow_lang/tests/api_functional_tests.py +1525 -1525
- botrun_flow_lang/tests/api_stress_test.py +357 -357
- botrun_flow_lang/tests/shared_hatch_tests.py +333 -333
- botrun_flow_lang/tests/test_botrun_app.py +46 -46
- botrun_flow_lang/tests/test_html_util.py +31 -31
- botrun_flow_lang/tests/test_img_analyzer.py +190 -190
- botrun_flow_lang/tests/test_img_util.py +39 -39
- botrun_flow_lang/tests/test_local_files.py +114 -114
- botrun_flow_lang/tests/test_mermaid_util.py +103 -103
- botrun_flow_lang/tests/test_pdf_analyzer.py +104 -104
- botrun_flow_lang/tests/test_plotly_util.py +151 -151
- botrun_flow_lang/tests/test_run_workflow_engine.py +65 -65
- botrun_flow_lang/tools/generate_docs.py +133 -133
- botrun_flow_lang/tools/templates/tools.html +153 -153
- botrun_flow_lang/utils/__init__.py +7 -7
- botrun_flow_lang/utils/botrun_logger.py +344 -344
- botrun_flow_lang/utils/clients/rate_limit_client.py +209 -209
- botrun_flow_lang/utils/clients/token_verify_client.py +153 -153
- botrun_flow_lang/utils/google_drive_utils.py +654 -654
- botrun_flow_lang/utils/langchain_utils.py +324 -324
- botrun_flow_lang/utils/yaml_utils.py +9 -9
- {botrun_flow_lang-5.12.263.dist-info → botrun_flow_lang-6.2.21.dist-info}/METADATA +6 -6
- botrun_flow_lang-6.2.21.dist-info/RECORD +104 -0
- botrun_flow_lang-5.12.263.dist-info/RECORD +0 -102
- {botrun_flow_lang-5.12.263.dist-info → botrun_flow_lang-6.2.21.dist-info}/WHEEL +0 -0
botrun_flow_lang/api/auth_api.py
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
from fastapi import APIRouter, HTTPException, Form
|
|
2
|
-
from typing import Dict, Any
|
|
3
|
-
|
|
4
|
-
from botrun_flow_lang.utils.clients.token_verify_client import TokenVerifyClient
|
|
5
|
-
|
|
6
|
-
router = APIRouter(prefix="/auth")
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@router.post("/token_verify")
|
|
10
|
-
async def verify_token(access_token: str = Form(...)) -> Dict[Any, Any]:
|
|
11
|
-
"""
|
|
12
|
-
驗證 access token 的有效性。
|
|
13
|
-
|
|
14
|
-
Args:
|
|
15
|
-
access_token: 要驗證的 access token (from form data)
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
包含驗證結果的字典:
|
|
19
|
-
{
|
|
20
|
-
"is_success": true,
|
|
21
|
-
"username": "user@example.com"
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
Raises:
|
|
25
|
-
HTTPException: 當 token 無效 (401) 或後端 API 無法訪問時 (500)
|
|
26
|
-
"""
|
|
27
|
-
try:
|
|
28
|
-
client = TokenVerifyClient()
|
|
29
|
-
result = await client.verify_token(access_token)
|
|
30
|
-
return result
|
|
31
|
-
except ValueError as e:
|
|
32
|
-
error_msg = str(e).lower()
|
|
33
|
-
# Token 無效時回傳 401
|
|
34
|
-
if "invalid" in error_msg and "token" in error_msg:
|
|
35
|
-
raise HTTPException(status_code=401, detail="Invalid access token")
|
|
36
|
-
# 請求格式錯誤時回傳 400
|
|
37
|
-
elif "bad request" in error_msg:
|
|
38
|
-
raise HTTPException(status_code=400, detail="Bad request: missing or invalid token format")
|
|
39
|
-
# 其他錯誤回傳 500
|
|
1
|
+
from fastapi import APIRouter, HTTPException, Form
|
|
2
|
+
from typing import Dict, Any
|
|
3
|
+
|
|
4
|
+
from botrun_flow_lang.utils.clients.token_verify_client import TokenVerifyClient
|
|
5
|
+
|
|
6
|
+
router = APIRouter(prefix="/auth")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@router.post("/token_verify")
|
|
10
|
+
async def verify_token(access_token: str = Form(...)) -> Dict[Any, Any]:
|
|
11
|
+
"""
|
|
12
|
+
驗證 access token 的有效性。
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
access_token: 要驗證的 access token (from form data)
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
包含驗證結果的字典:
|
|
19
|
+
{
|
|
20
|
+
"is_success": true,
|
|
21
|
+
"username": "user@example.com"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
HTTPException: 當 token 無效 (401) 或後端 API 無法訪問時 (500)
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
client = TokenVerifyClient()
|
|
29
|
+
result = await client.verify_token(access_token)
|
|
30
|
+
return result
|
|
31
|
+
except ValueError as e:
|
|
32
|
+
error_msg = str(e).lower()
|
|
33
|
+
# Token 無效時回傳 401
|
|
34
|
+
if "invalid" in error_msg and "token" in error_msg:
|
|
35
|
+
raise HTTPException(status_code=401, detail="Invalid access token")
|
|
36
|
+
# 請求格式錯誤時回傳 400
|
|
37
|
+
elif "bad request" in error_msg:
|
|
38
|
+
raise HTTPException(status_code=400, detail="Bad request: missing or invalid token format")
|
|
39
|
+
# 其他錯誤回傳 500
|
|
40
40
|
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
"""Utility functions for authentication shared across API modules."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import logging
|
|
5
|
-
from fastapi import HTTPException, Depends
|
|
6
|
-
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
7
|
-
from pydantic import BaseModel
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
9
|
-
|
|
10
|
-
from botrun_flow_lang.utils.clients.token_verify_client import TokenVerifyClient
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from botrun_flow_lang.services.hatch.hatch_fs_store import HatchFsStore
|
|
14
|
-
|
|
15
|
-
# Reusable HTTPBearer security scheme
|
|
16
|
-
security = HTTPBearer()
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class CurrentUser(BaseModel):
|
|
20
|
-
"""Current authenticated user information."""
|
|
21
|
-
user_id: str # From botrun_back API's username field or admin for universal tokens
|
|
22
|
-
is_admin: bool = False # Universal token users are marked as admin
|
|
23
|
-
|
|
24
|
-
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
|
25
|
-
"""Verify that the provided Bearer token is in the allowed JWT_TOKENS list.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
credentials: Parsed `Authorization` header credentials supplied by FastAPI's
|
|
29
|
-
dependency injection using `HTTPBearer`.
|
|
30
|
-
|
|
31
|
-
Raises:
|
|
32
|
-
HTTPException: If the token is missing or not present in the allowed list.
|
|
33
|
-
"""
|
|
34
|
-
jwt_tokens_env = os.getenv("JWT_TOKENS", "")
|
|
35
|
-
tokens = [t.strip() for t in jwt_tokens_env.split("\n") if t.strip()]
|
|
36
|
-
if credentials.credentials not in tokens:
|
|
37
|
-
raise HTTPException(
|
|
38
|
-
status_code=401,
|
|
39
|
-
detail="Invalid authentication credentials",
|
|
40
|
-
headers={"WWW-Authenticate": "Bearer"},
|
|
41
|
-
)
|
|
42
|
-
return True
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
async def verify_jwt_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -> CurrentUser:
|
|
46
|
-
"""Verify JWT token using dual authentication mechanism.
|
|
47
|
-
|
|
48
|
-
First checks if token is in JWT_TOKENS (universal tokens for testing),
|
|
49
|
-
then calls botrun_back API for user authentication using TokenVerifyClient.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
credentials: Parsed Authorization header credentials
|
|
53
|
-
|
|
54
|
-
Returns:
|
|
55
|
-
CurrentUser: Authenticated user information
|
|
56
|
-
|
|
57
|
-
Raises:
|
|
58
|
-
HTTPException: If authentication fails
|
|
59
|
-
"""
|
|
60
|
-
# 1. Check universal tokens (existing logic)
|
|
61
|
-
jwt_tokens_env = os.getenv("JWT_TOKENS", "")
|
|
62
|
-
tokens = [t.strip() for t in jwt_tokens_env.split("\n") if t.strip()]
|
|
63
|
-
|
|
64
|
-
if credentials.credentials in tokens:
|
|
65
|
-
return CurrentUser(user_id="admin", is_admin=True)
|
|
66
|
-
|
|
67
|
-
# 2. Use TokenVerifyClient for authentication (with IAP support)
|
|
68
|
-
try:
|
|
69
|
-
client = TokenVerifyClient()
|
|
70
|
-
result = await client.verify_token(credentials.credentials)
|
|
71
|
-
|
|
72
|
-
if result.get('is_success', False):
|
|
73
|
-
username = result.get('username', '')
|
|
74
|
-
if username:
|
|
75
|
-
return CurrentUser(user_id=username, is_admin=False)
|
|
76
|
-
else:
|
|
77
|
-
raise HTTPException(status_code=401, detail="Invalid response from auth service")
|
|
78
|
-
else:
|
|
79
|
-
raise HTTPException(status_code=401, detail="Invalid authentication credentials")
|
|
80
|
-
|
|
81
|
-
except ValueError as e:
|
|
82
|
-
error_msg = str(e).lower()
|
|
83
|
-
if "invalid" in error_msg and "token" in error_msg:
|
|
84
|
-
raise HTTPException(status_code=401, detail="Invalid authentication credentials")
|
|
85
|
-
elif "not configured" in error_msg:
|
|
86
|
-
raise HTTPException(status_code=500, detail="Authentication service not configured")
|
|
87
|
-
else:
|
|
88
|
-
raise HTTPException(status_code=500, detail="Authentication service unavailable")
|
|
89
|
-
except Exception as e:
|
|
90
|
-
logging.error(f"Unexpected error during authentication: {e}")
|
|
91
|
-
raise HTTPException(status_code=500, detail="Authentication error")
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def verify_user_permission(current_user: CurrentUser, target_user_id: str):
|
|
95
|
-
"""Verify user has permission to access resources for the specified user_id.
|
|
96
|
-
|
|
97
|
-
Args:
|
|
98
|
-
current_user: Current authenticated user
|
|
99
|
-
target_user_id: Target user ID to check permission for
|
|
100
|
-
|
|
101
|
-
Raises:
|
|
102
|
-
HTTPException: If user lacks permission
|
|
103
|
-
"""
|
|
104
|
-
if current_user.is_admin:
|
|
105
|
-
return # Admin can access all resources
|
|
106
|
-
|
|
107
|
-
if current_user.user_id != target_user_id:
|
|
108
|
-
raise HTTPException(
|
|
109
|
-
status_code=403,
|
|
110
|
-
detail="Insufficient permissions to access this resource"
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
async def verify_hatch_owner(current_user: CurrentUser, hatch_id: str, store: "HatchFsStore"):
|
|
115
|
-
"""Verify user is the owner of the specified hatch.
|
|
116
|
-
|
|
117
|
-
Args:
|
|
118
|
-
current_user: Current authenticated user
|
|
119
|
-
hatch_id: Hatch ID to check ownership for
|
|
120
|
-
store: Hatch store instance
|
|
121
|
-
|
|
122
|
-
Raises:
|
|
123
|
-
HTTPException: If user is not the owner or hatch not found
|
|
124
|
-
"""
|
|
125
|
-
if current_user.is_admin:
|
|
126
|
-
return # Admin can access all hatches
|
|
127
|
-
|
|
128
|
-
hatch = await store.get_hatch(hatch_id)
|
|
129
|
-
if not hatch:
|
|
130
|
-
raise HTTPException(status_code=404, detail="Hatch not found")
|
|
131
|
-
|
|
132
|
-
if hatch.user_id != current_user.user_id:
|
|
133
|
-
raise HTTPException(
|
|
134
|
-
status_code=403,
|
|
135
|
-
detail="Insufficient permissions to access this hatch"
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
async def verify_hatch_access(current_user: CurrentUser, hatch_id: str, store: "HatchFsStore"):
|
|
140
|
-
"""Verify user can read the hatch (owner or shared with user).
|
|
141
|
-
|
|
142
|
-
Args:
|
|
143
|
-
current_user: Current authenticated user
|
|
144
|
-
hatch_id: Hatch ID to check access for
|
|
145
|
-
store: Hatch store instance
|
|
146
|
-
|
|
147
|
-
Raises:
|
|
148
|
-
HTTPException: If user lacks access or hatch not found
|
|
149
|
-
"""
|
|
150
|
-
if current_user.is_admin:
|
|
151
|
-
return # Admin can access all hatches
|
|
152
|
-
|
|
153
|
-
hatch = await store.get_hatch(hatch_id)
|
|
154
|
-
if not hatch:
|
|
155
|
-
raise HTTPException(status_code=404, detail="Hatch not found")
|
|
156
|
-
|
|
157
|
-
# Check if user is owner
|
|
158
|
-
if hatch.user_id == current_user.user_id:
|
|
159
|
-
return
|
|
160
|
-
|
|
161
|
-
# Check if hatch is shared with user
|
|
162
|
-
is_shared, _ = await store.is_hatch_shared_with_user(hatch_id, current_user.user_id)
|
|
163
|
-
if not is_shared:
|
|
164
|
-
raise HTTPException(
|
|
165
|
-
status_code=403,
|
|
166
|
-
detail="Insufficient permissions to access this hatch"
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def verify_admin_permission(current_user: CurrentUser):
|
|
171
|
-
"""Verify user has admin permissions.
|
|
172
|
-
|
|
173
|
-
Args:
|
|
174
|
-
current_user: Current authenticated user
|
|
175
|
-
|
|
176
|
-
Raises:
|
|
177
|
-
HTTPException: If user is not admin
|
|
178
|
-
"""
|
|
179
|
-
if not current_user.is_admin:
|
|
180
|
-
raise HTTPException(
|
|
181
|
-
status_code=403,
|
|
182
|
-
detail="Admin permissions required"
|
|
183
|
-
)
|
|
1
|
+
"""Utility functions for authentication shared across API modules."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
5
|
+
from fastapi import HTTPException, Depends
|
|
6
|
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from botrun_flow_lang.utils.clients.token_verify_client import TokenVerifyClient
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from botrun_flow_lang.services.hatch.hatch_fs_store import HatchFsStore
|
|
14
|
+
|
|
15
|
+
# Reusable HTTPBearer security scheme
|
|
16
|
+
security = HTTPBearer()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CurrentUser(BaseModel):
|
|
20
|
+
"""Current authenticated user information."""
|
|
21
|
+
user_id: str # From botrun_back API's username field or admin for universal tokens
|
|
22
|
+
is_admin: bool = False # Universal token users are marked as admin
|
|
23
|
+
|
|
24
|
+
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
|
25
|
+
"""Verify that the provided Bearer token is in the allowed JWT_TOKENS list.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
credentials: Parsed `Authorization` header credentials supplied by FastAPI's
|
|
29
|
+
dependency injection using `HTTPBearer`.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
HTTPException: If the token is missing or not present in the allowed list.
|
|
33
|
+
"""
|
|
34
|
+
jwt_tokens_env = os.getenv("JWT_TOKENS", "")
|
|
35
|
+
tokens = [t.strip() for t in jwt_tokens_env.split("\n") if t.strip()]
|
|
36
|
+
if credentials.credentials not in tokens:
|
|
37
|
+
raise HTTPException(
|
|
38
|
+
status_code=401,
|
|
39
|
+
detail="Invalid authentication credentials",
|
|
40
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
41
|
+
)
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def verify_jwt_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -> CurrentUser:
|
|
46
|
+
"""Verify JWT token using dual authentication mechanism.
|
|
47
|
+
|
|
48
|
+
First checks if token is in JWT_TOKENS (universal tokens for testing),
|
|
49
|
+
then calls botrun_back API for user authentication using TokenVerifyClient.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
credentials: Parsed Authorization header credentials
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
CurrentUser: Authenticated user information
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
HTTPException: If authentication fails
|
|
59
|
+
"""
|
|
60
|
+
# 1. Check universal tokens (existing logic)
|
|
61
|
+
jwt_tokens_env = os.getenv("JWT_TOKENS", "")
|
|
62
|
+
tokens = [t.strip() for t in jwt_tokens_env.split("\n") if t.strip()]
|
|
63
|
+
|
|
64
|
+
if credentials.credentials in tokens:
|
|
65
|
+
return CurrentUser(user_id="admin", is_admin=True)
|
|
66
|
+
|
|
67
|
+
# 2. Use TokenVerifyClient for authentication (with IAP support)
|
|
68
|
+
try:
|
|
69
|
+
client = TokenVerifyClient()
|
|
70
|
+
result = await client.verify_token(credentials.credentials)
|
|
71
|
+
|
|
72
|
+
if result.get('is_success', False):
|
|
73
|
+
username = result.get('username', '')
|
|
74
|
+
if username:
|
|
75
|
+
return CurrentUser(user_id=username, is_admin=False)
|
|
76
|
+
else:
|
|
77
|
+
raise HTTPException(status_code=401, detail="Invalid response from auth service")
|
|
78
|
+
else:
|
|
79
|
+
raise HTTPException(status_code=401, detail="Invalid authentication credentials")
|
|
80
|
+
|
|
81
|
+
except ValueError as e:
|
|
82
|
+
error_msg = str(e).lower()
|
|
83
|
+
if "invalid" in error_msg and "token" in error_msg:
|
|
84
|
+
raise HTTPException(status_code=401, detail="Invalid authentication credentials")
|
|
85
|
+
elif "not configured" in error_msg:
|
|
86
|
+
raise HTTPException(status_code=500, detail="Authentication service not configured")
|
|
87
|
+
else:
|
|
88
|
+
raise HTTPException(status_code=500, detail="Authentication service unavailable")
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logging.error(f"Unexpected error during authentication: {e}")
|
|
91
|
+
raise HTTPException(status_code=500, detail="Authentication error")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def verify_user_permission(current_user: CurrentUser, target_user_id: str):
|
|
95
|
+
"""Verify user has permission to access resources for the specified user_id.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
current_user: Current authenticated user
|
|
99
|
+
target_user_id: Target user ID to check permission for
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
HTTPException: If user lacks permission
|
|
103
|
+
"""
|
|
104
|
+
if current_user.is_admin:
|
|
105
|
+
return # Admin can access all resources
|
|
106
|
+
|
|
107
|
+
if current_user.user_id != target_user_id:
|
|
108
|
+
raise HTTPException(
|
|
109
|
+
status_code=403,
|
|
110
|
+
detail="Insufficient permissions to access this resource"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
async def verify_hatch_owner(current_user: CurrentUser, hatch_id: str, store: "HatchFsStore"):
|
|
115
|
+
"""Verify user is the owner of the specified hatch.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
current_user: Current authenticated user
|
|
119
|
+
hatch_id: Hatch ID to check ownership for
|
|
120
|
+
store: Hatch store instance
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
HTTPException: If user is not the owner or hatch not found
|
|
124
|
+
"""
|
|
125
|
+
if current_user.is_admin:
|
|
126
|
+
return # Admin can access all hatches
|
|
127
|
+
|
|
128
|
+
hatch = await store.get_hatch(hatch_id)
|
|
129
|
+
if not hatch:
|
|
130
|
+
raise HTTPException(status_code=404, detail="Hatch not found")
|
|
131
|
+
|
|
132
|
+
if hatch.user_id != current_user.user_id:
|
|
133
|
+
raise HTTPException(
|
|
134
|
+
status_code=403,
|
|
135
|
+
detail="Insufficient permissions to access this hatch"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async def verify_hatch_access(current_user: CurrentUser, hatch_id: str, store: "HatchFsStore"):
|
|
140
|
+
"""Verify user can read the hatch (owner or shared with user).
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
current_user: Current authenticated user
|
|
144
|
+
hatch_id: Hatch ID to check access for
|
|
145
|
+
store: Hatch store instance
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
HTTPException: If user lacks access or hatch not found
|
|
149
|
+
"""
|
|
150
|
+
if current_user.is_admin:
|
|
151
|
+
return # Admin can access all hatches
|
|
152
|
+
|
|
153
|
+
hatch = await store.get_hatch(hatch_id)
|
|
154
|
+
if not hatch:
|
|
155
|
+
raise HTTPException(status_code=404, detail="Hatch not found")
|
|
156
|
+
|
|
157
|
+
# Check if user is owner
|
|
158
|
+
if hatch.user_id == current_user.user_id:
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
# Check if hatch is shared with user
|
|
162
|
+
is_shared, _ = await store.is_hatch_shared_with_user(hatch_id, current_user.user_id)
|
|
163
|
+
if not is_shared:
|
|
164
|
+
raise HTTPException(
|
|
165
|
+
status_code=403,
|
|
166
|
+
detail="Insufficient permissions to access this hatch"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def verify_admin_permission(current_user: CurrentUser):
|
|
171
|
+
"""Verify user has admin permissions.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
current_user: Current authenticated user
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
HTTPException: If user is not admin
|
|
178
|
+
"""
|
|
179
|
+
if not current_user.is_admin:
|
|
180
|
+
raise HTTPException(
|
|
181
|
+
status_code=403,
|
|
182
|
+
detail="Admin permissions required"
|
|
183
|
+
)
|
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from fastapi import APIRouter, HTTPException
|
|
3
|
-
import aiohttp
|
|
4
|
-
from urllib.parse import urljoin
|
|
5
|
-
import json
|
|
6
|
-
from google.auth.transport.requests import Request
|
|
7
|
-
from google.oauth2 import service_account
|
|
8
|
-
|
|
9
|
-
router = APIRouter()
|
|
10
|
-
|
|
11
|
-
router = APIRouter(prefix="/botrun_back")
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def normalize_url(base_url, path):
|
|
15
|
-
f_base_url = base_url
|
|
16
|
-
if not f_base_url.endswith("/"):
|
|
17
|
-
f_base_url = f_base_url + "/"
|
|
18
|
-
return urljoin(f_base_url, path)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@router.get("/info")
|
|
22
|
-
async def get_botrun_back_info():
|
|
23
|
-
"""Get information from the botrun backend service."""
|
|
24
|
-
try:
|
|
25
|
-
botrun_base_url = os.environ.get("BOTRUN_BACK_API_BASE")
|
|
26
|
-
if not botrun_base_url:
|
|
27
|
-
raise HTTPException(
|
|
28
|
-
status_code=500, detail="BOTRUN_BACK_API_BASE not configured"
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
info_url = normalize_url(botrun_base_url, "botrun/info")
|
|
32
|
-
iap_client_id = os.getenv("IAP_CLIENT_ID")
|
|
33
|
-
iap_service_account_key_file = os.getenv("IAP_SERVICE_ACCOUNT_KEY_FILE")
|
|
34
|
-
headers = {}
|
|
35
|
-
if iap_client_id and iap_service_account_key_file:
|
|
36
|
-
try:
|
|
37
|
-
credentials = (
|
|
38
|
-
service_account.IDTokenCredentials.from_service_account_file(
|
|
39
|
-
iap_service_account_key_file,
|
|
40
|
-
target_audience=iap_client_id,
|
|
41
|
-
)
|
|
42
|
-
)
|
|
43
|
-
credentials.refresh(Request())
|
|
44
|
-
token = credentials.token
|
|
45
|
-
headers = {"Authorization": f"Bearer {token}"}
|
|
46
|
-
except Exception as e:
|
|
47
|
-
raise ValueError(f"Error generating IAP JWT token: {str(e)}")
|
|
48
|
-
|
|
49
|
-
async with aiohttp.ClientSession() as session:
|
|
50
|
-
async with session.get(info_url, headers=headers) as response:
|
|
51
|
-
if response.status != 200:
|
|
52
|
-
error_text = await response.text()
|
|
53
|
-
raise HTTPException(
|
|
54
|
-
status_code=response.status,
|
|
55
|
-
detail=f"Error calling botrun backend: {error_text}",
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
text = await response.text()
|
|
59
|
-
return json.loads(text)
|
|
60
|
-
except aiohttp.ClientError as e:
|
|
61
|
-
raise HTTPException(
|
|
62
|
-
status_code=500, detail=f"Error connecting to botrun backend: {str(e)}"
|
|
63
|
-
)
|
|
64
|
-
except Exception as e:
|
|
65
|
-
raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}")
|
|
1
|
+
import os
|
|
2
|
+
from fastapi import APIRouter, HTTPException
|
|
3
|
+
import aiohttp
|
|
4
|
+
from urllib.parse import urljoin
|
|
5
|
+
import json
|
|
6
|
+
from google.auth.transport.requests import Request
|
|
7
|
+
from google.oauth2 import service_account
|
|
8
|
+
|
|
9
|
+
router = APIRouter()
|
|
10
|
+
|
|
11
|
+
router = APIRouter(prefix="/botrun_back")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def normalize_url(base_url, path):
|
|
15
|
+
f_base_url = base_url
|
|
16
|
+
if not f_base_url.endswith("/"):
|
|
17
|
+
f_base_url = f_base_url + "/"
|
|
18
|
+
return urljoin(f_base_url, path)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@router.get("/info")
|
|
22
|
+
async def get_botrun_back_info():
|
|
23
|
+
"""Get information from the botrun backend service."""
|
|
24
|
+
try:
|
|
25
|
+
botrun_base_url = os.environ.get("BOTRUN_BACK_API_BASE")
|
|
26
|
+
if not botrun_base_url:
|
|
27
|
+
raise HTTPException(
|
|
28
|
+
status_code=500, detail="BOTRUN_BACK_API_BASE not configured"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
info_url = normalize_url(botrun_base_url, "botrun/info")
|
|
32
|
+
iap_client_id = os.getenv("IAP_CLIENT_ID")
|
|
33
|
+
iap_service_account_key_file = os.getenv("IAP_SERVICE_ACCOUNT_KEY_FILE")
|
|
34
|
+
headers = {}
|
|
35
|
+
if iap_client_id and iap_service_account_key_file:
|
|
36
|
+
try:
|
|
37
|
+
credentials = (
|
|
38
|
+
service_account.IDTokenCredentials.from_service_account_file(
|
|
39
|
+
iap_service_account_key_file,
|
|
40
|
+
target_audience=iap_client_id,
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
credentials.refresh(Request())
|
|
44
|
+
token = credentials.token
|
|
45
|
+
headers = {"Authorization": f"Bearer {token}"}
|
|
46
|
+
except Exception as e:
|
|
47
|
+
raise ValueError(f"Error generating IAP JWT token: {str(e)}")
|
|
48
|
+
|
|
49
|
+
async with aiohttp.ClientSession() as session:
|
|
50
|
+
async with session.get(info_url, headers=headers) as response:
|
|
51
|
+
if response.status != 200:
|
|
52
|
+
error_text = await response.text()
|
|
53
|
+
raise HTTPException(
|
|
54
|
+
status_code=response.status,
|
|
55
|
+
detail=f"Error calling botrun backend: {error_text}",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
text = await response.text()
|
|
59
|
+
return json.loads(text)
|
|
60
|
+
except aiohttp.ClientError as e:
|
|
61
|
+
raise HTTPException(
|
|
62
|
+
status_code=500, detail=f"Error connecting to botrun backend: {str(e)}"
|
|
63
|
+
)
|
|
64
|
+
except Exception as e:
|
|
65
|
+
raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}")
|
botrun_flow_lang/api/flow_api.py
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from fastapi import FastAPI, HTTPException, Query, APIRouter, Body, Depends
|
|
2
|
-
|
|
3
|
-
crawl_api_router = APIRouter()
|
|
1
|
+
from fastapi import FastAPI, HTTPException, Query, APIRouter, Body, Depends
|
|
2
|
+
|
|
3
|
+
crawl_api_router = APIRouter()
|