"""Authentication utilities for Strava API."""
import os
from typing import Any
import httpx
from dotenv import load_dotenv
load_dotenv()
def get_authorization_url(client_id: str, redirect_uri: str = "http://localhost") -> str:
"""
Generate the Strava OAuth authorization URL.
Args:
client_id: The Strava API client ID
redirect_uri: The redirect URI (default: http://localhost)
Returns:
Authorization URL string
"""
scope = "read,activity:read,activity:read_all,profile:read_all"
return (
f"https://www.strava.com/oauth/authorize"
f"?client_id={client_id}"
f"&redirect_uri={redirect_uri}"
f"&response_type=code"
f"&scope={scope}"
)
def exchange_code_for_token(
client_id: str, client_secret: str, auth_code: str
) -> dict[str, Any]:
"""
Exchange the authorization code for tokens.
Args:
client_id: The Strava API client ID
client_secret: The Strava API client secret
auth_code: The authorization code from the redirect URL
Returns:
Dictionary containing token data
Raises:
Exception: If the token exchange fails
"""
token_url = "https://www.strava.com/oauth/token"
payload = {
"client_id": client_id,
"client_secret": client_secret,
"code": auth_code,
"grant_type": "authorization_code",
}
try:
with httpx.Client(timeout=30.0) as client:
response = client.post(token_url, data=payload)
response.raise_for_status()
return dict(response.json())
except httpx.HTTPStatusError as e:
raise Exception(f"Error {e.response.status_code}: {e.response.text}") from e
except Exception as e:
raise Exception(f"Error exchanging code for token: {e}") from e
def save_credentials(
client_id: str, client_secret: str, env_file: str = ".env"
) -> dict[str, Any]:
"""
Save client_id and client_secret to .env file.
Args:
client_id: The Strava API client ID
client_secret: The Strava API client secret
env_file: Path to the .env file (default: .env)
Returns:
Dictionary with status and file path
"""
env_vars: dict[str, str] = {}
if os.path.exists(env_file):
with open(env_file, "r") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
env_vars[key] = value
env_vars["STRAVA_CLIENT_ID"] = client_id
env_vars["STRAVA_CLIENT_SECRET"] = client_secret
with open(env_file, "w") as f:
for key, value in env_vars.items():
f.write(f"{key}={value}\n")
load_dotenv(override=True)
return {
"status": "success",
"file": env_file,
"message": f"Saved credentials to {env_file}",
}
def get_env_credentials() -> dict[str, Any]:
"""
Get Strava credentials from environment variables.
Returns:
Dictionary with client_id, client_secret, and refresh_token status
"""
client_id = os.environ.get("STRAVA_CLIENT_ID")
client_secret = os.environ.get("STRAVA_CLIENT_SECRET")
refresh_token = os.environ.get("STRAVA_REFRESH_TOKEN")
return {
"client_id": client_id,
"client_secret": "***" if client_secret else None,
"has_refresh_token": bool(refresh_token),
"is_configured": bool(client_id and client_secret and refresh_token),
}
def complete_auth_with_code(auth_code: str, env_file: str = ".env") -> dict[str, Any]:
"""
Complete authentication using stored credentials and authorization code.
Args:
auth_code: The authorization code from the redirect URL
env_file: Path to the .env file (default: .env)
Returns:
Dictionary containing setup status and token information
Raises:
Exception: If credentials are not found or token exchange fails
"""
load_dotenv(override=True)
client_id = os.environ.get("STRAVA_CLIENT_ID")
client_secret = os.environ.get("STRAVA_CLIENT_SECRET")
if not client_id or not client_secret:
raise Exception(
"Client ID and Client Secret not found. Please save them first using save_credentials."
)
token_data = exchange_code_for_token(client_id, client_secret, auth_code)
_update_env_file(client_id, client_secret, token_data, env_file)
return {
"status": "success",
"message": "Strava authentication completed successfully",
"refresh_token_preview": token_data["refresh_token"][:10] + "...",
"expires_at": token_data.get("expires_at"),
"env_file": env_file,
}
def _update_env_file(
client_id: str,
client_secret: str,
token_data: dict[str, Any],
env_file: str = ".env",
) -> None:
"""Update the .env file with token data and credentials."""
env_vars: dict[str, str] = {}
if os.path.exists(env_file):
with open(env_file, "r") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
env_vars[key] = value
env_vars["STRAVA_CLIENT_ID"] = client_id
env_vars["STRAVA_CLIENT_SECRET"] = client_secret
env_vars["STRAVA_REFRESH_TOKEN"] = token_data["refresh_token"]
env_vars["STRAVA_ACCESS_TOKEN"] = token_data.get("access_token", "")
env_vars["STRAVA_EXPIRES_AT"] = str(token_data.get("expires_at", ""))
with open(env_file, "w") as f:
for key, value in env_vars.items():
f.write(f"{key}={value}\n")