trigger google reconnect popover

This commit is contained in:
Arjun 2026-05-28 15:59:09 +05:30
parent 143c4f83ec
commit a70e7d1b02

View file

@ -535,21 +535,29 @@ export async function disconnectProvider(provider: string): Promise<{ success: b
/**
* Startup migration for Google scope changes. When a connected Google grant was
* issued before a scope was added (e.g. old installs on gmail.readonly that
* never received gmail.modify), disconnect it so the renderer re-prompts the
* user through the normal connect flow and they re-grant with the current
* scopes. The currently-requested scopes in the provider config are the source
* of truth: a grant missing any of them is treated as stale.
* never received gmail.modify), invalidate it so the user is prompted to
* reconnect and re-grant with the current scopes. The currently-requested
* scopes in the provider config are the source of truth: a grant missing any
* of them is treated as stale.
*
* We revoke + clear the stale token but DELIBERATELY keep the provider entry
* with an `error` set rather than calling disconnectProvider (which deletes the
* whole entry). The renderer's reconnect prompts the sidebar "Reconnect your
* accounts" alert and the connectors "Reconnect" row key off this `error`
* field, not off the connected flag. A fully deleted entry has no error and is
* indistinguishable from "never connected", so no prompt would ever appear.
*
* Tokens with no recorded scopes (very old installs that never persisted them)
* are also treated as stale. Safe to call on every startup it's a no-op once
* the grant covers all current scopes.
* the grant covers all current scopes, and once invalidated the early return on
* the missing token keeps it from re-running until the user reconnects.
*/
export async function disconnectGoogleIfScopesStale(): Promise<void> {
try {
const oauthRepo = getOAuthRepo();
const connection = await oauthRepo.read('google');
// Not connected — nothing to migrate.
// Not connected (or already invalidated) — nothing to migrate.
if (!connection.tokens) {
return;
}
@ -568,9 +576,32 @@ export async function disconnectGoogleIfScopesStale(): Promise<void> {
console.log(
`[OAuth] Google grant is missing current scopes [${missingScopes.join(', ')}]; ` +
'disconnecting so the user can reconnect with the new scopes.'
'invalidating it so the user is prompted to reconnect with the new scopes.'
);
await disconnectProvider('google');
// Best-effort revoke at Google for rowboat-mode grants (mirrors disconnectProvider).
if (connection.mode === 'rowboat' && connection.tokens.access_token) {
try {
const revokeUrl = `https://oauth2.googleapis.com/revoke?token=${encodeURIComponent(connection.tokens.access_token)}`;
const res = await fetch(revokeUrl, { method: 'POST', signal: AbortSignal.timeout(5000) });
if (!res.ok) {
console.warn(`[OAuth] Google revoke returned ${res.status}; continuing with local invalidation`);
}
} catch (error) {
console.warn('[OAuth] Google revoke failed; continuing with local invalidation:', error);
}
}
// Drop the stale token but keep the entry with an error so the reconnect
// prompt fires (see the note above).
await oauthRepo.upsert('google', {
tokens: null,
error: 'Google permissions changed. Please reconnect to continue.',
});
// Nudge any already-open window to re-read state. The renderer's initial
// mount also re-reads, so the prompt shows even if no window is up yet.
emitOAuthEvent({ provider: 'google', success: false });
} catch (error) {
console.error('[OAuth] Google scope migration check failed:', error);
}