From b79056b3a2ade9f9e5c43903edd76394f9c9f0e0 Mon Sep 17 00:00:00 2001 From: neo773 <62795688+neo773@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:54:53 +0530 Subject: [PATCH] refactor google refresh token service error handling (#17127) Refactors to be consistent with Microsoft service Handles scenarios like temporary error which was not handled before Moved `IsGmailNetworkError` from root orchestrator to driver level --- .../google-api-refresh-tokens.service.ts | 14 +--- .../utils/parse-google-oauth-error.util.ts | 82 +++++++++++++++++++ ...ted-account-refresh-tokens.service.spec.ts | 7 +- ...onnected-account-refresh-tokens.service.ts | 8 -- 4 files changed, 90 insertions(+), 21 deletions(-) create mode 100644 packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/utils/parse-google-oauth-error.util.ts diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service.ts index 14a8b7100d..b8792e2614 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service.ts @@ -1,6 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { GaxiosError } from 'gaxios'; import { google } from 'googleapis'; import { isDefined } from 'twenty-shared/utils'; @@ -10,6 +9,7 @@ import { ConnectedAccountRefreshAccessTokenExceptionCode, } from 'src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception'; import { type ConnectedAccountTokens } from 'src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service'; +import { parseGoogleOAuthError } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/utils/parse-google-oauth-error.util'; @Injectable() export class GoogleAPIRefreshAccessTokenService { @@ -39,17 +39,11 @@ export class GoogleAPIRefreshAccessTokenService { refreshToken, }; } catch (error) { - if ( - error instanceof GaxiosError && - error.response?.data?.error === 'invalid_grant' - ) { - throw new ConnectedAccountRefreshAccessTokenException( - 'Error refreshing Google tokens: Invalid refresh token', - ConnectedAccountRefreshAccessTokenExceptionCode.INVALID_REFRESH_TOKEN, - ); + if (error instanceof ConnectedAccountRefreshAccessTokenException) { + throw error; } - throw error; + throw parseGoogleOAuthError(error); } } } diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/utils/parse-google-oauth-error.util.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/utils/parse-google-oauth-error.util.ts new file mode 100644 index 0000000000..bef8b9a9cd --- /dev/null +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/utils/parse-google-oauth-error.util.ts @@ -0,0 +1,82 @@ +import { type GaxiosError } from 'gaxios'; + +import { + ConnectedAccountRefreshAccessTokenException, + ConnectedAccountRefreshAccessTokenExceptionCode, +} from 'src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception'; +import { isGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-network-error.util'; + +export const parseGoogleOAuthError = ( + error: unknown, +): ConnectedAccountRefreshAccessTokenException => { + if (isGmailNetworkError(error)) { + return new ConnectedAccountRefreshAccessTokenException( + `Google refresh token network error: ${error.code} - ${error.message}`, + ConnectedAccountRefreshAccessTokenExceptionCode.TEMPORARY_NETWORK_ERROR, + ); + } + + const gaxiosError = error as GaxiosError; + + const googleOAuthError = { + code: gaxiosError.response?.status, + reason: + gaxiosError.response?.data?.error || + gaxiosError.response?.data?.error_description || + 'Unknown reason', + message: + gaxiosError.response?.data?.error_description || + gaxiosError.message || + 'Unknown error', + }; + + switch (googleOAuthError.code) { + case 400: + if (googleOAuthError.reason === 'invalid_grant') { + return new ConnectedAccountRefreshAccessTokenException( + googleOAuthError.message, + ConnectedAccountRefreshAccessTokenExceptionCode.INVALID_REFRESH_TOKEN, + ); + } + + return new ConnectedAccountRefreshAccessTokenException( + googleOAuthError.message, + ConnectedAccountRefreshAccessTokenExceptionCode.INVALID_REFRESH_TOKEN, + ); + + case 401: + return new ConnectedAccountRefreshAccessTokenException( + googleOAuthError.message, + ConnectedAccountRefreshAccessTokenExceptionCode.INVALID_REFRESH_TOKEN, + ); + + case 403: + return new ConnectedAccountRefreshAccessTokenException( + googleOAuthError.message, + ConnectedAccountRefreshAccessTokenExceptionCode.INVALID_REFRESH_TOKEN, + ); + + case 429: + return new ConnectedAccountRefreshAccessTokenException( + googleOAuthError.message, + ConnectedAccountRefreshAccessTokenExceptionCode.TEMPORARY_NETWORK_ERROR, + ); + + case 500: + case 502: + case 503: + case 504: + return new ConnectedAccountRefreshAccessTokenException( + `${googleOAuthError.code} - ${googleOAuthError.message}`, + ConnectedAccountRefreshAccessTokenExceptionCode.TEMPORARY_NETWORK_ERROR, + ); + + default: + break; + } + + return new ConnectedAccountRefreshAccessTokenException( + `Google refresh token failed: ${googleOAuthError.message}`, + ConnectedAccountRefreshAccessTokenExceptionCode.INVALID_REFRESH_TOKEN, + ); +}; diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.spec.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.spec.ts index 193f895011..e7f7b66da1 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.spec.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.spec.ts @@ -275,9 +275,10 @@ describe('ConnectedAccountRefreshTokensService', () => { lastCredentialsRefreshedAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago } as ConnectedAccountWorkspaceEntity; - const networkError = new Error('Network error'); - - (networkError as any).code = 'ECONNRESET'; + const networkError = new ConnectedAccountRefreshAccessTokenException( + 'Google refresh token network error: ECONNRESET - Network error', + ConnectedAccountRefreshAccessTokenExceptionCode.TEMPORARY_NETWORK_ERROR, + ); jest .spyOn(googleAPIRefreshAccessTokenService, 'refreshTokens') diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts index 610deaf9cc..f75b08f56b 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts @@ -12,7 +12,6 @@ import { ConnectedAccountRefreshAccessTokenExceptionCode, } from 'src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception'; import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { isGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-network-error.util'; export type ConnectedAccountTokens = { accessToken: string; @@ -157,13 +156,6 @@ export class ConnectedAccountRefreshTokensService { ); } } catch (error) { - if (isGmailNetworkError(error)) { - throw new ConnectedAccountRefreshAccessTokenException( - `Error refreshing tokens for connected account ${connectedAccount.id} in workspace ${workspaceId}: ${error.code}`, - ConnectedAccountRefreshAccessTokenExceptionCode.TEMPORARY_NETWORK_ERROR, - ); - } - this.logger.log( `Error while refreshing tokens on connected account ${connectedAccount.id} in workspace ${workspaceId}`, error,