working
This commit is contained in:
Binary file not shown.
@@ -4,8 +4,12 @@ from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from sqlalchemy.orm import Session
|
||||
import traceback
|
||||
import httpx
|
||||
import base64
|
||||
import json
|
||||
from ..services.postgresql_manager import PostgreSQLManager
|
||||
from ..utils.config import config
|
||||
import garth
|
||||
from ..services.garmin.client import GarminClient
|
||||
|
||||
router = APIRouter()
|
||||
@@ -35,22 +39,117 @@ class AuthStatusResponse(BaseModel):
|
||||
garmin: Optional[dict] = None
|
||||
fitbit: Optional[dict] = None
|
||||
|
||||
class AuthStatusResponse(BaseModel):
|
||||
garmin: Optional[dict] = None
|
||||
fitbit: Optional[dict] = None
|
||||
|
||||
@router.post("/setup/load-consul-config")
|
||||
async def load_consul_config(db: Session = Depends(get_db)):
|
||||
"""
|
||||
Load configuration from Consul and save it to the database.
|
||||
It first tries to use tokens from Consul, if they are not present, it falls back to username/password login.
|
||||
"""
|
||||
consul_url = "http://consul.service.dc1.consul:8500/v1/kv/fitbit-garmin-sync/config"
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(consul_url)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
if not (data and 'Value' in data[0]):
|
||||
raise HTTPException(status_code=404, detail="Config not found in Consul")
|
||||
|
||||
config_value = base64.b64decode(data[0]['Value']).decode('utf-8')
|
||||
config = json.loads(config_value)
|
||||
|
||||
if 'garmin' in config:
|
||||
garmin_config = config['garmin']
|
||||
from ..models.api_token import APIToken
|
||||
from datetime import datetime
|
||||
|
||||
# Prefer tokens if available
|
||||
if 'garth_oauth1_token' in garmin_config and 'garth_oauth2_token' in garmin_config:
|
||||
token_record = db.query(APIToken).filter_by(token_type='garmin').first()
|
||||
if not token_record:
|
||||
token_record = APIToken(token_type='garmin')
|
||||
db.add(token_record)
|
||||
|
||||
token_record.garth_oauth1_token = garmin_config['garth_oauth1_token']
|
||||
token_record.garth_oauth2_token = garmin_config['garth_oauth2_token']
|
||||
token_record.updated_at = datetime.now()
|
||||
db.commit()
|
||||
|
||||
return {"status": "success", "message": "Garmin tokens from Consul have been saved."}
|
||||
|
||||
# Fallback to username/password login
|
||||
elif 'username' in garmin_config and 'password' in garmin_config:
|
||||
garmin_creds = GarminCredentials(**garmin_config)
|
||||
garmin_client = GarminClient(garmin_creds.username, garmin_creds.password, garmin_creds.is_china)
|
||||
status = garmin_client.login()
|
||||
|
||||
if status == "mfa_required":
|
||||
return {"status": "mfa_required", "message": "Garmin login from Consul requires MFA. Please complete it manually."}
|
||||
elif status != "success":
|
||||
raise HTTPException(status_code=400, detail=f"Failed to login to Garmin with Consul credentials: {status}")
|
||||
|
||||
# TODO: Add Fitbit credentials handling
|
||||
|
||||
return {"status": "success", "message": "Configuration from Consul processed."}
|
||||
|
||||
except httpx.RequestError as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to connect to Consul: {e}")
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise HTTPException(status_code=500, detail=f"An error occurred: {e}")
|
||||
|
||||
@router.get("/setup/auth-status", response_model=AuthStatusResponse)
|
||||
async def get_auth_status(db: Session = Depends(get_db)):
|
||||
return AuthStatusResponse(
|
||||
garmin={
|
||||
"username": "example@example.com",
|
||||
"authenticated": False,
|
||||
"token_expires_at": None,
|
||||
"last_login": None,
|
||||
"is_china": False
|
||||
},
|
||||
fitbit={
|
||||
"client_id": "example_client_id",
|
||||
"authenticated": False,
|
||||
"token_expires_at": None,
|
||||
"last_login": None
|
||||
from ..models.api_token import APIToken
|
||||
|
||||
garmin_status = {}
|
||||
fitbit_status = {}
|
||||
|
||||
# Garmin Status
|
||||
garmin_token = db.query(APIToken).filter_by(token_type='garmin').first()
|
||||
if garmin_token:
|
||||
garmin_status = {
|
||||
"token_stored": True,
|
||||
"authenticated": garmin_token.garth_oauth1_token is not None and garmin_token.garth_oauth2_token is not None,
|
||||
"garth_oauth1_token_exists": garmin_token.garth_oauth1_token is not None,
|
||||
"garth_oauth2_token_exists": garmin_token.garth_oauth2_token is not None,
|
||||
"mfa_state_exists": garmin_token.mfa_state is not None,
|
||||
"mfa_expires_at": garmin_token.mfa_expires_at,
|
||||
"last_used": garmin_token.last_used,
|
||||
"updated_at": garmin_token.updated_at,
|
||||
"username": "N/A", # Placeholder, username is not stored in APIToken
|
||||
"is_china": False # Placeholder
|
||||
}
|
||||
else:
|
||||
garmin_status = {
|
||||
"token_stored": False,
|
||||
"authenticated": False
|
||||
}
|
||||
|
||||
# Fitbit Status (Existing logic, might need adjustment if Fitbit tokens are stored differently)
|
||||
fitbit_token = db.query(APIToken).filter_by(token_type='fitbit').first()
|
||||
if fitbit_token:
|
||||
fitbit_status = {
|
||||
"token_stored": True,
|
||||
"authenticated": fitbit_token.access_token is not None,
|
||||
"client_id": fitbit_token.access_token[:10] + "..." if fitbit_token.access_token else "N/A",
|
||||
"expires_at": fitbit_token.expires_at,
|
||||
"last_used": fitbit_token.last_used,
|
||||
"updated_at": fitbit_token.updated_at
|
||||
}
|
||||
else:
|
||||
fitbit_status = {
|
||||
"token_stored": False,
|
||||
"authenticated": False
|
||||
}
|
||||
|
||||
return AuthStatusResponse(
|
||||
garmin=garmin_status,
|
||||
fitbit=fitbit_status
|
||||
)
|
||||
|
||||
@router.post("/setup/garmin")
|
||||
@@ -63,44 +162,25 @@ async def save_garmin_credentials(credentials: GarminCredentials, db: Session =
|
||||
garmin_client = GarminClient(credentials.username, credentials.password, credentials.is_china)
|
||||
logger.debug("GarminClient instance created successfully")
|
||||
|
||||
try:
|
||||
logger.debug("Attempting to log in to Garmin")
|
||||
garmin_client.login()
|
||||
|
||||
logger.info(f"Successfully authenticated Garmin user: {credentials.username}")
|
||||
logger.debug("Attempting to log in to Garmin")
|
||||
# Check the status returned directly
|
||||
status = garmin_client.login()
|
||||
|
||||
if status == "mfa_required":
|
||||
# Hardcode the session_id as 'garmin' since you use a single record in APIToken
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={"status": "success", "message": "Garmin credentials saved and authenticated successfully"}
|
||||
content={
|
||||
"status": "mfa_required",
|
||||
"message": "MFA Required",
|
||||
"session_id": "garmin"
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error during Garmin authentication: {str(e)}")
|
||||
|
||||
error_message = str(e)
|
||||
|
||||
if "MFA" in error_message or "mfa" in error_message.lower() or "MFA Required" in error_message:
|
||||
logger.info("MFA required for Garmin authentication")
|
||||
try:
|
||||
session_id = garmin_client.initiate_mfa(credentials.username)
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={
|
||||
"status": "mfa_required",
|
||||
"message": "Multi-factor authentication required",
|
||||
"session_id": session_id
|
||||
}
|
||||
)
|
||||
except Exception as mfa_error:
|
||||
logger.error(f"Error initiating MFA: {str(mfa_error)}")
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"status": "error", "message": f"Error initiating MFA: {str(mfa_error)}"}
|
||||
)
|
||||
else:
|
||||
# For other exceptions during login, return a generic error
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"status": "error", "message": f"An unexpected error occurred: {error_message}"}
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={"status": "success", "message": "Logged in!"}
|
||||
)
|
||||
|
||||
@router.post("/setup/garmin/mfa")
|
||||
async def complete_garmin_mfa(mfa_request: GarminMFARequest, db: Session = Depends(get_db)):
|
||||
@@ -156,3 +236,48 @@ async def save_fitbit_credentials(credentials: FitbitCredentials, db: Session =
|
||||
@router.post("/setup/fitbit/callback")
|
||||
async def fitbit_callback(callback_data: FitbitCallback, db: Session = Depends(get_db)):
|
||||
return {"status": "success", "message": "Fitbit OAuth flow completed successfully"}
|
||||
|
||||
@router.post("/setup/garmin/test-token")
|
||||
async def test_garmin_token(db: Session = Depends(get_db)):
|
||||
from ..models.api_token import APIToken
|
||||
from garth.auth_tokens import OAuth1Token, OAuth2Token
|
||||
import json
|
||||
|
||||
token_record = db.query(APIToken).filter_by(token_type='garmin').first()
|
||||
if not token_record or not token_record.garth_oauth1_token or not token_record.garth_oauth2_token:
|
||||
raise HTTPException(status_code=404, detail="Garmin token not found or incomplete.")
|
||||
|
||||
try:
|
||||
from ..utils.helpers import setup_logger
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
logger.info("garth_oauth1_token from DB: %s", token_record.garth_oauth1_token)
|
||||
logger.info("Type of garth_oauth1_token: %s", type(token_record.garth_oauth1_token))
|
||||
logger.info("garth_oauth2_token from DB: %s", token_record.garth_oauth2_token)
|
||||
logger.info("Type of garth_oauth2_token: %s", type(token_record.garth_oauth2_token))
|
||||
|
||||
if not token_record.garth_oauth1_token or not token_record.garth_oauth2_token:
|
||||
raise HTTPException(status_code=400, detail="OAuth1 or OAuth2 token is empty.")
|
||||
|
||||
import garth
|
||||
|
||||
# Parse JSON to dictionaries
|
||||
oauth1_dict = json.loads(token_record.garth_oauth1_token)
|
||||
oauth2_dict = json.loads(token_record.garth_oauth2_token)
|
||||
|
||||
# Convert to proper token objects
|
||||
garth.client.oauth1_token = OAuth1Token(**oauth1_dict)
|
||||
garth.client.oauth2_token = OAuth2Token(**oauth2_dict)
|
||||
|
||||
# Also configure the domain if present
|
||||
if oauth1_dict.get('domain'):
|
||||
garth.configure(domain=oauth1_dict['domain'])
|
||||
|
||||
profile_info = garth.UserProfile.get()
|
||||
return profile_info
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise HTTPException(status_code=500, detail=f"Failed to test Garmin token: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user