Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Injectable } from '@nestjs/common';

import { GaxiosError } from 'gaxios';
import { google } from 'googleapis';
import { isDefined } from 'twenty-shared/utils';

Expand All @@ -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 {
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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,
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
Loading