Fix Garmin Connect save — make password optional for settings updates
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 7s
Build and push images / build-worker (push) Successful in 49s
Build and push images / build-frontend (push) Successful in 27s

password was required in GarminConfigIn, causing a 422 when users updated
toggles or sync_lookback_days without re-entering their credentials.

Now only re-authenticates when a new password is supplied; settings-only
updates (sync_enabled, sync_activities, sync_wellness, sync_lookback_days)
go through without touching the stored credentials.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 00:58:00 +01:00
parent f8c126fbda
commit a3c039b3ea
+21 -20
View File
@@ -14,7 +14,7 @@ router = APIRouter()
class GarminConfigIn(BaseModel): class GarminConfigIn(BaseModel):
email: str email: str
password: str # plaintext; encrypted before storage password: Optional[str] = None # plaintext; encrypted before storage. None = keep existing.
sync_enabled: bool = True sync_enabled: bool = True
sync_activities: bool = True sync_activities: bool = True
sync_wellness: bool = True sync_wellness: bool = True
@@ -69,33 +69,29 @@ async def save_config(
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
): ):
""" """
Save (or replace) Garmin Connect credentials. Save Garmin Connect settings. If a password is provided, re-authenticates and
Attempts a test login first so we can store the initial OAuth token and refreshes the stored OAuth token. If no password is provided, only updates the
fail fast on wrong credentials. non-credential settings (toggles, lookback days) without re-logging in.
""" """
from app.services.garmin_connect_sync import encrypt_password, authenticate_garmin from app.services.garmin_connect_sync import encrypt_password, authenticate_garmin
enc = encrypt_password(body.password)
# Test login — raises HTTPException on auth failure
try:
garmin, token_store = authenticate_garmin(body.email, enc, None)
except Exception as exc:
raise HTTPException(status_code=400, detail=f"Garmin login failed: {exc}")
result = await db.execute( result = await db.execute(
select(GarminConnectConfig).where(GarminConnectConfig.user_id == current_user.id) select(GarminConnectConfig).where(GarminConnectConfig.user_id == current_user.id)
) )
cfg = result.scalar_one_or_none() cfg = result.scalar_one_or_none()
if body.password:
# Credentials update — test-login before saving
enc = encrypt_password(body.password)
try:
garmin, token_store = authenticate_garmin(body.email, enc, None)
except Exception as exc:
raise HTTPException(status_code=400, detail=f"Garmin login failed: {exc}")
if cfg: if cfg:
cfg.email = body.email cfg.email = body.email
cfg.password_enc = enc cfg.password_enc = enc
cfg.token_store = token_store cfg.token_store = token_store
cfg.sync_enabled = body.sync_enabled
cfg.sync_activities = body.sync_activities
cfg.sync_wellness = body.sync_wellness
cfg.sync_lookback_days = body.sync_lookback_days
cfg.last_sync_status = "Credentials updated" cfg.last_sync_status = "Credentials updated"
else: else:
cfg = GarminConnectConfig( cfg = GarminConnectConfig(
@@ -103,13 +99,18 @@ async def save_config(
email=body.email, email=body.email,
password_enc=enc, password_enc=enc,
token_store=token_store, token_store=token_store,
sync_enabled=body.sync_enabled,
sync_activities=body.sync_activities,
sync_wellness=body.sync_wellness,
sync_lookback_days=body.sync_lookback_days,
last_sync_status="Connected", last_sync_status="Connected",
) )
db.add(cfg) db.add(cfg)
else:
# Settings-only update — password unchanged
if not cfg:
raise HTTPException(status_code=400, detail="No Garmin account connected — password required for first-time setup")
cfg.sync_enabled = body.sync_enabled
cfg.sync_activities = body.sync_activities
cfg.sync_wellness = body.sync_wellness
cfg.sync_lookback_days = body.sync_lookback_days
await db.commit() await db.commit()
await db.refresh(cfg) await db.refresh(cfg)