From b978b2876016f9fc363f322c31ef9df6bfa03050 Mon Sep 17 00:00:00 2001 From: neo773 <62795688+neo773@users.noreply.github.com> Date: Mon, 27 Oct 2025 17:51:38 +0530 Subject: [PATCH] fix gmail access token (#15374) --- ...d-account-refresh-tokens-manager.module.ts | 7 +++- .../google-api-refresh-access-token.module.ts | 11 +++++ .../google-api-refresh-tokens.service.ts | 41 +++++++++++++++++++ ...ted-account-refresh-tokens.service.spec.ts | 7 ++++ ...onnected-account-refresh-tokens.service.ts | 9 ++-- 5 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/google-api-refresh-access-token.module.ts create mode 100644 packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service.ts diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/connected-account-refresh-tokens-manager.module.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/connected-account-refresh-tokens-manager.module.ts index 80b80a1eb0..4ebe6b164f 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/connected-account-refresh-tokens-manager.module.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/connected-account-refresh-tokens-manager.module.ts @@ -1,11 +1,16 @@ import { Module } from '@nestjs/common'; import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module'; +import { GoogleAPIRefreshAccessTokenModule } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/google-api-refresh-access-token.module'; import { MicrosoftAPIRefreshAccessTokenModule } from 'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/microsoft-api-refresh-access-token.module'; import { ConnectedAccountRefreshTokensService } from 'src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service'; @Module({ - imports: [JwtModule, MicrosoftAPIRefreshAccessTokenModule], + imports: [ + JwtModule, + GoogleAPIRefreshAccessTokenModule, + MicrosoftAPIRefreshAccessTokenModule, + ], providers: [ConnectedAccountRefreshTokensService], exports: [ConnectedAccountRefreshTokensService], }) diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/google-api-refresh-access-token.module.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/google-api-refresh-access-token.module.ts new file mode 100644 index 0000000000..1743eae296 --- /dev/null +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/google-api-refresh-access-token.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; + +import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module'; +import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service'; + +@Module({ + imports: [TwentyConfigModule], + providers: [GoogleAPIRefreshAccessTokenService], + exports: [GoogleAPIRefreshAccessTokenService], +}) +export class GoogleAPIRefreshAccessTokenModule {} 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 new file mode 100644 index 0000000000..59101ad2fc --- /dev/null +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@nestjs/common'; + +import { isDefined } from 'class-validator'; +import { google } from 'googleapis'; + +import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; +import { + ConnectedAccountRefreshAccessTokenException, + 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'; + +@Injectable() +export class GoogleAPIRefreshAccessTokenService { + constructor(private readonly twentyConfigService: TwentyConfigService) {} + + async refreshTokens(refreshToken: string): Promise { + const oAuth2Client = new google.auth.OAuth2( + this.twentyConfigService.get('AUTH_GOOGLE_CLIENT_ID'), + this.twentyConfigService.get('AUTH_GOOGLE_CLIENT_SECRET'), + ); + + oAuth2Client.setCredentials({ + refresh_token: refreshToken, + }); + + const { token } = await oAuth2Client.getAccessToken(); + + if (!isDefined(token)) { + throw new ConnectedAccountRefreshAccessTokenException( + 'Failed to refresh google access token', + ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_ACCESS_TOKEN_FAILED, + ); + } + + return { + accessToken: token as string, + refreshToken, + }; + } +} 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 150bbf244f..afb5b0e561 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 @@ -3,6 +3,7 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service'; import { MicrosoftAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/services/microsoft-api-refresh-tokens.service'; import { isAccessTokenExpiredOrInvalid } from 'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/utils/is-access-token-expired-or-invalid.util'; import { @@ -32,6 +33,12 @@ describe('ConnectedAccountRefreshTokensService', () => { const module: TestingModule = await Test.createTestingModule({ providers: [ ConnectedAccountRefreshTokensService, + { + provide: GoogleAPIRefreshAccessTokenService, + useValue: { + refreshTokens: jest.fn(), + }, + }, { provide: MicrosoftAPIRefreshAccessTokenService, useValue: { 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 652a91e065..f89cf3a417 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 @@ -4,6 +4,7 @@ import { ConnectedAccountProvider } from 'twenty-shared/types'; import { assertUnreachable } from 'twenty-shared/utils'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service'; import { MicrosoftAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/services/microsoft-api-refresh-tokens.service'; import { isAccessTokenExpiredOrInvalid } from 'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/utils/is-access-token-expired-or-invalid.util'; import { @@ -25,6 +26,7 @@ export class ConnectedAccountRefreshTokensService { ); constructor( + private readonly googleAPIRefreshAccessTokenService: GoogleAPIRefreshAccessTokenService, private readonly microsoftAPIRefreshAccessTokenService: MicrosoftAPIRefreshAccessTokenService, private readonly twentyORMManager: TwentyORMManager, ) {} @@ -87,7 +89,7 @@ export class ConnectedAccountRefreshTokensService { ): Promise { switch (connectedAccount.provider) { case ConnectedAccountProvider.GOOGLE: { - // Google's OAuth2Client handles token auto-refresh internally, no need to check token validity + // Google's access tokens are opaque and needs network calls to check if they are valid we default to true for now return true; } case ConnectedAccountProvider.MICROSOFT: { @@ -113,10 +115,9 @@ export class ConnectedAccountRefreshTokensService { try { switch (connectedAccount.provider) { case ConnectedAccountProvider.GOOGLE: - return { - accessToken: '', + return await this.googleAPIRefreshAccessTokenService.refreshTokens( refreshToken, - }; + ); case ConnectedAccountProvider.MICROSOFT: return await this.microsoftAPIRefreshAccessTokenService.refreshTokens( refreshToken,