feat(scouts): pending-session Gmail OAuth — create cloud scout at finalize

Refactor _pending_scout_oauth_states from a tuple to a dict carrying
mode (reconnect|create), draft fields, and a transient encrypted token.
Add authorize-draft, session-labels, and cloud/finalize endpoints so the
scout row is created only when the flow completes — abandoned flows leave
no orphan rows. Zero-trust: the encrypted token lives only in the in-memory
session (<=15 min) until finalize persists it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Roberto
2026-06-10 18:23:52 +02:00
parent 95d4e4be75
commit f64ca11888
3 changed files with 322 additions and 40 deletions

View File

@@ -45,12 +45,15 @@ def _extract_plain_text_body(payload: dict) -> str:
return ""
def _get_gmail_service(scout):
"""Return a synchronous Google API client for low-level metadata/history calls."""
def _gmail_service_from_token(creds_info: dict):
"""Build a synchronous Gmail API client from a decrypted credentials dict.
Shared by ``_get_gmail_service`` (scout-backed) and the pending-session
OAuth flow which has a raw token but no scout row yet.
"""
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
creds_info = decrypt_token(scout.oauth_token_encrypted)
credentials = Credentials(
token=creds_info.get("token"),
refresh_token=creds_info.get("refresh_token"),
@@ -62,6 +65,12 @@ def _get_gmail_service(scout):
return build("gmail", "v1", credentials=credentials, cache_discovery=False)
def _get_gmail_service(scout):
"""Return a synchronous Google API client for low-level metadata/history calls."""
creds_info = decrypt_token(scout.oauth_token_encrypted)
return _gmail_service_from_token(creds_info)
class GmailConnector:
source_type = "gmail"