mirror of
https://github.com/joaovitoriasilva/endurain.git
synced 2026-01-09 15:57:59 -05:00
Added strava link and refresh strava token logic
This commit is contained in:
@@ -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")
|
||||
|
||||
|
||||
@@ -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"}
|
||||
@@ -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")
|
||||
|
||||
@@ -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 ,
|
||||
|
||||
1
db/db.py
1
db/db.py
@@ -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)
|
||||
|
||||
4
main.py
4
main.py
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user