Added strava link and refresh strava token logic

This commit is contained in:
João Silva
2023-11-09 10:43:15 +00:00
parent 1a54fe1d64
commit e36eb3f47c
6 changed files with 158 additions and 50 deletions

View File

@@ -86,6 +86,11 @@ def get_user_data(token: str):
if not user:
raise HTTPException(status_code=404, detail="User not found")
if user.strava_token is None:
is_strava_linked = 0
else:
is_strava_linked = 1
# Map the user object to a dictionary that matches the UserResponse model
user_data = {
"id": user.id,
@@ -100,9 +105,7 @@ def get_user_data(token: str):
"photo_path": user.photo_path,
"photo_path_aux": user.photo_path_aux,
"is_active": user.is_active,
"strava_token": user.strava_token,
"strava_refresh_token": user.strava_refresh_token,
"strava_token_expires_at": user.strava_token_expires_at,
"is_strava_linked": is_strava_linked,
}
return user_data
@@ -112,15 +115,26 @@ def get_user_data(token: str):
def validate_token(token: str):
try:
decoded_token = jwt.decode(token, os.getenv("SECRET_KEY"), algorithms=[os.getenv("ALGORITHM")])
user_id = decoded_token.get("id")
with get_db_session() as db_session:
access_token = db_session.query(AccessToken).filter(
AccessToken.user_id == user_id,
AccessToken.token == token
).first()
if access_token:
expiration_datetime = datetime.fromtimestamp(decoded_token['exp'])
current_time = datetime.utcnow()
if current_time > expiration_datetime:
raise JWTError("Token expired")
else:
return {"message": "Token is valid"}
else:
raise JWTError("Token expired")
#if 'exp' not in decoded_token:
# return {"message": "Token is valid"}
#else:
expiration_datetime = datetime.fromtimestamp(decoded_token['exp'])
current_time = datetime.utcnow()
if current_time > expiration_datetime:
raise JWTError("Token expired")
else:
return {"message": "Token is valid"}
except JWTError:
raise JWTError("Invalid token")

View File

@@ -1,6 +1,13 @@
import os
from stravalib.client import Client
from dotenv import load_dotenv
from datetime import datetime, timedelta
from db.db import get_db_session, User
from fastapi import APIRouter, HTTPException, Depends
from fastapi.responses import RedirectResponse
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
import logging
import requests
router = APIRouter()
@@ -9,45 +16,130 @@ logger = logging.getLogger("myLogger")
# Load the environment variables from config/.env
load_dotenv('config/.env')
#stravaClient = Client()
#access_token = client.exchange_code_for_token(
# client_id=os.getenv("STRAVA_CLIENT_ID"),
# client_secret=os.getenv("STRAVA_CLIENT_SECRET"),
# code=os.getenv("STRAVA_AUTH_CODE"),
#)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
#stravaClient = Client(access_token=access_token)
#athlete = client.get_athlete()
#activities = client.get_activities(limit=10)
@router.get("/strava/strava-callback")
async def strava_callback(state: str, code: str):
token_url = 'https://www.strava.com/oauth/token'
payload = {
'client_id': os.getenv("STRAVA_CLIENT_ID"),
'client_secret': os.getenv("STRAVA_CLIENT_SECRET"),
'code': code,
'grant_type': 'authorization_code'
}
try:
response = requests.post(token_url, data=payload)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="Error retrieving tokens from Strava.")
#for activity in activities:
# print(activity.name)
tokens = response.json()
@app.get("/strava/strava-auth")
async def start_strava_auth(request: Request):
client = Client()
authorize_url = client.authorization_url(
client_id=os.getenv("STRAVA_CLIENT_ID"), redirect_uri="http://localhost:8282/authorized"
)
return RedirectResponse(url=authorize_url)
with get_db_session() as db_session:
@app.get("/strava/authorized")
async def handle_strava_callback(request: Request):
# retrieve code parameter from request
code = request.query_params.get("code")
# Query the activities records using SQLAlchemy
db_user = db_session.query(User).filter(User.strava_state == state).first()
# create token
token_response = client.exchange_code_for_token(
client_id=os.getenv("STRAVA_CLIENT_ID"), client_secret=os.getenv("STRAVA_CLIENT_SECRET"), code=code
)
if db_user:
db_user.strava_token = tokens['access_token']
db_user.strava_refresh_token = tokens['refresh_token']
db_user.strava_token_expires_at = datetime.fromtimestamp(tokens['expires_at'])
db_session.commit() # Commit the changes to the database
# store access_token to variable
access_token = token_response["access_token"]
# store refresh_token to variable
refresh_token = token_response["refresh_token"]
expires_at = token_response["expires_at"]
# Redirect to the main page or any other desired page after processing
redirect_url = "https://gearguardian.jvslab.pt/settings/settings.php?profileSettings=1&stravaLinked=1" # Change this URL to your main page
return RedirectResponse(url=redirect_url)
else:
raise HTTPException(status_code=404, detail="User not found.")
from . import userController
# Store the 'code' somewhere or initiate the token exchange process
return {"message": "Authorization code received. You can now exchange it for an access token."}
except JWTError:
raise HTTPException(status_code=401, detail="Unauthorized")
except Error as err:
print(err)
def refresh_strava_token():
# Strava token refresh endpoint
token_url = 'https://www.strava.com/oauth/token'
try:
with get_db_session() as db_session:
# Query all users from the database
users = db_session.query(User).all()
for user in users:
#expires_at = user.strava_token_expires_at
if user.strava_token_expires_at is not None:
refresh_time = user.strava_token_expires_at - timedelta(minutes=60)
if datetime.utcnow() > refresh_time:
# Parameters for the token refresh request
payload = {
"client_id": os.getenv("STRAVA_CLIENT_ID"),
"client_secret": os.getenv("STRAVA_CLIENT_SECRET"),
"refresh_token": user.strava_refresh_token,
"grant_type": "refresh_token",
}
try:
# Make a POST request to refresh the Strava token
response = requests.post(token_url, data=payload)
response.raise_for_status() # Raise an error for bad responses
tokens = response.json()
# Update the user in the database
db_user = db_session.query(User).filter(User.id == user.id).first()
if db_user:
db_user.strava_token = tokens['access_token']
db_user.strava_refresh_token = tokens['refresh_token']
db_user.strava_token_expires_at = datetime.fromtimestamp(tokens['expires_at'])
db_session.commit() # Commit the changes to the database
logger.info(f"Token refreshed successfully for user {user.id}.")
else:
logger.error("User not found in the database.")
except requests.exceptions.RequestException as req_err:
logger.error(f"Error refreshing token for user {user.id}: {req_err}")
else:
logger.info(f"Token not refreshed for user {user.id}. Will not expire in less than 60min")
else:
logger.info(f"User {user.id} does not have strava linked")
except Error as db_err:
logger.error(f"Database error: {db_err}")
# Define an HTTP PUT route to delete a user's photo
@router.put("/strava/set-user-unique-state/{state}")
async def strava_set_user_unique_state(
state: str,
token: str = Depends(oauth2_scheme)
):
from . import sessionController
try:
# Validate the user's access token using the oauth2_scheme
sessionController.validate_token(token)
with get_db_session() as db_session:
payload = jwt.decode(token, os.getenv("SECRET_KEY"), algorithms=[os.getenv("ALGORITHM")])
user_id = payload.get("id")
# Query the database to find the user by their ID
user = db_session.query(User).filter(User.id == user_id).first()
# Check if the user with the given ID exists
if not user:
raise HTTPException(status_code=404, detail="User not found")
# Set the user's photo paths to None to delete the photo
user.strava_state = state
# Commit the changes to the database
db_session.commit()
except JWTError:
# Handle JWT (JSON Web Token) authentication error
raise HTTPException(status_code=401, detail="Unauthorized")
except Exception as err:
# Handle any other unexpected exceptions
print(err)
raise HTTPException(status_code=500, detail="Failed to update user strava state")
# Return a success message
return {"message": f"Strava state for user {user_id} has been updated"}

View File

@@ -28,9 +28,7 @@ class UserResponse(BaseModel):
photo_path: Optional[str]
photo_path_aux: Optional[str]
is_active: int
strava_token: Optional[str]
strava_refresh_token: Optional[str]
strava_token_expires_at: Optional[str]
is_strava_linked: Optional[int]
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@@ -50,7 +48,7 @@ async def read_users_all(token: str = Depends(oauth2_scheme)):
users = db_session.query(User).all()
# Convert the SQLAlchemy User objects to dictionaries
results = [user.to_dict() for user in users]
results = [user.__dict__ for user in users]
except JWTError:
# Handle JWT (JSON Web Token) authentication error
raise HTTPException(status_code=401, detail="Unauthorized")

View File

@@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS `gearguardian`.`users` (
`photo_path` VARCHAR(250) NULL COMMENT 'User photo path' ,
`photo_path_aux` VARCHAR(250) NULL COMMENT 'Auxiliar photo path' ,
`is_active` INT(1) NOT NULL COMMENT 'Is user active (2 - not active, 1 - active)' ,
`strava_state` VARCHAR(45) NULL ,
`strava_token` VARCHAR(250) NULL ,
`strava_refresh_token` VARCHAR(250) NULL ,
`strava_token_expires_at` DATETIME NULL ,

View File

@@ -44,6 +44,7 @@ class User(Base):
photo_path = Column(String(length=250), nullable=True, comment='User photo path')
photo_path_aux = Column(String(length=250), nullable=True, comment='Auxiliary photo path')
is_active = Column(Integer, nullable=False, comment='Is user active (2 - not active, 1 - active)')
strava_state = Column(String(length=45), nullable=True)
strava_token = Column(String(length=250), nullable=True)
strava_refresh_token = Column(String(length=250), nullable=True)
strava_token_expires_at = Column(DateTime, nullable=True)

View File

@@ -1,6 +1,6 @@
from fastapi import FastAPI
from apscheduler.schedulers.background import BackgroundScheduler
from controllers import sessionController, userController, gearController, activitiesController
from controllers import sessionController, userController, gearController, activitiesController, stravaController
import logging
app = FastAPI()
@@ -21,6 +21,7 @@ app.include_router(sessionController.router)
app.include_router(userController.router)
app.include_router(gearController.router)
app.include_router(activitiesController.router)
app.include_router(stravaController.router)
# Create a background scheduler instance
scheduler = BackgroundScheduler()
@@ -28,6 +29,7 @@ scheduler.start()
# Remove the leading space
scheduler.add_job(sessionController.remove_expired_tokens, 'interval', minutes=1)
scheduler.add_job(stravaController.refresh_strava_token, 'interval', minutes=30)
# Add the background scheduler to the app's shutdown event
@app.on_event("shutdown")