"""Decrypt and inspect the Gmail scout's stored OAuth token. Shows what scopes were granted at consent time. If gmail.readonly / gmail.modify are missing, the consent screen didn't actually grant them. Usage: python scripts/inspect_gmail_scout_token.py """ from __future__ import annotations import asyncio import sys from pathlib import Path _API_ROOT = Path(__file__).resolve().parent.parent if str(_API_ROOT) not in sys.path: sys.path.insert(0, str(_API_ROOT)) from sqlalchemy import select from app.db import async_session from app.integrations import decrypt_token from app.models import CloudScoutConfig async def main() -> None: async with async_session() as session: scouts = ( await session.execute( select(CloudScoutConfig).where(CloudScoutConfig.provider == "gmail") ) ).scalars().all() if not scouts: print("No Gmail scouts found.") return for scout in scouts: print(f"\nScout: {scout.name} (id={scout.id})") if not scout.oauth_token_encrypted: print(" (no token stored)") continue try: creds = decrypt_token(scout.oauth_token_encrypted) except Exception as exc: print(f" decrypt failed: {exc}") continue print(f" has refresh_token: {bool(creds.get('refresh_token'))}") print(f" stored scopes: {creds.get('scopes')}") print(f" token_uri: {creds.get('token_uri')}") print(f" client_id (last 8): ...{(creds.get('client_id') or '')[-8:]}") if __name__ == "__main__": asyncio.run(main())