fix: Fix invalidating gmail tokens#692
Conversation
|
@edulelis is attempting to deploy a commit to the Inbox Zero OSS Program Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughIntroduces refresh-token-aware Gmail permission checks. Actions now retrieve both accessToken and tokens.refreshToken from getGmailAndAccessTokenForEmail and pass refreshToken to handleGmailPermissionsCheck. The Gmail permission handler attempts a token refresh on invalid_token before falling back to cleanup that clears tokens and marks access expired. Changes
Sequence Diagram(s)sequenceDiagram
participant Caller as Action (check/admin)
participant Account as getGmailAndAccessTokenForEmail
participant Perms as handleGmailPermissionsCheck
participant Google as Gmail API
participant DB as Database
Caller->>Account: fetch { accessToken, tokens.refreshToken }
Account-->>Caller: credentials
Caller->>Perms: accessToken, refreshToken, emailAccountId
Perms->>Google: check permissions with accessToken
alt permissions ok
Google-->>Perms: ok
Perms-->>Caller: result
else invalid_token
alt refreshToken provided
Perms->>Google: refresh via client (force refresh)
Google-->>Perms: new accessToken
Perms->>Google: re-check permissions with new token
Google-->>Perms: result
Perms-->>Caller: result
else no refresh or refresh failed
Perms->>DB: clear access_token, refresh_token, expires_at
DB-->>Perms: updated
Perms-->>Caller: error (access expired, missingScopes)
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
| accessToken: null, | ||
| refreshToken, | ||
| // force refresh even if existing expiry suggests it's valid | ||
| expiresAt: null, |
There was a problem hiding this comment.
This needs to be set to force a token refresh. Otherwise, it will return the client with the invalid access token.
There was a problem hiding this comment.
Actionable comments posted: 0
🔭 Outside diff range comments (2)
apps/web/utils/actions/permissions.ts (2)
42-48: Preserve SafeError messages instead of always masking with a generic error.Throwing a SafeError earlier (e.g., “Gmail access expired. Please reconnect your account.”) is caught here and replaced with “Failed to check permissions”, losing actionable context for the UI.
- } catch (error) { - logger.error("Failed to check permissions", { - emailAccountId, - error, - }); - throw new SafeError("Failed to check permissions"); - } + } catch (error) { + logger.error("Failed to check permissions", { emailAccountId, error }); + if (error instanceof SafeError) throw error; + throw new SafeError("Failed to check permissions"); + }
75-79: Preserve SafeError messages in the admin action as well.Same concern as the user action: don’t mask SafeError messages that the UI can handle meaningfully.
- } catch (error) { - logger.error("Admin failed to check permissions", { email, error }); - throw new SafeError("Failed to check permissions"); - } + } catch (error) { + logger.error("Admin failed to check permissions", { email, error }); + if (error instanceof SafeError) throw error; + throw new SafeError("Failed to check permissions"); + }
🧹 Nitpick comments (4)
apps/web/utils/actions/permissions.ts (1)
36-41: Unify the returned shape to always include hasRefreshToken.Right now, the branch where hasAllPermissions is false returns only { hasAllPermissions: false }, while other branches include hasRefreshToken. This inconsistency complicates consumers.
Consider simplifying to compute hasRefreshToken once and always return both fields.
- if (!hasAllPermissions) return { hasAllPermissions: false }; - - if (!tokens.refreshToken) - return { hasRefreshToken: false, hasAllPermissions }; - - return { hasRefreshToken: true, hasAllPermissions }; + const hasRefreshToken = Boolean(tokens.refreshToken); + return { hasAllPermissions, hasRefreshToken };apps/web/utils/gmail/permissions.ts (3)
33-35: Avoid caching the token introspection request.Next.js’ fetch can cache under some circumstances; token status must be real-time. Add cache: "no-store".
- const response = await fetch( - `https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=${accessToken}`, - ); + const response = await fetch( + `https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=${accessToken}`, + { cache: "no-store" }, + );
105-108: Log refresh failures for better observability.Currently the refresh failure is silently swallowed here; adding a warn with context helps triage.
- } catch (_) { - // getGmailClientWithRefresh, getAccessTokenFromClient will throw if access token is invalid - // Refresh failed, fall through to cleanup - } + } catch (err) { + // getGmailClientWithRefresh, getAccessTokenFromClient will throw if access token is invalid + logger.warn("Failed to refresh Gmail token during permissions check", { + emailAccountId, + error: err, + }); + // Refresh failed, fall through to cleanup + }
116-118: Return shape consistency: include missingScopes on “Email account not found”.All other paths include missingScopes; keep the surface area consistent.
- if (!emailAccount) - return { hasAllPermissions: false, error: "Email account not found" }; + if (!emailAccount) + return { + hasAllPermissions: false, + error: "Email account not found", + missingScopes, + };
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
apps/web/utils/actions/permissions.ts(2 hunks)apps/web/utils/gmail/permissions.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use@/for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX
Files:
apps/web/utils/gmail/permissions.tsapps/web/utils/actions/permissions.ts
!{.cursor/rules/*.mdc}
📄 CodeRabbit Inference Engine (.cursor/rules/cursor-rules.mdc)
Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location
Files:
apps/web/utils/gmail/permissions.tsapps/web/utils/actions/permissions.ts
**/*.ts
📄 CodeRabbit Inference Engine (.cursor/rules/form-handling.mdc)
**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod
Files:
apps/web/utils/gmail/permissions.tsapps/web/utils/actions/permissions.ts
apps/web/utils/gmail/**/*.ts
📄 CodeRabbit Inference Engine (.cursor/rules/gmail-api.mdc)
Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')
Files:
apps/web/utils/gmail/permissions.ts
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/utils/gmail/permissions.tsapps/web/utils/actions/permissions.ts
apps/web/utils/**
📄 CodeRabbit Inference Engine (.cursor/rules/project-structure.mdc)
Create utility functions in
utils/folder for reusable logic
Files:
apps/web/utils/gmail/permissions.tsapps/web/utils/actions/permissions.ts
apps/web/utils/**/*.ts
📄 CodeRabbit Inference Engine (.cursor/rules/project-structure.mdc)
apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size
Files:
apps/web/utils/gmail/permissions.tsapps/web/utils/actions/permissions.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/utils/gmail/permissions.tsapps/web/utils/actions/permissions.ts
!pages/_document.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.
Files:
apps/web/utils/gmail/permissions.tsapps/web/utils/actions/permissions.ts
apps/web/utils/actions/**/*.ts
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-actionprovides centralized error handling
Use Zod schemas for validation on both client and server
UserevalidatePathin server actions for cache invalidation
apps/web/utils/actions/**/*.ts: Use server actions (withnext-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
UserevalidatePathin server actions to invalidate cache after mutations.
Files:
apps/web/utils/actions/permissions.ts
apps/web/utils/actions/*.ts
📄 CodeRabbit Inference Engine (.cursor/rules/server-actions.mdc)
apps/web/utils/actions/*.ts: Implement all server actions using thenext-safe-actionlibrary for type safety, input validation, context management, and error handling. Refer toapps/web/utils/actions/safe-action.tsfor client definitions (actionClient,actionClientUser,adminActionClient).
UseactionClientUserwhen only authenticated user context (userId) is needed.
UseactionClientwhen both authenticated user context and a specificemailAccountIdare needed. TheemailAccountIdmust be bound when calling the action from the client.
UseadminActionClientfor actions restricted to admin users.
Access necessary context (likeuserId,emailAccountId, etc.) provided by the safe action client via thectxobject in the.action()handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
UseSafeErrorfor expected/handled errors within actions if needed.next-safe-actionprovides centralized error handling.
Use the.metadata({ name: "actionName" })method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied viawithServerActionInstrumentationwithin the safe action clients.
If an action modifies data displayed elsewhere, userevalidatePathorrevalidateTagfromnext/cachewithin the action handler as needed.Server action files must start with
use server
Files:
apps/web/utils/actions/permissions.ts
🧬 Code Graph Analysis (2)
apps/web/utils/gmail/permissions.ts (1)
apps/web/utils/gmail/client.ts (2)
getGmailClientWithRefresh(49-103)getAccessTokenFromClient(117-122)
apps/web/utils/actions/permissions.ts (2)
apps/web/utils/account.ts (1)
getGmailAndAccessTokenForEmail(29-43)apps/web/utils/gmail/permissions.ts (1)
handleGmailPermissionsCheck(75-135)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Static Code Analysis Js
- GitHub Check: Secret Detection
- GitHub Check: Jit Security
🔇 Additional comments (6)
apps/web/utils/actions/permissions.ts (3)
28-32: Passing refreshToken into the permissions check is correct and enables refresh-on-invalid flow.Good wiring: forwarding tokens.refreshToken to handleGmailPermissionsCheck unlocks the new refresh path and aligns with the updated client utilities.
63-66: Admin path now uses getGmailAndAccessTokenForEmail — good alignment with the new token shape.This ensures admin checks also benefit from the updated access token derivation and token bundle.
68-72: Good: admin check passes refreshToken into handleGmailPermissionsCheck.This ensures the refresh-on-invalid path is available in admin-triggered checks as well.
apps/web/utils/gmail/permissions.ts (3)
2-5: Imports for refresh-capable Gmail client are correctly introduced.Bringing in getAccessTokenFromClient and getGmailClientWithRefresh is necessary for the refresh-and-retry flow.
75-82: Signature now accepts refreshToken — aligns with callers.Type is properly nullable, matching upstream availability.
88-110: Refresh-and-retry path on invalid_token looks solid.
- Forces a refresh even if expiry suggests validity.
- Re-runs the permissions check with the fresh access token.
- Falls back to cleanup if refresh isn’t possible.
Nice failover flow.
Summary by CodeRabbit
New Features
Bug Fixes