burtenshaw commited on
Commit
2e832d9
Β·
1 Parent(s): 84c7caa

make auth steps more explicit

Browse files
Files changed (2) hide show
  1. check_token_scopes.py +126 -0
  2. strava_mcp/gradio_server.py +39 -6
check_token_scopes.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Utility script to check what scopes are available with your current refresh token.
4
+ This helps diagnose scope-related issues.
5
+ """
6
+
7
+ import asyncio
8
+ import os
9
+ import sys
10
+ from datetime import datetime
11
+
12
+ import httpx
13
+
14
+
15
+ async def check_token_scopes(client_id: str, client_secret: str, refresh_token: str):
16
+ """Check what scopes are available with the given refresh token."""
17
+
18
+ print("πŸ” Checking refresh token scopes...")
19
+
20
+ try:
21
+ # Refresh the access token
22
+ async with httpx.AsyncClient() as client:
23
+ response = await client.post(
24
+ "https://www.strava.com/oauth/token",
25
+ json={
26
+ "client_id": client_id,
27
+ "client_secret": client_secret,
28
+ "refresh_token": refresh_token,
29
+ "grant_type": "refresh_token",
30
+ },
31
+ )
32
+
33
+ if response.status_code != 200:
34
+ print(
35
+ f"❌ Failed to refresh token: {response.status_code} - {response.text}"
36
+ )
37
+ return False
38
+
39
+ token_data = response.json()
40
+ access_token = token_data["access_token"]
41
+ scope = token_data.get("scope", "unknown")
42
+
43
+ print(f"βœ… Token refresh successful")
44
+ print(f"πŸ“‹ Available scopes: {scope}")
45
+
46
+ # Check if we have the required scopes
47
+ required_scopes = ["read", "activity:read"]
48
+ missing_scopes = []
49
+
50
+ for req_scope in required_scopes:
51
+ if req_scope not in scope:
52
+ missing_scopes.append(req_scope)
53
+
54
+ if missing_scopes:
55
+ print(f"❌ Missing required scopes: {', '.join(missing_scopes)}")
56
+ print("πŸ”§ You need to get a new refresh token with the correct scopes")
57
+ return False
58
+ else:
59
+ print("βœ… All required scopes are present")
60
+
61
+ # Test a simple API call
62
+ print("\nπŸ§ͺ Testing API call to /athlete/activities...")
63
+ response = await client.get(
64
+ "https://www.strava.com/api/v3/athlete/activities",
65
+ headers={"Authorization": f"Bearer {access_token}"},
66
+ params={"per_page": 1},
67
+ )
68
+
69
+ if response.status_code == 200:
70
+ print("βœ… API call successful! Your token works correctly.")
71
+ activities = response.json()
72
+ print(f"πŸ“Š Found {len(activities)} activities in test call")
73
+ return True
74
+ else:
75
+ print(
76
+ f"❌ API call failed: {response.status_code} - {response.text}"
77
+ )
78
+ return False
79
+
80
+ except Exception as e:
81
+ print(f"❌ Error checking token: {e}")
82
+ return False
83
+
84
+
85
+ def main():
86
+ """Main function."""
87
+ print("πŸ” Strava Refresh Token Scope Checker\n")
88
+
89
+ # Get credentials from environment or command line
90
+ client_id = os.environ.get("STRAVA_CLIENT_ID")
91
+ client_secret = os.environ.get("STRAVA_CLIENT_SECRET")
92
+ refresh_token = os.environ.get("STRAVA_REFRESH_TOKEN")
93
+
94
+ if len(sys.argv) == 4:
95
+ client_id = sys.argv[1]
96
+ client_secret = sys.argv[2]
97
+ refresh_token = sys.argv[3]
98
+ elif not all([client_id, client_secret, refresh_token]):
99
+ print("Usage:")
100
+ print(
101
+ " python check_token_scopes.py <client_id> <client_secret> <refresh_token>"
102
+ )
103
+ print(
104
+ " Or set environment variables: STRAVA_CLIENT_ID, STRAVA_CLIENT_SECRET, STRAVA_REFRESH_TOKEN"
105
+ )
106
+ sys.exit(1)
107
+
108
+ # Run the check
109
+ success = asyncio.run(check_token_scopes(client_id, client_secret, refresh_token))
110
+
111
+ if not success:
112
+ print("\n🚨 SOLUTION:")
113
+ print("1. Go to your Hugging Face Space")
114
+ print("2. Click on the 'OAuth Helper' tab")
115
+ print(
116
+ "3. Follow the instructions to get a new refresh token with correct scopes"
117
+ )
118
+ print(
119
+ "4. Update your STRAVA_REFRESH_TOKEN environment variable or use the Authentication tab"
120
+ )
121
+
122
+ sys.exit(0 if success else 1)
123
+
124
+
125
+ if __name__ == "__main__":
126
+ main()
strava_mcp/gradio_server.py CHANGED
@@ -85,12 +85,14 @@ def get_authorization_url() -> str:
85
 
86
  # For Hugging Face Spaces, we need to provide a redirect URI that points to a manual flow
87
  redirect_uri = "http://localhost:3008/exchange_token" # This is just for display
88
- auth_url = f"https://www.strava.com/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&scope=activity:read_all,read_all,profile:read_all&approval_prompt=force"
89
 
90
  instructions = f"""
91
  πŸ” **Manual OAuth Setup Instructions:**
92
 
93
- 1. **Click this link to authorize with Strava:**
 
 
94
  {auth_url}
95
 
96
  2. **After authorization, you'll be redirected to a page that might show an error (this is expected)**
@@ -106,7 +108,9 @@ def get_authorization_url() -> str:
106
  -d grant_type=authorization_code
107
  ```
108
 
109
- 5. **Copy the 'refresh_token' from the response** and paste it in the "Refresh Token" field above.
 
 
110
 
111
  ⚠️ **Note:** You'll need your STRAVA_CLIENT_SECRET for step 4. Contact the app administrator if you don't have it.
112
  """
@@ -140,7 +144,18 @@ async def get_user_activities(
140
  return [activity.model_dump() for activity in activities]
141
  except Exception as e:
142
  logger.error(f"Error getting user activities: {str(e)}")
143
- raise gr.Error(f"Failed to get activities: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
144
 
145
 
146
  async def get_activity_details(
@@ -165,7 +180,16 @@ async def get_activity_details(
165
  return activity.model_dump()
166
  except Exception as e:
167
  logger.error(f"Error getting activity details: {str(e)}")
168
- raise gr.Error(f"Failed to get activity details: {str(e)}")
 
 
 
 
 
 
 
 
 
169
 
170
 
171
  async def get_activity_segments(activity_id: int) -> List[Dict]:
@@ -186,7 +210,16 @@ async def get_activity_segments(activity_id: int) -> List[Dict]:
186
  return [segment.model_dump() for segment in segments]
187
  except Exception as e:
188
  logger.error(f"Error getting activity segments: {str(e)}")
189
- raise gr.Error(f"Failed to get activity segments: {str(e)}")
 
 
 
 
 
 
 
 
 
190
 
191
 
192
  def create_interface():
 
85
 
86
  # For Hugging Face Spaces, we need to provide a redirect URI that points to a manual flow
87
  redirect_uri = "http://localhost:3008/exchange_token" # This is just for display
88
+ auth_url = f"https://www.strava.com/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&scope=read_all,activity:read,activity:read_all,profile:read_all&approval_prompt=force"
89
 
90
  instructions = f"""
91
  πŸ” **Manual OAuth Setup Instructions:**
92
 
93
+ **⚠️ IMPORTANT:** If you're getting "activity:read_permission missing" errors, it means your current refresh token was created without the correct scopes. You MUST follow these steps to get a new token.
94
+
95
+ 1. **Click this link to authorize with Strava (with correct scopes):**
96
  {auth_url}
97
 
98
  2. **After authorization, you'll be redirected to a page that might show an error (this is expected)**
 
108
  -d grant_type=authorization_code
109
  ```
110
 
111
+ 5. **Copy the 'refresh_token' from the response** and paste it in the "Authentication" tab above.
112
+
113
+ πŸ” **Required Scopes:** This URL includes the correct scopes: `read_all`, `activity:read`, `activity:read_all`, `profile:read_all`
114
 
115
  ⚠️ **Note:** You'll need your STRAVA_CLIENT_SECRET for step 4. Contact the app administrator if you don't have it.
116
  """
 
144
  return [activity.model_dump() for activity in activities]
145
  except Exception as e:
146
  logger.error(f"Error getting user activities: {str(e)}")
147
+ error_msg = str(e)
148
+
149
+ # Check for scope-related errors and provide helpful guidance
150
+ if "activity:read_permission" in error_msg and "missing" in error_msg:
151
+ raise gr.Error(
152
+ "❌ Authorization Error: Your refresh token doesn't have the required 'activity:read' permission. "
153
+ "This means your token was created without the correct scopes. "
154
+ "Please use the 'OAuth Helper' tab to get a new authorization URL with the correct scopes, "
155
+ "then follow the steps to get a new refresh token with the proper permissions."
156
+ )
157
+ else:
158
+ raise gr.Error(f"Failed to get activities: {error_msg}")
159
 
160
 
161
  async def get_activity_details(
 
180
  return activity.model_dump()
181
  except Exception as e:
182
  logger.error(f"Error getting activity details: {str(e)}")
183
+ error_msg = str(e)
184
+
185
+ # Check for scope-related errors and provide helpful guidance
186
+ if "activity:read_permission" in error_msg and "missing" in error_msg:
187
+ raise gr.Error(
188
+ "❌ Authorization Error: Your refresh token doesn't have the required 'activity:read' permission. "
189
+ "Please use the 'OAuth Helper' tab to get a new refresh token with the correct scopes."
190
+ )
191
+ else:
192
+ raise gr.Error(f"Failed to get activity details: {error_msg}")
193
 
194
 
195
  async def get_activity_segments(activity_id: int) -> List[Dict]:
 
210
  return [segment.model_dump() for segment in segments]
211
  except Exception as e:
212
  logger.error(f"Error getting activity segments: {str(e)}")
213
+ error_msg = str(e)
214
+
215
+ # Check for scope-related errors and provide helpful guidance
216
+ if "activity:read_permission" in error_msg and "missing" in error_msg:
217
+ raise gr.Error(
218
+ "❌ Authorization Error: Your refresh token doesn't have the required 'activity:read' permission. "
219
+ "Please use the 'OAuth Helper' tab to get a new refresh token with the correct scopes."
220
+ )
221
+ else:
222
+ raise gr.Error(f"Failed to get activity segments: {error_msg}")
223
 
224
 
225
  def create_interface():