- Add OAuth1 to OAuth2 token exchange using Garmin's exchange endpoint
- Track refresh token expiry (~30 days) instead of access token expiry (~21 hours)
- Auto-refresh access tokens in cron sync before they expire
- Update Python script to output refresh_token_expires_at
- Add garminRefreshTokenExpiresAt field to User type and database schema
- Fix token input UX: show when warning active, not just when disconnected
- Add Cache-Control headers to /api/user and /api/garmin/status to prevent stale data
- Add oauth-1.0a package for OAuth1 signature generation
The system now automatically refreshes OAuth2 tokens using the stored OAuth1 token,
so users only need to re-run the Python auth script every ~30 days (when refresh
token expires) instead of every ~21 hours (when access token expires).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fetch fresh user data from database in status endpoint instead of
relying on auth store cookie, which may be stale after token save.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added three Garmin API endpoints for token management:
- POST /api/garmin/tokens: Accepts oauth1, oauth2, expires_at;
encrypts tokens using AES-256-GCM; stores in user record;
returns daysUntilExpiry
- DELETE /api/garmin/tokens: Clears encrypted tokens from user
record and sets garminConnected to false
- GET /api/garmin/status: Returns connection status, days until
expiry, expired flag, and warning level (critical ≤7 days,
warning 8-14 days)
All endpoints use withAuth() middleware for authentication.
Added 26 tests covering encryption, validation, auth, and
warning level thresholds.
Also added pb_data/ to .gitignore for PocketBase data.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>