feat: expand Gmail OAuth scopes, unify Google callback URL, store Gmail creds on login

- Add gmail.readonly to GMAIL_SCOPES (gmail_service) and GMAIL_API_SCOPES (providers)
  so users().getProfile() no longer returns 403 insufficientPermissions
- Consolidate Gmail scope list: GMAIL_SCOPES in gmail_service.py is the single
  source of truth; auth.py and providers.py now import and spread it
- Add include_granted_scopes=true to both Gmail and login authorize URLs so
  scope additions take effect for previously-connected users
- Add state=gmail_connect to the Gmail authorize URL; the shared /auth/callback
  page routes to gmailApi.saveCallback() when this state is present, otherwise
  falls through to the normal login flow
- Google Sign-In authorize URL now requests all six scopes (openid, email,
  profile + 3 Gmail scopes) with access_type=offline, prompt=consent, and
  include_granted_scopes=true
- POST /auth/google now stores Gmail credentials automatically after sign-in
  (non-fatal: login succeeds even if credential storage fails)
- Settings Connect Gmail button now uses /auth/callback instead of
  /auth/gmail-callback - only ONE redirect URI needed in Google Cloud Console
- URL-encode scope parameter in both authorize URL builders
- Update auth_service._register_google scope to include all Gmail scopes
- Update CHANGELOG.md and docs/TODO.md

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Agent-Logs-Url: https://github.com/christianlouis/pop_puller_to_gmail/sessions/d6e2de8b-088d-46f3-8127-137245c3ecdd
This commit is contained in:
copilot-swe-agent[bot]
2026-03-25 19:53:46 +00:00
parent 372632ce6f
commit b46fabd8cd
8 changed files with 145 additions and 21 deletions
+8 -1
View File
@@ -25,12 +25,15 @@ class OAuthService:
def _register_google(self):
"""Register Google OAuth2 provider"""
if settings.GOOGLE_CLIENT_ID and settings.GOOGLE_CLIENT_SECRET:
from app.services.gmail_service import GMAIL_SCOPES
scope = " ".join(["openid", "email", "profile", *GMAIL_SCOPES])
self.oauth.register(
name="google",
client_id=settings.GOOGLE_CLIENT_ID,
client_secret=settings.GOOGLE_CLIENT_SECRET,
server_metadata_url="https://accounts.google.com/.well-known/openid-configuration",
client_kwargs={"scope": "openid email profile"},
client_kwargs={"scope": scope},
)
async def get_google_user_info(
@@ -99,6 +102,10 @@ class OAuthService:
"google_id": user_info.get("id"),
"picture": user_info.get("picture"),
"verified_email": user_info.get("verified_email", False),
"access_token": access_token,
"refresh_token": token_data.get("refresh_token"),
"expires_in": token_data.get("expires_in"),
"scope": token_data.get("scope", ""),
}
except HTTPException:
+1
View File
@@ -21,6 +21,7 @@ logger = logging.getLogger(__name__)
GMAIL_SCOPES = [
"https://www.googleapis.com/auth/gmail.insert",
"https://www.googleapis.com/auth/gmail.labels",
"https://www.googleapis.com/auth/gmail.readonly",
]