Commit
·
79f305b
1
Parent(s):
f474e49
Enhance API key handling and user authentication
Browse files- Improved API key extraction from requests using context variables.
- Updated user authentication to validate API keys and handle development mode securely.
- Added cleanup scripts for correcting bad user_id records in the database.
- Implemented verification for user_id fixes to ensure data integrity.
- chat/tools.py +156 -52
- cleanup_bad_user_ids.py +124 -0
- database/api_keys.py +5 -1
- fix_user_id.py +53 -0
- server.py +89 -49
- verify_fix.py +51 -0
chat/tools.py
CHANGED
|
@@ -1340,10 +1340,14 @@ def handle_create_order(tool_input: dict, user_id: str = None) -> dict:
|
|
| 1340 |
Returns:
|
| 1341 |
Order creation result
|
| 1342 |
"""
|
| 1343 |
-
# Authentication check - allow dev mode
|
| 1344 |
if not user_id:
|
| 1345 |
# Development mode - use default user
|
| 1346 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1347 |
user_id = "dev-user"
|
| 1348 |
else:
|
| 1349 |
return {
|
|
@@ -1476,9 +1480,13 @@ def handle_create_driver(tool_input: dict, user_id: str = None) -> dict:
|
|
| 1476 |
Returns:
|
| 1477 |
Driver creation result
|
| 1478 |
"""
|
| 1479 |
-
# Authentication check - allow dev mode
|
| 1480 |
if not user_id:
|
| 1481 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1482 |
user_id = "dev-user"
|
| 1483 |
else:
|
| 1484 |
return {
|
|
@@ -1603,9 +1611,13 @@ def handle_update_order(tool_input: dict, user_id: str = None) -> dict:
|
|
| 1603 |
Returns:
|
| 1604 |
Update result
|
| 1605 |
"""
|
| 1606 |
-
# Authentication check - allow dev mode
|
| 1607 |
if not user_id:
|
| 1608 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1609 |
user_id = "dev-user"
|
| 1610 |
else:
|
| 1611 |
return {
|
|
@@ -1829,9 +1841,13 @@ def handle_delete_all_orders(tool_input: dict, user_id: str = None) -> dict:
|
|
| 1829 |
Returns:
|
| 1830 |
Deletion result with count
|
| 1831 |
"""
|
| 1832 |
-
# Authentication check - allow dev mode
|
| 1833 |
if not user_id:
|
| 1834 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1835 |
user_id = "dev-user"
|
| 1836 |
else:
|
| 1837 |
return {
|
|
@@ -1914,9 +1930,13 @@ def handle_delete_order(tool_input: dict, user_id: str = None) -> dict:
|
|
| 1914 |
Returns:
|
| 1915 |
Deletion result
|
| 1916 |
"""
|
| 1917 |
-
# Authentication check - allow dev mode
|
| 1918 |
if not user_id:
|
| 1919 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1920 |
user_id = "dev-user"
|
| 1921 |
else:
|
| 1922 |
return {
|
|
@@ -2017,9 +2037,13 @@ def handle_update_driver(tool_input: dict, user_id: str = None) -> dict:
|
|
| 2017 |
Returns:
|
| 2018 |
Update result
|
| 2019 |
"""
|
| 2020 |
-
# Authentication check - allow dev mode
|
| 2021 |
if not user_id:
|
| 2022 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2023 |
user_id = "dev-user"
|
| 2024 |
else:
|
| 2025 |
return {
|
|
@@ -2166,9 +2190,13 @@ def handle_delete_all_drivers(tool_input: dict, user_id: str = None) -> dict:
|
|
| 2166 |
Returns:
|
| 2167 |
Deletion result with count
|
| 2168 |
"""
|
| 2169 |
-
# Authentication check - allow dev mode
|
| 2170 |
if not user_id:
|
| 2171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2172 |
user_id = "dev-user"
|
| 2173 |
else:
|
| 2174 |
return {
|
|
@@ -2256,9 +2284,13 @@ def handle_delete_driver(tool_input: dict, user_id: str = None) -> dict:
|
|
| 2256 |
Returns:
|
| 2257 |
Deletion result
|
| 2258 |
"""
|
| 2259 |
-
# Authentication check - allow dev mode
|
| 2260 |
if not user_id:
|
| 2261 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2262 |
user_id = "dev-user"
|
| 2263 |
else:
|
| 2264 |
return {
|
|
@@ -2384,9 +2416,13 @@ def handle_count_orders(tool_input: dict, user_id: str = None) -> dict:
|
|
| 2384 |
Returns:
|
| 2385 |
Order count result with breakdown (only user's orders)
|
| 2386 |
"""
|
| 2387 |
-
# Authentication check - allow dev mode
|
| 2388 |
if not user_id:
|
| 2389 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2390 |
user_id = "dev-user"
|
| 2391 |
else:
|
| 2392 |
return {
|
|
@@ -2493,10 +2529,14 @@ def handle_fetch_orders(tool_input: dict, user_id: str = None) -> dict:
|
|
| 2493 |
Returns:
|
| 2494 |
List of orders matching criteria (only user's orders)
|
| 2495 |
"""
|
| 2496 |
-
# Authentication check - allow dev mode
|
| 2497 |
if not user_id:
|
| 2498 |
# Development mode - use default user
|
| 2499 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2500 |
user_id = "dev-user"
|
| 2501 |
else:
|
| 2502 |
return {
|
|
@@ -2647,9 +2687,13 @@ def handle_get_order_details(tool_input: dict, user_id: str = None) -> dict:
|
|
| 2647 |
Returns:
|
| 2648 |
Complete order details (only if owned by user)
|
| 2649 |
"""
|
| 2650 |
-
# Authentication check - allow dev mode
|
| 2651 |
if not user_id:
|
| 2652 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2653 |
user_id = "dev-user"
|
| 2654 |
else:
|
| 2655 |
return {
|
|
@@ -2768,9 +2812,13 @@ def handle_search_orders(tool_input: dict, user_id: str = None) -> dict:
|
|
| 2768 |
Returns:
|
| 2769 |
List of matching orders
|
| 2770 |
"""
|
| 2771 |
-
# Authentication check - allow dev mode
|
| 2772 |
if not user_id:
|
| 2773 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2774 |
user_id = "dev-user"
|
| 2775 |
else:
|
| 2776 |
return {
|
|
@@ -2857,9 +2905,13 @@ def handle_get_incomplete_orders(tool_input: dict, user_id: str = None) -> dict:
|
|
| 2857 |
Returns:
|
| 2858 |
List of incomplete orders (pending, assigned, in_transit)
|
| 2859 |
"""
|
| 2860 |
-
# Authentication check - allow dev mode
|
| 2861 |
if not user_id:
|
| 2862 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2863 |
user_id = "dev-user"
|
| 2864 |
else:
|
| 2865 |
return {
|
|
@@ -2938,9 +2990,13 @@ def handle_count_drivers(tool_input: dict, user_id: str = None) -> dict:
|
|
| 2938 |
Returns:
|
| 2939 |
Driver count result with breakdown
|
| 2940 |
"""
|
| 2941 |
-
# Authentication check - allow dev mode
|
| 2942 |
if not user_id:
|
| 2943 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2944 |
user_id = "dev-user"
|
| 2945 |
else:
|
| 2946 |
return {
|
|
@@ -3023,9 +3079,13 @@ def handle_fetch_drivers(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3023 |
Returns:
|
| 3024 |
List of drivers matching criteria (only user's drivers)
|
| 3025 |
"""
|
| 3026 |
-
# Authentication check - allow dev mode
|
| 3027 |
if not user_id:
|
| 3028 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3029 |
user_id = "dev-user"
|
| 3030 |
else:
|
| 3031 |
return {
|
|
@@ -3148,9 +3208,13 @@ def handle_get_driver_details(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3148 |
Returns:
|
| 3149 |
Complete driver details
|
| 3150 |
"""
|
| 3151 |
-
# Authentication check - allow dev mode
|
| 3152 |
if not user_id:
|
| 3153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3154 |
user_id = "dev-user"
|
| 3155 |
else:
|
| 3156 |
return {
|
|
@@ -3267,9 +3331,13 @@ def handle_search_drivers(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3267 |
Returns:
|
| 3268 |
List of matching drivers
|
| 3269 |
"""
|
| 3270 |
-
# Authentication check - allow dev mode
|
| 3271 |
if not user_id:
|
| 3272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3273 |
user_id = "dev-user"
|
| 3274 |
else:
|
| 3275 |
return {
|
|
@@ -3357,9 +3425,13 @@ def handle_get_available_drivers(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3357 |
Returns:
|
| 3358 |
List of available drivers (active or offline)
|
| 3359 |
"""
|
| 3360 |
-
# Authentication check - allow dev mode
|
| 3361 |
if not user_id:
|
| 3362 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3363 |
user_id = "dev-user"
|
| 3364 |
else:
|
| 3365 |
return {
|
|
@@ -3461,9 +3533,13 @@ def handle_create_assignment(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3461 |
Returns:
|
| 3462 |
Assignment creation result with route data
|
| 3463 |
"""
|
| 3464 |
-
# Authentication check - allow dev mode
|
| 3465 |
if not user_id:
|
| 3466 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3467 |
user_id = "dev-user"
|
| 3468 |
else:
|
| 3469 |
return {
|
|
@@ -3737,9 +3813,13 @@ def handle_auto_assign_order(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3737 |
Returns:
|
| 3738 |
Assignment details with selected driver info and distance
|
| 3739 |
"""
|
| 3740 |
-
# Authentication check - allow dev mode
|
| 3741 |
if not user_id:
|
| 3742 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3743 |
user_id = "dev-user"
|
| 3744 |
else:
|
| 3745 |
return {
|
|
@@ -3955,9 +4035,13 @@ def handle_intelligent_assign_order(tool_input: dict, user_id: str = None) -> di
|
|
| 3955 |
Returns:
|
| 3956 |
Assignment details with AI reasoning and selected driver info
|
| 3957 |
"""
|
| 3958 |
-
# Authentication check - allow dev mode
|
| 3959 |
if not user_id:
|
| 3960 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3961 |
user_id = "dev-user"
|
| 3962 |
else:
|
| 3963 |
return {
|
|
@@ -4285,9 +4369,13 @@ def handle_get_assignment_details(tool_input: dict, user_id: str = None) -> dict
|
|
| 4285 |
Returns:
|
| 4286 |
Assignment details or list of assignments
|
| 4287 |
"""
|
| 4288 |
-
# Authentication check - allow dev mode
|
| 4289 |
if not user_id:
|
| 4290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4291 |
user_id = "dev-user"
|
| 4292 |
else:
|
| 4293 |
return {
|
|
@@ -4442,9 +4530,13 @@ def handle_update_assignment(tool_input: dict, user_id: str = None) -> dict:
|
|
| 4442 |
Returns:
|
| 4443 |
Update result
|
| 4444 |
"""
|
| 4445 |
-
# Authentication check - allow dev mode
|
| 4446 |
if not user_id:
|
| 4447 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4448 |
user_id = "dev-user"
|
| 4449 |
else:
|
| 4450 |
return {
|
|
@@ -4638,9 +4730,13 @@ def handle_unassign_order(tool_input: dict, user_id: str = None) -> dict:
|
|
| 4638 |
Returns:
|
| 4639 |
Unassignment result
|
| 4640 |
"""
|
| 4641 |
-
# Authentication check - allow dev mode
|
| 4642 |
if not user_id:
|
| 4643 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4644 |
user_id = "dev-user"
|
| 4645 |
else:
|
| 4646 |
return {
|
|
@@ -4781,9 +4877,13 @@ def handle_complete_delivery(tool_input: dict, user_id: str = None) -> dict:
|
|
| 4781 |
Returns:
|
| 4782 |
Completion result
|
| 4783 |
"""
|
| 4784 |
-
# Authentication check - allow dev mode
|
| 4785 |
if not user_id:
|
| 4786 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4787 |
user_id = "dev-user"
|
| 4788 |
else:
|
| 4789 |
return {
|
|
@@ -5016,9 +5116,13 @@ def handle_fail_delivery(tool_input: dict, user_id: str = None) -> dict:
|
|
| 5016 |
Returns:
|
| 5017 |
Failure recording result
|
| 5018 |
"""
|
| 5019 |
-
# Authentication check - allow dev mode
|
| 5020 |
if not user_id:
|
| 5021 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5022 |
user_id = "dev-user"
|
| 5023 |
else:
|
| 5024 |
return {
|
|
|
|
| 1340 |
Returns:
|
| 1341 |
Order creation result
|
| 1342 |
"""
|
| 1343 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 1344 |
if not user_id:
|
| 1345 |
# Development mode - use default user
|
| 1346 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 1347 |
+
env = os.getenv("ENV", "production").lower()
|
| 1348 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 1349 |
+
|
| 1350 |
+
if skip_auth and env != "production":
|
| 1351 |
user_id = "dev-user"
|
| 1352 |
else:
|
| 1353 |
return {
|
|
|
|
| 1480 |
Returns:
|
| 1481 |
Driver creation result
|
| 1482 |
"""
|
| 1483 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 1484 |
if not user_id:
|
| 1485 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 1486 |
+
env = os.getenv("ENV", "production").lower()
|
| 1487 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 1488 |
+
|
| 1489 |
+
if skip_auth and env != "production":
|
| 1490 |
user_id = "dev-user"
|
| 1491 |
else:
|
| 1492 |
return {
|
|
|
|
| 1611 |
Returns:
|
| 1612 |
Update result
|
| 1613 |
"""
|
| 1614 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 1615 |
if not user_id:
|
| 1616 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 1617 |
+
env = os.getenv("ENV", "production").lower()
|
| 1618 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 1619 |
+
|
| 1620 |
+
if skip_auth and env != "production":
|
| 1621 |
user_id = "dev-user"
|
| 1622 |
else:
|
| 1623 |
return {
|
|
|
|
| 1841 |
Returns:
|
| 1842 |
Deletion result with count
|
| 1843 |
"""
|
| 1844 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 1845 |
if not user_id:
|
| 1846 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 1847 |
+
env = os.getenv("ENV", "production").lower()
|
| 1848 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 1849 |
+
|
| 1850 |
+
if skip_auth and env != "production":
|
| 1851 |
user_id = "dev-user"
|
| 1852 |
else:
|
| 1853 |
return {
|
|
|
|
| 1930 |
Returns:
|
| 1931 |
Deletion result
|
| 1932 |
"""
|
| 1933 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 1934 |
if not user_id:
|
| 1935 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 1936 |
+
env = os.getenv("ENV", "production").lower()
|
| 1937 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 1938 |
+
|
| 1939 |
+
if skip_auth and env != "production":
|
| 1940 |
user_id = "dev-user"
|
| 1941 |
else:
|
| 1942 |
return {
|
|
|
|
| 2037 |
Returns:
|
| 2038 |
Update result
|
| 2039 |
"""
|
| 2040 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 2041 |
if not user_id:
|
| 2042 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 2043 |
+
env = os.getenv("ENV", "production").lower()
|
| 2044 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 2045 |
+
|
| 2046 |
+
if skip_auth and env != "production":
|
| 2047 |
user_id = "dev-user"
|
| 2048 |
else:
|
| 2049 |
return {
|
|
|
|
| 2190 |
Returns:
|
| 2191 |
Deletion result with count
|
| 2192 |
"""
|
| 2193 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 2194 |
if not user_id:
|
| 2195 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 2196 |
+
env = os.getenv("ENV", "production").lower()
|
| 2197 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 2198 |
+
|
| 2199 |
+
if skip_auth and env != "production":
|
| 2200 |
user_id = "dev-user"
|
| 2201 |
else:
|
| 2202 |
return {
|
|
|
|
| 2284 |
Returns:
|
| 2285 |
Deletion result
|
| 2286 |
"""
|
| 2287 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 2288 |
if not user_id:
|
| 2289 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 2290 |
+
env = os.getenv("ENV", "production").lower()
|
| 2291 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 2292 |
+
|
| 2293 |
+
if skip_auth and env != "production":
|
| 2294 |
user_id = "dev-user"
|
| 2295 |
else:
|
| 2296 |
return {
|
|
|
|
| 2416 |
Returns:
|
| 2417 |
Order count result with breakdown (only user's orders)
|
| 2418 |
"""
|
| 2419 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 2420 |
if not user_id:
|
| 2421 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 2422 |
+
env = os.getenv("ENV", "production").lower()
|
| 2423 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 2424 |
+
|
| 2425 |
+
if skip_auth and env != "production":
|
| 2426 |
user_id = "dev-user"
|
| 2427 |
else:
|
| 2428 |
return {
|
|
|
|
| 2529 |
Returns:
|
| 2530 |
List of orders matching criteria (only user's orders)
|
| 2531 |
"""
|
| 2532 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 2533 |
if not user_id:
|
| 2534 |
# Development mode - use default user
|
| 2535 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 2536 |
+
env = os.getenv("ENV", "production").lower()
|
| 2537 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 2538 |
+
|
| 2539 |
+
if skip_auth and env != "production":
|
| 2540 |
user_id = "dev-user"
|
| 2541 |
else:
|
| 2542 |
return {
|
|
|
|
| 2687 |
Returns:
|
| 2688 |
Complete order details (only if owned by user)
|
| 2689 |
"""
|
| 2690 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 2691 |
if not user_id:
|
| 2692 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 2693 |
+
env = os.getenv("ENV", "production").lower()
|
| 2694 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 2695 |
+
|
| 2696 |
+
if skip_auth and env != "production":
|
| 2697 |
user_id = "dev-user"
|
| 2698 |
else:
|
| 2699 |
return {
|
|
|
|
| 2812 |
Returns:
|
| 2813 |
List of matching orders
|
| 2814 |
"""
|
| 2815 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 2816 |
if not user_id:
|
| 2817 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 2818 |
+
env = os.getenv("ENV", "production").lower()
|
| 2819 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 2820 |
+
|
| 2821 |
+
if skip_auth and env != "production":
|
| 2822 |
user_id = "dev-user"
|
| 2823 |
else:
|
| 2824 |
return {
|
|
|
|
| 2905 |
Returns:
|
| 2906 |
List of incomplete orders (pending, assigned, in_transit)
|
| 2907 |
"""
|
| 2908 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 2909 |
if not user_id:
|
| 2910 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 2911 |
+
env = os.getenv("ENV", "production").lower()
|
| 2912 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 2913 |
+
|
| 2914 |
+
if skip_auth and env != "production":
|
| 2915 |
user_id = "dev-user"
|
| 2916 |
else:
|
| 2917 |
return {
|
|
|
|
| 2990 |
Returns:
|
| 2991 |
Driver count result with breakdown
|
| 2992 |
"""
|
| 2993 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 2994 |
if not user_id:
|
| 2995 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 2996 |
+
env = os.getenv("ENV", "production").lower()
|
| 2997 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 2998 |
+
|
| 2999 |
+
if skip_auth and env != "production":
|
| 3000 |
user_id = "dev-user"
|
| 3001 |
else:
|
| 3002 |
return {
|
|
|
|
| 3079 |
Returns:
|
| 3080 |
List of drivers matching criteria (only user's drivers)
|
| 3081 |
"""
|
| 3082 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 3083 |
if not user_id:
|
| 3084 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 3085 |
+
env = os.getenv("ENV", "production").lower()
|
| 3086 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 3087 |
+
|
| 3088 |
+
if skip_auth and env != "production":
|
| 3089 |
user_id = "dev-user"
|
| 3090 |
else:
|
| 3091 |
return {
|
|
|
|
| 3208 |
Returns:
|
| 3209 |
Complete driver details
|
| 3210 |
"""
|
| 3211 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 3212 |
if not user_id:
|
| 3213 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 3214 |
+
env = os.getenv("ENV", "production").lower()
|
| 3215 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 3216 |
+
|
| 3217 |
+
if skip_auth and env != "production":
|
| 3218 |
user_id = "dev-user"
|
| 3219 |
else:
|
| 3220 |
return {
|
|
|
|
| 3331 |
Returns:
|
| 3332 |
List of matching drivers
|
| 3333 |
"""
|
| 3334 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 3335 |
if not user_id:
|
| 3336 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 3337 |
+
env = os.getenv("ENV", "production").lower()
|
| 3338 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 3339 |
+
|
| 3340 |
+
if skip_auth and env != "production":
|
| 3341 |
user_id = "dev-user"
|
| 3342 |
else:
|
| 3343 |
return {
|
|
|
|
| 3425 |
Returns:
|
| 3426 |
List of available drivers (active or offline)
|
| 3427 |
"""
|
| 3428 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 3429 |
if not user_id:
|
| 3430 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 3431 |
+
env = os.getenv("ENV", "production").lower()
|
| 3432 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 3433 |
+
|
| 3434 |
+
if skip_auth and env != "production":
|
| 3435 |
user_id = "dev-user"
|
| 3436 |
else:
|
| 3437 |
return {
|
|
|
|
| 3533 |
Returns:
|
| 3534 |
Assignment creation result with route data
|
| 3535 |
"""
|
| 3536 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 3537 |
if not user_id:
|
| 3538 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 3539 |
+
env = os.getenv("ENV", "production").lower()
|
| 3540 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 3541 |
+
|
| 3542 |
+
if skip_auth and env != "production":
|
| 3543 |
user_id = "dev-user"
|
| 3544 |
else:
|
| 3545 |
return {
|
|
|
|
| 3813 |
Returns:
|
| 3814 |
Assignment details with selected driver info and distance
|
| 3815 |
"""
|
| 3816 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 3817 |
if not user_id:
|
| 3818 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 3819 |
+
env = os.getenv("ENV", "production").lower()
|
| 3820 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 3821 |
+
|
| 3822 |
+
if skip_auth and env != "production":
|
| 3823 |
user_id = "dev-user"
|
| 3824 |
else:
|
| 3825 |
return {
|
|
|
|
| 4035 |
Returns:
|
| 4036 |
Assignment details with AI reasoning and selected driver info
|
| 4037 |
"""
|
| 4038 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 4039 |
if not user_id:
|
| 4040 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 4041 |
+
env = os.getenv("ENV", "production").lower()
|
| 4042 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 4043 |
+
|
| 4044 |
+
if skip_auth and env != "production":
|
| 4045 |
user_id = "dev-user"
|
| 4046 |
else:
|
| 4047 |
return {
|
|
|
|
| 4369 |
Returns:
|
| 4370 |
Assignment details or list of assignments
|
| 4371 |
"""
|
| 4372 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 4373 |
if not user_id:
|
| 4374 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 4375 |
+
env = os.getenv("ENV", "production").lower()
|
| 4376 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 4377 |
+
|
| 4378 |
+
if skip_auth and env != "production":
|
| 4379 |
user_id = "dev-user"
|
| 4380 |
else:
|
| 4381 |
return {
|
|
|
|
| 4530 |
Returns:
|
| 4531 |
Update result
|
| 4532 |
"""
|
| 4533 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 4534 |
if not user_id:
|
| 4535 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 4536 |
+
env = os.getenv("ENV", "production").lower()
|
| 4537 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 4538 |
+
|
| 4539 |
+
if skip_auth and env != "production":
|
| 4540 |
user_id = "dev-user"
|
| 4541 |
else:
|
| 4542 |
return {
|
|
|
|
| 4730 |
Returns:
|
| 4731 |
Unassignment result
|
| 4732 |
"""
|
| 4733 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 4734 |
if not user_id:
|
| 4735 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 4736 |
+
env = os.getenv("ENV", "production").lower()
|
| 4737 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 4738 |
+
|
| 4739 |
+
if skip_auth and env != "production":
|
| 4740 |
user_id = "dev-user"
|
| 4741 |
else:
|
| 4742 |
return {
|
|
|
|
| 4877 |
Returns:
|
| 4878 |
Completion result
|
| 4879 |
"""
|
| 4880 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 4881 |
if not user_id:
|
| 4882 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 4883 |
+
env = os.getenv("ENV", "production").lower()
|
| 4884 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 4885 |
+
|
| 4886 |
+
if skip_auth and env != "production":
|
| 4887 |
user_id = "dev-user"
|
| 4888 |
else:
|
| 4889 |
return {
|
|
|
|
| 5116 |
Returns:
|
| 5117 |
Failure recording result
|
| 5118 |
"""
|
| 5119 |
+
# Authentication check - allow dev mode (only in non-production environments)
|
| 5120 |
if not user_id:
|
| 5121 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 5122 |
+
env = os.getenv("ENV", "production").lower()
|
| 5123 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 5124 |
+
|
| 5125 |
+
if skip_auth and env != "production":
|
| 5126 |
user_id = "dev-user"
|
| 5127 |
else:
|
| 5128 |
return {
|
cleanup_bad_user_ids.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Cleanup script for records with incorrect user_id='user_id'
|
| 3 |
+
This fixes data created before the verify_api_key() bug was fixed
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from database.connection import get_db_connection
|
| 7 |
+
from database.api_keys import list_api_keys
|
| 8 |
+
import sys
|
| 9 |
+
|
| 10 |
+
def check_bad_records():
|
| 11 |
+
"""Check how many records have user_id='user_id'"""
|
| 12 |
+
conn = get_db_connection()
|
| 13 |
+
cursor = conn.cursor()
|
| 14 |
+
|
| 15 |
+
print("\n=== Checking for records with incorrect user_id='user_id' ===\n")
|
| 16 |
+
|
| 17 |
+
tables = ['orders', 'drivers', 'assignments']
|
| 18 |
+
total_bad = 0
|
| 19 |
+
details = {}
|
| 20 |
+
|
| 21 |
+
for table in tables:
|
| 22 |
+
try:
|
| 23 |
+
cursor.execute(f"SELECT COUNT(*) as count FROM {table} WHERE user_id = %s", ('user_id',))
|
| 24 |
+
result = cursor.fetchone()
|
| 25 |
+
count = result['count'] if result else 0
|
| 26 |
+
|
| 27 |
+
if count > 0:
|
| 28 |
+
print(f" {table}: {count} records with user_id='user_id'")
|
| 29 |
+
total_bad += count
|
| 30 |
+
details[table] = count
|
| 31 |
+
else:
|
| 32 |
+
print(f" {table}: No bad records found")
|
| 33 |
+
details[table] = 0
|
| 34 |
+
except Exception as e:
|
| 35 |
+
print(f" {table}: Error checking - {e}")
|
| 36 |
+
details[table] = 0
|
| 37 |
+
|
| 38 |
+
cursor.close()
|
| 39 |
+
conn.close()
|
| 40 |
+
|
| 41 |
+
print(f"\nTotal bad records: {total_bad}\n")
|
| 42 |
+
return total_bad, details
|
| 43 |
+
|
| 44 |
+
def get_first_api_key_user():
|
| 45 |
+
"""Get the first API key user (likely the correct one)"""
|
| 46 |
+
try:
|
| 47 |
+
keys = list_api_keys()
|
| 48 |
+
if keys and len(keys) > 0:
|
| 49 |
+
return keys[0]['user_id']
|
| 50 |
+
except Exception as e:
|
| 51 |
+
print(f"Error fetching API keys: {e}")
|
| 52 |
+
return None
|
| 53 |
+
|
| 54 |
+
def cleanup_bad_records(correct_user_id: str):
|
| 55 |
+
"""
|
| 56 |
+
Update records with user_id='user_id' to the correct user_id
|
| 57 |
+
|
| 58 |
+
Args:
|
| 59 |
+
correct_user_id: The actual user_id to assign (e.g., 'user_75d23af433e0')
|
| 60 |
+
"""
|
| 61 |
+
conn = get_db_connection()
|
| 62 |
+
cursor = conn.cursor()
|
| 63 |
+
|
| 64 |
+
print(f"\n=== Updating records to user_id='{correct_user_id}' ===\n")
|
| 65 |
+
|
| 66 |
+
tables = ['orders', 'drivers', 'assignments']
|
| 67 |
+
total_updated = 0
|
| 68 |
+
|
| 69 |
+
for table in tables:
|
| 70 |
+
try:
|
| 71 |
+
cursor.execute(
|
| 72 |
+
f"UPDATE {table} SET user_id = %s WHERE user_id = %s",
|
| 73 |
+
(correct_user_id, 'user_id')
|
| 74 |
+
)
|
| 75 |
+
count = cursor.rowcount
|
| 76 |
+
|
| 77 |
+
if count > 0:
|
| 78 |
+
print(f" {table}: Updated {count} records")
|
| 79 |
+
total_updated += count
|
| 80 |
+
else:
|
| 81 |
+
print(f" {table}: No records to update")
|
| 82 |
+
except Exception as e:
|
| 83 |
+
print(f" {table}: Error updating - {e}")
|
| 84 |
+
conn.rollback()
|
| 85 |
+
cursor.close()
|
| 86 |
+
conn.close()
|
| 87 |
+
return 0
|
| 88 |
+
|
| 89 |
+
conn.commit()
|
| 90 |
+
cursor.close()
|
| 91 |
+
conn.close()
|
| 92 |
+
|
| 93 |
+
print(f"\nTotal records updated: {total_updated}\n")
|
| 94 |
+
return total_updated
|
| 95 |
+
|
| 96 |
+
if __name__ == "__main__":
|
| 97 |
+
print("=== FleetMind User ID Cleanup Utility ===")
|
| 98 |
+
|
| 99 |
+
# First, check how many bad records exist
|
| 100 |
+
bad_count, details = check_bad_records()
|
| 101 |
+
|
| 102 |
+
if bad_count == 0:
|
| 103 |
+
print("No cleanup needed - all records have correct user_id values!")
|
| 104 |
+
sys.exit(0)
|
| 105 |
+
|
| 106 |
+
# Get the correct user_id from API keys table
|
| 107 |
+
correct_user_id = get_first_api_key_user()
|
| 108 |
+
|
| 109 |
+
if not correct_user_id:
|
| 110 |
+
print("\nERROR: Could not determine correct user_id from API keys table")
|
| 111 |
+
print("Please run this script with the user_id as argument:")
|
| 112 |
+
print(" python cleanup_bad_user_ids.py <user_id>")
|
| 113 |
+
sys.exit(1)
|
| 114 |
+
|
| 115 |
+
print(f"\nFound API key user: {correct_user_id}")
|
| 116 |
+
|
| 117 |
+
# Auto-cleanup
|
| 118 |
+
print(f"\nProceeding with automatic cleanup...")
|
| 119 |
+
updated = cleanup_bad_records(correct_user_id)
|
| 120 |
+
|
| 121 |
+
if updated > 0:
|
| 122 |
+
print(f"\nSUCCESS: Cleanup complete! Updated {updated} records.")
|
| 123 |
+
else:
|
| 124 |
+
print("\nNo updates were made.")
|
database/api_keys.py
CHANGED
|
@@ -163,7 +163,11 @@ def verify_api_key(api_key: str) -> Optional[Dict[str, str]]:
|
|
| 163 |
if not result:
|
| 164 |
return None
|
| 165 |
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
if not is_active:
|
| 169 |
return None
|
|
|
|
| 163 |
if not result:
|
| 164 |
return None
|
| 165 |
|
| 166 |
+
# Access RealDictRow fields by key (not tuple unpacking!)
|
| 167 |
+
user_id = result['user_id']
|
| 168 |
+
email = result['email']
|
| 169 |
+
name = result['name']
|
| 170 |
+
is_active = result['is_active']
|
| 171 |
|
| 172 |
if not is_active:
|
| 173 |
return None
|
fix_user_id.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Quick fix for the bad user_id record"""
|
| 2 |
+
|
| 3 |
+
from database.connection import get_db_connection
|
| 4 |
+
|
| 5 |
+
def fix_bad_user_id():
|
| 6 |
+
conn = get_db_connection()
|
| 7 |
+
cursor = conn.cursor()
|
| 8 |
+
|
| 9 |
+
# Get the correct user_id from api_keys table
|
| 10 |
+
cursor.execute("SELECT user_id FROM api_keys WHERE is_active = true LIMIT 1")
|
| 11 |
+
result = cursor.fetchone()
|
| 12 |
+
|
| 13 |
+
if not result:
|
| 14 |
+
print("ERROR: No active API key found")
|
| 15 |
+
cursor.close()
|
| 16 |
+
conn.close()
|
| 17 |
+
return
|
| 18 |
+
|
| 19 |
+
correct_user_id = result['user_id']
|
| 20 |
+
print(f"Found active user: {correct_user_id}")
|
| 21 |
+
|
| 22 |
+
# Update orders with bad user_id
|
| 23 |
+
cursor.execute(
|
| 24 |
+
"UPDATE orders SET user_id = %s WHERE user_id = %s",
|
| 25 |
+
(correct_user_id, 'user_id')
|
| 26 |
+
)
|
| 27 |
+
orders_updated = cursor.rowcount
|
| 28 |
+
print(f"Updated {orders_updated} orders")
|
| 29 |
+
|
| 30 |
+
# Update drivers with bad user_id
|
| 31 |
+
cursor.execute(
|
| 32 |
+
"UPDATE drivers SET user_id = %s WHERE user_id = %s",
|
| 33 |
+
(correct_user_id, 'user_id')
|
| 34 |
+
)
|
| 35 |
+
drivers_updated = cursor.rowcount
|
| 36 |
+
print(f"Updated {drivers_updated} drivers")
|
| 37 |
+
|
| 38 |
+
# Update assignments with bad user_id
|
| 39 |
+
cursor.execute(
|
| 40 |
+
"UPDATE assignments SET user_id = %s WHERE user_id = %s",
|
| 41 |
+
(correct_user_id, 'user_id')
|
| 42 |
+
)
|
| 43 |
+
assignments_updated = cursor.rowcount
|
| 44 |
+
print(f"Updated {assignments_updated} assignments")
|
| 45 |
+
|
| 46 |
+
conn.commit()
|
| 47 |
+
cursor.close()
|
| 48 |
+
conn.close()
|
| 49 |
+
|
| 50 |
+
print(f"\nTotal records fixed: {orders_updated + drivers_updated + assignments_updated}")
|
| 51 |
+
|
| 52 |
+
if __name__ == "__main__":
|
| 53 |
+
fix_bad_user_id()
|
server.py
CHANGED
|
@@ -13,6 +13,7 @@ import logging
|
|
| 13 |
from pathlib import Path
|
| 14 |
from typing import Literal
|
| 15 |
from datetime import datetime
|
|
|
|
| 16 |
|
| 17 |
# Add project root to path
|
| 18 |
sys.path.insert(0, str(Path(__file__).parent))
|
|
@@ -37,6 +38,38 @@ logging.basicConfig(
|
|
| 37 |
)
|
| 38 |
logger = logging.getLogger(__name__)
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
# ============================================================================
|
| 41 |
# MCP SERVER INITIALIZATION
|
| 42 |
# ============================================================================
|
|
@@ -63,67 +96,50 @@ except Exception as e:
|
|
| 63 |
|
| 64 |
def get_authenticated_user():
|
| 65 |
"""
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
Supports 3 authentication methods (in order):
|
| 69 |
-
1. Request context (from FastMCP request, if available)
|
| 70 |
-
2. Server environment variable FLEETMIND_API_KEY
|
| 71 |
-
3. Development Mode (SKIP_AUTH=true)
|
| 72 |
|
| 73 |
Returns:
|
| 74 |
User info dict with user_id, email, scopes, name or None if not authenticated
|
| 75 |
"""
|
| 76 |
try:
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
#
|
| 81 |
-
try:
|
| 82 |
-
from fastmcp.server.dependencies import get_request_context
|
| 83 |
-
context = get_request_context()
|
| 84 |
-
if context and hasattr(context, 'query_params'):
|
| 85 |
-
api_key = context.query_params.get('api_key')
|
| 86 |
-
if api_key:
|
| 87 |
-
logger.debug("API key found in query params")
|
| 88 |
-
elif context and hasattr(context, 'headers'):
|
| 89 |
-
# Try Authorization header
|
| 90 |
-
auth_header = context.headers.get('authorization', '')
|
| 91 |
-
if auth_header.startswith('Bearer '):
|
| 92 |
-
api_key = auth_header[7:]
|
| 93 |
-
logger.debug("API key found in Authorization header")
|
| 94 |
-
except (ImportError, RuntimeError, AttributeError) as e:
|
| 95 |
-
logger.debug(f"No request context available: {e}")
|
| 96 |
-
pass
|
| 97 |
-
|
| 98 |
-
# METHOD 2: Try server environment variable (for local development)
|
| 99 |
if not api_key:
|
| 100 |
api_key = os.getenv("FLEETMIND_API_KEY")
|
| 101 |
if api_key:
|
| 102 |
-
logger.debug("API key
|
| 103 |
|
| 104 |
-
#
|
| 105 |
if api_key:
|
| 106 |
from database.api_keys import verify_api_key
|
| 107 |
user_info = verify_api_key(api_key)
|
| 108 |
if user_info:
|
| 109 |
-
logger.info(f"✅
|
| 110 |
return user_info
|
| 111 |
else:
|
| 112 |
-
logger.warning(f"❌ Invalid API key
|
| 113 |
return None
|
| 114 |
|
| 115 |
# METHOD 3: Development bypass mode (local testing only)
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
-
# No authentication provided
|
| 126 |
-
logger.debug("No API key or SKIP_AUTH - authentication required")
|
| 127 |
return None
|
| 128 |
|
| 129 |
except Exception as e:
|
|
@@ -138,7 +154,7 @@ def get_authenticated_user():
|
|
| 138 |
def get_orders_resource() -> str:
|
| 139 |
"""
|
| 140 |
Real-time orders dataset for AI context.
|
| 141 |
-
Returns
|
| 142 |
|
| 143 |
Returns:
|
| 144 |
JSON string containing orders array with key fields:
|
|
@@ -146,16 +162,27 @@ def get_orders_resource() -> str:
|
|
| 146 |
- status, priority, created_at, assigned_driver_id
|
| 147 |
"""
|
| 148 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
query = """
|
| 150 |
SELECT order_id, customer_name, delivery_address,
|
| 151 |
status, priority, created_at, assigned_driver_id
|
| 152 |
FROM orders
|
| 153 |
-
WHERE
|
|
|
|
| 154 |
ORDER BY created_at DESC
|
| 155 |
LIMIT 1000
|
| 156 |
"""
|
| 157 |
-
orders = execute_query(query)
|
| 158 |
-
logger.info(f"Resource orders://all -
|
| 159 |
return json.dumps(orders, default=str, indent=2)
|
| 160 |
except Exception as e:
|
| 161 |
logger.error(f"Resource orders://all failed: {e}")
|
|
@@ -166,7 +193,7 @@ def get_orders_resource() -> str:
|
|
| 166 |
def get_drivers_resource() -> str:
|
| 167 |
"""
|
| 168 |
Real-time drivers dataset for AI context.
|
| 169 |
-
Returns
|
| 170 |
|
| 171 |
Returns:
|
| 172 |
JSON string containing drivers array with key fields:
|
|
@@ -174,14 +201,25 @@ def get_drivers_resource() -> str:
|
|
| 174 |
- current_lat, current_lng, last_location_update
|
| 175 |
"""
|
| 176 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
query = """
|
| 178 |
SELECT driver_id, name, status, vehicle_type, vehicle_plate,
|
| 179 |
current_lat, current_lng, last_location_update
|
| 180 |
FROM drivers
|
|
|
|
| 181 |
ORDER BY name ASC
|
| 182 |
"""
|
| 183 |
-
drivers = execute_query(query)
|
| 184 |
-
logger.info(f"Resource drivers://all -
|
| 185 |
return json.dumps(drivers, default=str, indent=2)
|
| 186 |
except Exception as e:
|
| 187 |
logger.error(f"Resource drivers://all failed: {e}")
|
|
@@ -1830,5 +1868,7 @@ if __name__ == "__main__":
|
|
| 1830 |
logger.info("Tools: 27 tools registered (19 core + 6 assignment + 2 bulk delete)")
|
| 1831 |
logger.info("Resources: 2 resources available")
|
| 1832 |
logger.info("Prompts: 3 workflow templates")
|
|
|
|
|
|
|
| 1833 |
logger.info("Starting MCP server...")
|
| 1834 |
mcp.run()
|
|
|
|
| 13 |
from pathlib import Path
|
| 14 |
from typing import Literal
|
| 15 |
from datetime import datetime
|
| 16 |
+
from contextvars import ContextVar
|
| 17 |
|
| 18 |
# Add project root to path
|
| 19 |
sys.path.insert(0, str(Path(__file__).parent))
|
|
|
|
| 38 |
)
|
| 39 |
logger = logging.getLogger(__name__)
|
| 40 |
|
| 41 |
+
# ============================================================================
|
| 42 |
+
# API KEY EXTRACTION FROM REQUEST
|
| 43 |
+
# ============================================================================
|
| 44 |
+
|
| 45 |
+
# Store API key per request using context variable
|
| 46 |
+
_current_api_key: ContextVar[str] = ContextVar('api_key', default=None)
|
| 47 |
+
|
| 48 |
+
def get_api_key_from_context() -> str:
|
| 49 |
+
"""
|
| 50 |
+
Get API key from the current request context.
|
| 51 |
+
This is set by our custom dependencies.
|
| 52 |
+
"""
|
| 53 |
+
return _current_api_key.get(None)
|
| 54 |
+
|
| 55 |
+
def extract_api_key_from_request():
|
| 56 |
+
"""
|
| 57 |
+
Extract API key from HTTP request and store in context variable.
|
| 58 |
+
Called at the start of each tool execution.
|
| 59 |
+
"""
|
| 60 |
+
try:
|
| 61 |
+
from fastmcp.server.dependencies import get_http_request
|
| 62 |
+
request = get_http_request()
|
| 63 |
+
api_key = request.query_params.get('api_key')
|
| 64 |
+
if api_key:
|
| 65 |
+
_current_api_key.set(api_key)
|
| 66 |
+
logger.debug(f"API key extracted from request: {api_key[:10]}...")
|
| 67 |
+
return api_key
|
| 68 |
+
except RuntimeError:
|
| 69 |
+
# No HTTP request available (e.g., stdio transport)
|
| 70 |
+
logger.debug("No HTTP request available for API key extraction")
|
| 71 |
+
return None
|
| 72 |
+
|
| 73 |
# ============================================================================
|
| 74 |
# MCP SERVER INITIALIZATION
|
| 75 |
# ============================================================================
|
|
|
|
| 96 |
|
| 97 |
def get_authenticated_user():
|
| 98 |
"""
|
| 99 |
+
Get authenticated user by validating API key from request context.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
Returns:
|
| 102 |
User info dict with user_id, email, scopes, name or None if not authenticated
|
| 103 |
"""
|
| 104 |
try:
|
| 105 |
+
# METHOD 1: Extract API key from current HTTP request
|
| 106 |
+
api_key = extract_api_key_from_request()
|
| 107 |
+
|
| 108 |
+
# METHOD 2: Fallback to environment variable for testing
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
if not api_key:
|
| 110 |
api_key = os.getenv("FLEETMIND_API_KEY")
|
| 111 |
if api_key:
|
| 112 |
+
logger.debug("Using API key from environment")
|
| 113 |
|
| 114 |
+
# Validate the API key
|
| 115 |
if api_key:
|
| 116 |
from database.api_keys import verify_api_key
|
| 117 |
user_info = verify_api_key(api_key)
|
| 118 |
if user_info:
|
| 119 |
+
logger.info(f"✅ Authenticated: {user_info['email']} (user_id: {user_info['user_id']})")
|
| 120 |
return user_info
|
| 121 |
else:
|
| 122 |
+
logger.warning(f"❌ Invalid API key: {api_key[:10]}...")
|
| 123 |
return None
|
| 124 |
|
| 125 |
# METHOD 3: Development bypass mode (local testing only)
|
| 126 |
+
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 127 |
+
env = os.getenv("ENV", "production").lower()
|
| 128 |
+
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 129 |
+
|
| 130 |
+
if skip_auth:
|
| 131 |
+
if env == "production":
|
| 132 |
+
logger.error("⚠️ SKIP_AUTH is enabled but ENV=production - DENYING access for security")
|
| 133 |
+
return None
|
| 134 |
+
else:
|
| 135 |
+
logger.warning(f"⚠️ SKIP_AUTH enabled in {env} environment - using development user")
|
| 136 |
+
return {
|
| 137 |
+
'user_id': 'dev-user',
|
| 138 |
+
'email': 'dev@fleetmind.local',
|
| 139 |
+
'scopes': ['admin'],
|
| 140 |
+
'name': 'Development User'
|
| 141 |
+
}
|
| 142 |
|
|
|
|
|
|
|
| 143 |
return None
|
| 144 |
|
| 145 |
except Exception as e:
|
|
|
|
| 154 |
def get_orders_resource() -> str:
|
| 155 |
"""
|
| 156 |
Real-time orders dataset for AI context.
|
| 157 |
+
Returns authenticated user's orders from the last 30 days.
|
| 158 |
|
| 159 |
Returns:
|
| 160 |
JSON string containing orders array with key fields:
|
|
|
|
| 162 |
- status, priority, created_at, assigned_driver_id
|
| 163 |
"""
|
| 164 |
try:
|
| 165 |
+
# Authenticate user
|
| 166 |
+
user = get_authenticated_user()
|
| 167 |
+
if not user:
|
| 168 |
+
logger.warning("Resource orders://all - Authentication required")
|
| 169 |
+
return json.dumps({
|
| 170 |
+
"error": "Authentication required",
|
| 171 |
+
"message": "Please provide a valid API key to access orders"
|
| 172 |
+
})
|
| 173 |
+
|
| 174 |
+
# Query only this user's orders
|
| 175 |
query = """
|
| 176 |
SELECT order_id, customer_name, delivery_address,
|
| 177 |
status, priority, created_at, assigned_driver_id
|
| 178 |
FROM orders
|
| 179 |
+
WHERE user_id = %s
|
| 180 |
+
AND created_at > NOW() - INTERVAL '30 days'
|
| 181 |
ORDER BY created_at DESC
|
| 182 |
LIMIT 1000
|
| 183 |
"""
|
| 184 |
+
orders = execute_query(query, (user['user_id'],))
|
| 185 |
+
logger.info(f"Resource orders://all - User {user['user_id']} retrieved {len(orders) if orders else 0} orders")
|
| 186 |
return json.dumps(orders, default=str, indent=2)
|
| 187 |
except Exception as e:
|
| 188 |
logger.error(f"Resource orders://all failed: {e}")
|
|
|
|
| 193 |
def get_drivers_resource() -> str:
|
| 194 |
"""
|
| 195 |
Real-time drivers dataset for AI context.
|
| 196 |
+
Returns authenticated user's drivers with current locations and status.
|
| 197 |
|
| 198 |
Returns:
|
| 199 |
JSON string containing drivers array with key fields:
|
|
|
|
| 201 |
- current_lat, current_lng, last_location_update
|
| 202 |
"""
|
| 203 |
try:
|
| 204 |
+
# Authenticate user
|
| 205 |
+
user = get_authenticated_user()
|
| 206 |
+
if not user:
|
| 207 |
+
logger.warning("Resource drivers://all - Authentication required")
|
| 208 |
+
return json.dumps({
|
| 209 |
+
"error": "Authentication required",
|
| 210 |
+
"message": "Please provide a valid API key to access drivers"
|
| 211 |
+
})
|
| 212 |
+
|
| 213 |
+
# Query only this user's drivers
|
| 214 |
query = """
|
| 215 |
SELECT driver_id, name, status, vehicle_type, vehicle_plate,
|
| 216 |
current_lat, current_lng, last_location_update
|
| 217 |
FROM drivers
|
| 218 |
+
WHERE user_id = %s
|
| 219 |
ORDER BY name ASC
|
| 220 |
"""
|
| 221 |
+
drivers = execute_query(query, (user['user_id'],))
|
| 222 |
+
logger.info(f"Resource drivers://all - User {user['user_id']} retrieved {len(drivers) if drivers else 0} drivers")
|
| 223 |
return json.dumps(drivers, default=str, indent=2)
|
| 224 |
except Exception as e:
|
| 225 |
logger.error(f"Resource drivers://all failed: {e}")
|
|
|
|
| 1868 |
logger.info("Tools: 27 tools registered (19 core + 6 assignment + 2 bulk delete)")
|
| 1869 |
logger.info("Resources: 2 resources available")
|
| 1870 |
logger.info("Prompts: 3 workflow templates")
|
| 1871 |
+
logger.info("Authentication: Multi-tenant API key via URL query params")
|
| 1872 |
+
logger.info("Usage: Connect with ?api_key=YOUR_KEY in the SSE URL")
|
| 1873 |
logger.info("Starting MCP server...")
|
| 1874 |
mcp.run()
|
verify_fix.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Verify the user_id fix"""
|
| 2 |
+
|
| 3 |
+
from database.connection import get_db_connection
|
| 4 |
+
|
| 5 |
+
def verify_order_fix():
|
| 6 |
+
conn = get_db_connection()
|
| 7 |
+
cursor = conn.cursor()
|
| 8 |
+
|
| 9 |
+
# Check the specific order mentioned by user
|
| 10 |
+
order_id = "ORD-202511192234450754"
|
| 11 |
+
|
| 12 |
+
cursor.execute(
|
| 13 |
+
"SELECT order_id, user_id, customer_name, status FROM orders WHERE order_id = %s",
|
| 14 |
+
(order_id,)
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
result = cursor.fetchone()
|
| 18 |
+
|
| 19 |
+
if result:
|
| 20 |
+
print(f"\nOrder: {result['order_id']}")
|
| 21 |
+
print(f"User ID: {result['user_id']}")
|
| 22 |
+
print(f"Customer: {result['customer_name']}")
|
| 23 |
+
print(f"Status: {result['status']}")
|
| 24 |
+
|
| 25 |
+
if result['user_id'] == 'user_id':
|
| 26 |
+
print("\n[ERROR] Still has bad user_id value!")
|
| 27 |
+
elif result['user_id'].startswith('user_'):
|
| 28 |
+
print("\n[SUCCESS] User ID is now correct!")
|
| 29 |
+
else:
|
| 30 |
+
print(f"\n[WARNING] Unexpected user_id format: {result['user_id']}")
|
| 31 |
+
else:
|
| 32 |
+
print(f"\n[NOT FOUND] Order {order_id} not found")
|
| 33 |
+
|
| 34 |
+
# Check if any records still have bad user_id
|
| 35 |
+
print("\n--- Checking for remaining bad records ---")
|
| 36 |
+
|
| 37 |
+
for table in ['orders', 'drivers', 'assignments']:
|
| 38 |
+
cursor.execute(f"SELECT COUNT(*) as count FROM {table} WHERE user_id = %s", ('user_id',))
|
| 39 |
+
result = cursor.fetchone()
|
| 40 |
+
count = result['count'] if result else 0
|
| 41 |
+
|
| 42 |
+
if count > 0:
|
| 43 |
+
print(f"{table}: {count} records still have user_id='user_id'")
|
| 44 |
+
else:
|
| 45 |
+
print(f"{table}: All clean!")
|
| 46 |
+
|
| 47 |
+
cursor.close()
|
| 48 |
+
conn.close()
|
| 49 |
+
|
| 50 |
+
if __name__ == "__main__":
|
| 51 |
+
verify_order_fix()
|