"""Real Google OAuth 2.0 + Gmail API integration. - Authorization code flow with state verification (CSRF protection on the OAuth handshake). - Tokens stored encrypted (Fernet) via app.core.encryption. - Refresh handled transparently; never logs secrets. - Sending via Gmail API (users.messages.send) or Gmail SMTP with XOAUTH2 (no passwords ever). """ import base64 import json import logging import secrets from datetime import datetime, timedelta, timezone from email.mime.image import MIMEImage from email.utils import formatdate from urllib.parse import urlencode import google.auth.transport.requests import googleapiclient.discovery import googleapiclient.errors import requests from google.oauth2 import credentials as gcred from google_auth_oauthlib.flow import Flow from googleapiclient.discovery import build from googleapiclient.http import MediaIoBaseUpload from app.core import encryption from app.core.config import settings log = logging.getLogger("gmail") # Minimum scope required to send mail through the user's own account. GMAIL_SCOPES = [ "https://www.googleapis.com/auth/gmail.send", "https://www.googleapis.com/auth/gmail.compose", ] USERINFO_SCOPE = "openid https://www.googleapis.com/auth/userinfo.email" ALL_SCOPES = GMAIL_SCOPES + [USERINFO_SCOPE] GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth" GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token" GOOGLE_REVOKE_URL = "https://oauth2.googleapis.com/revoke" GOOGLE_USERINFO_URL = "https://openidconnect.googleapis.com/v1/userinfo" class GmailOauthError(Exception): pass # --------------------------------------------------------------------------- # OAuth client configuration resolution