mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
fix(google-oauth): request offline access so BYOK tokens refresh
BYOK never requested access_type=offline/prompt=consent so no refresh token was issued and tokens died after ~1h; also stop handing back expired tokens and extend the connect timeout to 10m.
This commit is contained in:
parent
d08bf49d5a
commit
bfcffa7d3a
2 changed files with 19 additions and 3 deletions
|
|
@ -420,15 +420,23 @@ export async function connectProvider(provider: string, credentials?: { clientId
|
||||||
scope: scopes.join(' '),
|
scope: scopes.join(' '),
|
||||||
code_challenge: codeChallenge,
|
code_challenge: codeChallenge,
|
||||||
state,
|
state,
|
||||||
|
// Google only returns a refresh_token when offline access is requested,
|
||||||
|
// and only re-issues one when re-consent is forced. Without these, a
|
||||||
|
// BYOK token expires after ~1h with no way to refresh (it goes stale and
|
||||||
|
// every Google call — including the Picker — starts failing).
|
||||||
|
...(provider === 'google' ? { access_type: 'offline', prompt: 'consent' } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set timeout to clean up abandoned flows (2 minutes)
|
// Set timeout to clean up abandoned flows. Generous (10 min) because a
|
||||||
|
// first-time connect can involve creating/locating OAuth credentials in
|
||||||
|
// the Cloud Console mid-flow; a short window tears down the callback
|
||||||
|
// server before the user finishes consent, silently dropping the token.
|
||||||
const cleanupTimeout = setTimeout(() => {
|
const cleanupTimeout = setTimeout(() => {
|
||||||
if (activeFlow?.state === state) {
|
if (activeFlow?.state === state) {
|
||||||
console.log(`[OAuth] Cleaning up abandoned OAuth flow for ${provider} (timeout)`);
|
console.log(`[OAuth] Cleaning up abandoned OAuth flow for ${provider} (timeout)`);
|
||||||
cancelActiveFlow('timed_out');
|
cancelActiveFlow('timed_out');
|
||||||
}
|
}
|
||||||
}, 2 * 60 * 1000);
|
}, 10 * 60 * 1000);
|
||||||
|
|
||||||
activeFlow = {
|
activeFlow = {
|
||||||
provider,
|
provider,
|
||||||
|
|
|
||||||
|
|
@ -200,8 +200,16 @@ export async function getGoogleDocsConnectionStatus(): Promise<{
|
||||||
* Picker (file selection happens client-side; the app never lists Drive).
|
* Picker (file selection happens client-side; the app never lists Drive).
|
||||||
*/
|
*/
|
||||||
export async function getGoogleAccessToken(): Promise<string | null> {
|
export async function getGoogleAccessToken(): Promise<string | null> {
|
||||||
|
// getClient() refreshes an expired token when it can. If it returns a token
|
||||||
|
// that's still past expiry, the refresh failed (e.g. no refresh_token) — hand
|
||||||
|
// back null rather than a dead token, so the UI prompts a reconnect instead
|
||||||
|
// of silently passing an expired token to the Picker (which 403s on it).
|
||||||
const auth = await GoogleClientFactory.getClient();
|
const auth = await GoogleClientFactory.getClient();
|
||||||
return auth?.credentials?.access_token ?? null;
|
const token = auth?.credentials?.access_token ?? null;
|
||||||
|
if (!token) return null;
|
||||||
|
const expiry = auth?.credentials?.expiry_date;
|
||||||
|
if (typeof expiry === 'number' && expiry <= Date.now()) return null;
|
||||||
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Import a Google Doc as a local .docx and register the link. */
|
/** Import a Google Doc as a local .docx and register the link. */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue