```html

Fixing Permanent OAuth Token Expiration: Moving the JADA Google Cloud App from Testing to Production Mode

The Problem: A 7-Day Refresh Token Ceiling

The JADA booking and calendar synchronization infrastructure has been plagued by recurring OAuth authentication failures. Every week or two, the Google Calendar API and Gmail OAuth tokens expire, requiring manual re-authentication. The root cause was never addressed—only the symptom (refreshing the token) was treated. After investigation, the issue is clear: the Google Cloud project is configured in Testing mode, which hard-limits refresh token validity to 7 days, regardless of how well the token is managed.

This affects multiple critical workflows:

  • Calendar synchronization: The CalendarSync.gs Apps Script cannot write to external calendar platforms (Boatsetter, Sailo, GetMyBoat) because Gmail OAuth is expired
  • Crew dispatch dashboard: Captain/crew assignment workflows fail when calendar Lambda endpoints can't authenticate with DynamoDB
  • Captain reports: Post-sail AAR submissions rely on OAuth-authenticated writes to the SCC (Sailing Crew Checklist) DynamoDB table

The solution is permanent: move the Google Cloud app from Testing to Production mode. This requires a one-time setup cost (OAuth consent screen configuration + security verification) but eliminates the recurring token expiration problem entirely.

Technical Details: Why Testing Mode Breaks Long-Lived Tokens

Google Cloud's OAuth 2.0 implementation treats refresh tokens differently based on app status:

  • Testing mode (Development): Refresh tokens valid for 7 days only. After 7 days of inactivity, the token is revoked automatically. This is intentional—it's a safety mechanism for apps not yet verified by Google.
  • Production mode: Refresh tokens are long-lived (typically 6 months of inactivity). Once published and verified, the app gets elevated token lifecycle guarantees.

Our infrastructure token storage is in `/Users/cb/Documents/repos/tools/token_files/` (local development) and referenced by environment variable `JADA_TOKEN_PATH`. The reauth_jada_all.py script I wrote automates the re-authentication flow, but it's a band-aid—the real fix is changing the app's OAuth consent mode.

Infrastructure Changes Required

1. OAuth Consent Screen Configuration

Navigate to Google Cloud Console → APIs & Services → OAuth consent screen. The configuration requires:

  • App name: "JADA Sailing" (or whatever is currently set)
  • User support email: jadasailing@gmail.com (must be a real, monitored inbox)
  • Developer contact: The primary Google Cloud project owner's email
  • Scopes: Must include exactly what we're requesting:
    • https://www.googleapis.com/auth/calendar (read/write calendar events)
    • https://www.googleapis.com/auth/gmail.send (send emails for booking confirmations)
    • https://www.googleapis.com/auth/drive (if using Sheets/Docs for crew assignments)
  • Authorized domains: sailjada.com (the production domain for OAuth redirects)
  • Privacy policy URL: https://sailjada.com/privacy (must exist and be public)
  • Terms of service URL: https://sailjada.com/terms (must exist and be public)

Once configured, click "Publish App" to move from Testing to Production.

2. Verification: Google's Security Review

Google will review the app for:

  • Whether scopes match actual functionality
  • Whether the privacy/terms pages are legitimate
  • Whether the app handles user data responsibly

For a booking/crew management app accessing calendar and email, this should be straightforward. Expect 1–3 business days for review.

3. Token Refresh Infrastructure Update

The token refresh logic in reauth_jada_all.py should be updated to verify the new Production consent screen status:


# After moving to Production, verify via:
gcloud oauth2-login --scopes \
  https://www.googleapis.com/auth/calendar \
  https://www.googleapis.com/auth/gmail.send

# Store tokens in the standard location:
# ~/.config/gcloud/application_default_credentials.json
# This is picked up automatically by Google Cloud client libraries

Current token storage references `JADA_TOKEN_PATH`; post-migration, switch to Google Cloud's standard Application Default Credentials (ADC) pattern for better tooling compatibility.

Downstream Impact: Calendar Sync

Once OAuth is fixed, the calendar synchronization pipeline can be completed:

  1. Boatsetter → JADA Internal Calendar: The iCal URL is already configured in CalendarSync.gs, but the trigger function calendarDashboardSetup() has never been invoked in the Apps Script editor. Run it once, manually, in Google Apps Script → Extensions → Apps Script.
  2. JADA Internal → Sailo / GetMyBoat: Once the Google Calendar OAuth is valid, the Lambda function at /calendar-sync (deployed in API Gateway) can push to Sailo's iCal endpoint and GetMyBoat's API.
  3. Viator integration: Viator replied to the API integration request (ticket t-ad4b92d7). Read their response in jadasailing@gmail.com and update the corresponding Lambda handler.

Key Decisions

  • Why not just refresh tokens more aggressively? It's operationally expensive and fragile. Every 6 days, the system would need to re-auth, with risk of the refresh failing (network error, user revokes access, etc.).
  • Why move to Production vs. staying in Testing with a service account? A service account (non-human identity) would work, but it violates the intended data model—booking confirmations and captain reports should come from JADA's brand identity (jadasailing@gmail.com), not a robot. Production mode is the correct architectural choice.
  • Why use Application Default Credentials? ADC is Google Cloud's standard pattern. It's supported by all Google client libraries, integrates with `gcloud`, and simplifies deployment to Cloud Run / Cloud Functions / Lambda (via environment variable injection).

What's Next

  1. Configure OAuth consent screen in Cloud Console (5 minutes, UI-only)
  2. Publish app to Production (1 click, then wait for Google's review)
  3. Re-authenticate all tokens using the updated reauth_jada_all.py script against the Production consent screen
  4. Invoke calendarDashboardSet