Reuse OAuth access tokens (#15089)

This commit is contained in:
neo773
2025-10-21 15:08:49 +05:30
committed by GitHub
parent 793dc3d6fc
commit c66261a38b
49 changed files with 632 additions and 421 deletions
@@ -1,7 +1,5 @@
import { Injectable } from '@nestjs/common';
import { google } from 'googleapis';
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
@@ -15,11 +13,12 @@ export class GoogleEmailAliasManagerService {
connectedAccount: ConnectedAccountWorkspaceEntity,
) {
const oAuth2Client =
await this.oAuth2ClientManagerService.getOAuth2Client(connectedAccount);
await this.oAuth2ClientManagerService.getGoogleOAuth2Client(
connectedAccount,
);
const people = google.people({
const people = oAuth2Client.people({
version: 'v1',
auth: oAuth2Client,
});
const emailsResponse = await people.people.get({
@@ -2,25 +2,27 @@ import { Injectable } from '@nestjs/common';
import { isNonEmptyString } from '@sniptt/guards';
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import {
MessageImportDriverException,
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import { MicrosoftClientProvider } from 'src/modules/messaging/message-import-manager/drivers/microsoft/providers/microsoft-client.provider';
import { isAccessTokenRefreshingError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-access-token-refreshing-error.utils';
@Injectable()
export class MicrosoftEmailAliasManagerService {
constructor(
private readonly microsoftClientProvider: MicrosoftClientProvider,
private readonly oAuth2ClientManagerService: OAuth2ClientManagerService,
) {}
public async getHandleAliases(
connectedAccount: ConnectedAccountWorkspaceEntity,
) {
const microsoftClient =
await this.microsoftClientProvider.getMicrosoftClient(connectedAccount);
await this.oAuth2ClientManagerService.getMicrosoftOAuth2Client(
connectedAccount,
);
const response = await microsoftClient
.api('/me?$select=proxyAddresses')
@@ -4,7 +4,6 @@ import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/em
import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service';
import { EmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/services/email-alias-manager.service';
import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module';
import { MicrosoftClientProvider } from 'src/modules/messaging/message-import-manager/drivers/microsoft/providers/microsoft-client.provider';
@Module({
imports: [OAuth2ClientManagerModule],
@@ -12,7 +11,6 @@ import { MicrosoftClientProvider } from 'src/modules/messaging/message-import-ma
EmailAliasManagerService,
GoogleEmailAliasManagerService,
MicrosoftEmailAliasManagerService,
MicrosoftClientProvider,
],
exports: [EmailAliasManagerService],
})
@@ -1,14 +1,14 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { type Repository } from 'typeorm';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import { type Repository } from 'typeorm';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/google-email-alias-manager.service';
import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service';
import { microsoftGraphMeResponseWithProxyAddresses } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/mocks/microsoft-api-examples';
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { MicrosoftClientProvider } from 'src/modules/messaging/message-import-manager/drivers/microsoft/providers/microsoft-client.provider';
import { EmailAliasManagerService } from './email-alias-manager.service';
@@ -42,9 +42,9 @@ describe('Email Alias Manager Service', () => {
},
MicrosoftEmailAliasManagerService,
{
provide: MicrosoftClientProvider,
provide: OAuth2ClientManagerService,
useValue: {
getMicrosoftClient: jest.fn().mockResolvedValue({
getMicrosoftOAuth2Client: jest.fn().mockResolvedValue({
api: jest.fn().mockReturnValue({
get: jest
.fn()
@@ -1,7 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { type OAuth2Client } from 'google-auth-library';
import { google } from 'googleapis';
import { google, GoogleApis } from 'googleapis';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@@ -12,7 +11,7 @@ export class GoogleOAuth2ClientManagerService {
private readonly logger: Logger,
) {}
public async getOAuth2Client(refreshToken: string): Promise<OAuth2Client> {
public async getOAuth2Client(refreshToken: string): Promise<GoogleApis> {
const gmailClientId = this.twentyConfigService.get('AUTH_GOOGLE_CLIENT_ID');
const gmailClientSecret = this.twentyConfigService.get(
'AUTH_GOOGLE_CLIENT_SECRET',
@@ -28,7 +27,9 @@ export class GoogleOAuth2ClientManagerService {
refresh_token: refreshToken,
});
return oAuth2Client;
google.options({ auth: oAuth2Client });
return google;
} catch (error) {
this.logger.error(
`Error in ${GoogleOAuth2ClientManagerService.name}`,
@@ -0,0 +1,11 @@
import type { AuthenticationProvider } from '@microsoft/microsoft-graph-client';
export class MicrosoftOAuth2ClientAuthProvider
implements AuthenticationProvider
{
constructor(private readonly accessToken: string) {}
public async getAccessToken(): Promise<string> {
return this.accessToken;
}
}
@@ -1,78 +1,18 @@
import { Injectable, Logger } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import {
type AuthProvider,
type AuthProviderCallback,
Client,
} from '@microsoft/microsoft-graph-client';
import { Client } from '@microsoft/microsoft-graph-client';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { ConnectedAccountRefreshAccessTokenExceptionCode } from 'src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception';
import { MicrosoftOAuth2ClientAuthProvider } from 'src/modules/connected-account/oauth2-client-manager/drivers/microsoft/microsoft-oauth2-client-auth-provider';
@Injectable()
export class MicrosoftOAuth2ClientManagerService {
private readonly logger = new Logger(
MicrosoftOAuth2ClientManagerService.name,
);
constructor(private readonly twentyConfigService: TwentyConfigService) {}
public async getOAuth2Client(accessToken: string): Promise<Client> {
const authProvider = new MicrosoftOAuth2ClientAuthProvider(accessToken);
public async getOAuth2Client(refreshToken: string): Promise<Client> {
const authProvider: AuthProvider = async (
callback: AuthProviderCallback,
) => {
try {
const urlData = new URLSearchParams();
urlData.append(
'client_id',
this.twentyConfigService.get('AUTH_MICROSOFT_CLIENT_ID'),
);
urlData.append('scope', 'https://graph.microsoft.com/.default');
urlData.append('refresh_token', refreshToken);
urlData.append(
'client_secret',
this.twentyConfigService.get('AUTH_MICROSOFT_CLIENT_SECRET'),
);
urlData.append('grant_type', 'refresh_token');
const res = await fetch(
`https://login.microsoftonline.com/common/oauth2/v2.0/token`,
{
method: 'POST',
body: urlData,
},
);
const data = await res.json();
if (!res.ok) {
if (data) {
const accessTokenSliced = data?.access_token?.slice(0, 10);
const refreshTokenSliced = data?.refresh_token?.slice(0, 10);
delete data.access_token;
delete data.refresh_token;
this.logger.error(data);
this.logger.error(`accessTokenSliced: ${accessTokenSliced}`);
this.logger.error(`refreshTokenSliced: ${refreshTokenSliced}`);
}
this.logger.error(res);
throw new Error(
`${MicrosoftOAuth2ClientManagerService.name} error: ${ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_ACCESS_TOKEN_FAILED}`,
);
}
callback(null, data.access_token);
} catch (error) {
callback(error, null);
}
};
const client = Client.init({
const client = Client.initWithMiddleware({
defaultVersion: 'v1.0',
debugLogging: false,
authProvider: authProvider,
authProvider,
});
return client;
@@ -1,34 +1,38 @@
import { Injectable } from '@nestjs/common';
import { type OAuth2Client } from 'google-auth-library';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import { type Client } from '@microsoft/microsoft-graph-client';
import { GoogleApis } from 'googleapis';
import { GoogleOAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/drivers/google/google-oauth2-client-manager.service';
import { MicrosoftOAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/drivers/microsoft/microsoft-oauth2-client-manager.service';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
@Injectable()
export class OAuth2ClientManagerService {
constructor(
private readonly googleOAuth2ClientManagerService: GoogleOAuth2ClientManagerService,
private readonly microsoftOAuth2ClientManagerService: MicrosoftOAuth2ClientManagerService,
) {}
public async getOAuth2Client(
public async getGoogleOAuth2Client(
connectedAccount: Pick<
ConnectedAccountWorkspaceEntity,
'provider' | 'refreshToken'
>,
): Promise<OAuth2Client> {
const { refreshToken } = connectedAccount;
): Promise<GoogleApis> {
return this.googleOAuth2ClientManagerService.getOAuth2Client(
connectedAccount.refreshToken,
);
}
switch (connectedAccount.provider) {
case ConnectedAccountProvider.GOOGLE:
return this.googleOAuth2ClientManagerService.getOAuth2Client(
refreshToken,
);
default:
throw new Error(
`OAuth2 client manager for provider ${connectedAccount.provider} is not implemented`,
);
}
public async getMicrosoftOAuth2Client(
connectedAccount: Pick<
ConnectedAccountWorkspaceEntity,
'provider' | 'accessToken'
>,
): Promise<Client> {
return this.microsoftOAuth2ClientManagerService.getOAuth2Client(
connectedAccount.accessToken,
);
}
}
@@ -1,14 +1,11 @@
import { Module } from '@nestjs/common';
import { GoogleAPIRefreshAccessTokenModule } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/google-api-refresh-access-token.module';
import { JwtModule } from 'src/engine/core-modules/jwt/jwt.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: [
GoogleAPIRefreshAccessTokenModule,
MicrosoftAPIRefreshAccessTokenModule,
],
imports: [JwtModule, MicrosoftAPIRefreshAccessTokenModule],
providers: [ConnectedAccountRefreshTokensService],
exports: [ConnectedAccountRefreshTokensService],
})
@@ -1,11 +0,0 @@
import { Module } from '@nestjs/common';
import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-access-token.service';
import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module';
@Module({
imports: [MessagingCommonModule],
providers: [GoogleAPIRefreshAccessTokenService],
exports: [GoogleAPIRefreshAccessTokenService],
})
export class GoogleAPIRefreshAccessTokenModule {}
@@ -1,51 +0,0 @@
import { Injectable } from '@nestjs/common';
import axios from 'axios';
import { z } from 'zod';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { type ConnectedAccountTokens } from 'src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service';
export type GoogleTokens = {
accessToken: string;
};
interface GoogleRefreshTokenResponse {
access_token: string;
id_token?: string;
token_type?: string;
expires_in?: number;
scope?: string;
}
@Injectable()
export class GoogleAPIRefreshAccessTokenService {
constructor(private readonly twentyConfigService: TwentyConfigService) {}
async refreshAccessToken(
refreshToken: string,
): Promise<ConnectedAccountTokens> {
const response = await axios.post<GoogleRefreshTokenResponse>(
'https://oauth2.googleapis.com/token',
{
client_id: this.twentyConfigService.get('AUTH_GOOGLE_CLIENT_ID'),
client_secret: this.twentyConfigService.get(
'AUTH_GOOGLE_CLIENT_SECRET',
),
refresh_token: refreshToken,
grant_type: 'refresh_token',
},
{
headers: {
'Content-Type': 'application/json',
},
},
);
z.string().parse(response.data.access_token);
return {
accessToken: response.data.access_token,
refreshToken,
};
}
}
@@ -1,10 +1,11 @@
import { Module } from '@nestjs/common';
import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module';
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
import { MicrosoftAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/services/microsoft-api-refresh-tokens.service';
@Module({
imports: [TwentyConfigModule],
imports: [TwentyConfigModule, JwtModule],
providers: [MicrosoftAPIRefreshAccessTokenService],
exports: [MicrosoftAPIRefreshAccessTokenService],
})
@@ -0,0 +1,25 @@
import { isNonEmptyString } from '@sniptt/guards';
import jwt from 'jsonwebtoken';
export const isAccessTokenExpiredOrInvalid = (
token: string,
expirationBufferInSeconds = 5 * 60,
): boolean => {
if (!isNonEmptyString(token)) {
return true;
}
try {
const payload = jwt.decode(token) as { exp?: number } | null;
if (!payload || typeof payload.exp !== 'number') {
return true;
}
const currentTime = Math.floor(Date.now() / 1000);
return payload.exp < currentTime + expirationBufferInSeconds;
} catch {
return true;
}
};
@@ -0,0 +1,171 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
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 {
ConnectedAccountRefreshAccessTokenException,
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 { ConnectedAccountRefreshTokensService } from './connected-account-refresh-tokens.service';
jest.mock(
'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/utils/is-access-token-expired-or-invalid.util',
);
describe('ConnectedAccountRefreshTokensService', () => {
let service: ConnectedAccountRefreshTokensService;
let microsoftAPIRefreshAccessTokenService: MicrosoftAPIRefreshAccessTokenService;
let twentyORMManager: TwentyORMManager;
const mockWorkspaceId = 'workspace-123';
const mockConnectedAccountId = 'account-456';
const mockAccessToken = 'valid-access-token';
const mockRefreshToken = 'valid-refresh-token';
const mockNewAccessToken = 'new-access-token';
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ConnectedAccountRefreshTokensService,
{
provide: MicrosoftAPIRefreshAccessTokenService,
useValue: {
refreshTokens: jest.fn(),
},
},
{
provide: TwentyORMManager,
useValue: {
getRepository: jest.fn(),
},
},
],
}).compile();
service = module.get<ConnectedAccountRefreshTokensService>(
ConnectedAccountRefreshTokensService,
);
microsoftAPIRefreshAccessTokenService =
module.get<MicrosoftAPIRefreshAccessTokenService>(
MicrosoftAPIRefreshAccessTokenService,
);
twentyORMManager = module.get<TwentyORMManager>(TwentyORMManager);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('refreshAndSaveTokens', () => {
it('should reuse valid access token without refreshing', async () => {
const connectedAccount = {
id: mockConnectedAccountId,
provider: ConnectedAccountProvider.MICROSOFT,
accessToken: mockAccessToken,
refreshToken: mockRefreshToken,
} as ConnectedAccountWorkspaceEntity;
(isAccessTokenExpiredOrInvalid as jest.Mock).mockReturnValue(false);
const result = await service.refreshAndSaveTokens(
connectedAccount,
mockWorkspaceId,
);
expect(result).toEqual({
accessToken: mockAccessToken,
refreshToken: mockRefreshToken,
});
expect(
microsoftAPIRefreshAccessTokenService.refreshTokens,
).not.toHaveBeenCalled();
expect(twentyORMManager.getRepository).not.toHaveBeenCalled();
});
it('should refresh and save new Microsoft token when expired', async () => {
const connectedAccount = {
id: mockConnectedAccountId,
provider: ConnectedAccountProvider.MICROSOFT,
accessToken: mockAccessToken,
refreshToken: mockRefreshToken,
} as ConnectedAccountWorkspaceEntity;
const mockRepository = { update: jest.fn() };
const newTokens = {
accessToken: mockNewAccessToken,
refreshToken: mockRefreshToken,
};
(isAccessTokenExpiredOrInvalid as jest.Mock).mockReturnValue(true);
jest
.spyOn(microsoftAPIRefreshAccessTokenService, 'refreshTokens')
.mockResolvedValue(newTokens);
jest
.spyOn(twentyORMManager, 'getRepository')
.mockResolvedValue(mockRepository as any);
const result = await service.refreshAndSaveTokens(
connectedAccount,
mockWorkspaceId,
);
expect(result).toEqual(newTokens);
expect(
microsoftAPIRefreshAccessTokenService.refreshTokens,
).toHaveBeenCalledWith(mockRefreshToken);
expect(mockRepository.update).toHaveBeenCalledWith(
{ id: mockConnectedAccountId },
newTokens,
);
});
it('should throw when refresh token is missing', async () => {
const connectedAccount = {
id: mockConnectedAccountId,
provider: ConnectedAccountProvider.GOOGLE,
accessToken: mockAccessToken,
refreshToken: null,
} as unknown as ConnectedAccountWorkspaceEntity;
await expect(
service.refreshAndSaveTokens(connectedAccount, mockWorkspaceId),
).rejects.toThrow(
new ConnectedAccountRefreshAccessTokenException(
`No refresh token found for connected account ${mockConnectedAccountId} in workspace ${mockWorkspaceId}`,
ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_TOKEN_NOT_FOUND,
),
);
});
it('should throw when Microsoft refresh fails with axios error', async () => {
const connectedAccount = {
id: mockConnectedAccountId,
provider: ConnectedAccountProvider.MICROSOFT,
accessToken: mockAccessToken,
refreshToken: mockRefreshToken,
} as ConnectedAccountWorkspaceEntity;
const axiosError = {
message: 'Request failed',
response: {
status: 400,
data: { error: 'invalid_grant', error_description: 'Token expired' },
},
};
(isAccessTokenExpiredOrInvalid as jest.Mock).mockReturnValue(true);
jest
.spyOn(microsoftAPIRefreshAccessTokenService, 'refreshTokens')
.mockRejectedValue(axiosError);
await expect(
service.refreshAndSaveTokens(connectedAccount, mockWorkspaceId),
).rejects.toThrow(ConnectedAccountRefreshAccessTokenException);
});
});
});
@@ -4,8 +4,8 @@ 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-access-token.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 {
ConnectedAccountRefreshAccessTokenException,
ConnectedAccountRefreshAccessTokenExceptionCode,
@@ -25,7 +25,6 @@ export class ConnectedAccountRefreshTokensService {
);
constructor(
private readonly googleAPIRefreshAccessTokenService: GoogleAPIRefreshAccessTokenService,
private readonly microsoftAPIRefreshAccessTokenService: MicrosoftAPIRefreshAccessTokenService,
private readonly twentyORMManager: TwentyORMManager,
) {}
@@ -34,7 +33,7 @@ export class ConnectedAccountRefreshTokensService {
connectedAccount: ConnectedAccountWorkspaceEntity,
workspaceId: string,
): Promise<ConnectedAccountTokens> {
const refreshToken = connectedAccount.refreshToken;
const { refreshToken, accessToken } = connectedAccount;
if (!refreshToken) {
throw new ConnectedAccountRefreshAccessTokenException(
@@ -43,6 +42,26 @@ export class ConnectedAccountRefreshTokensService {
);
}
const isAccessTokenValid = await this.checkAccessTokenValidity(
connectedAccount,
accessToken,
);
if (isAccessTokenValid) {
this.logger.debug(
`Reusing valid access token for connected account ${connectedAccount.id.slice(0, 7)} in workspace ${workspaceId.slice(0, 7)}`,
);
return {
accessToken,
refreshToken,
};
}
this.logger.log(
`Access token expired for connected account ${connectedAccount.id.slice(0, 7)} in workspace ${workspaceId.slice(0, 7)}, refreshing...`,
);
const connectedAccountTokens = await this.refreshTokens(
connectedAccount,
refreshToken,
@@ -62,6 +81,30 @@ export class ConnectedAccountRefreshTokensService {
return connectedAccountTokens;
}
async checkAccessTokenValidity(
connectedAccount: ConnectedAccountWorkspaceEntity,
accessToken: string,
): Promise<boolean> {
switch (connectedAccount.provider) {
case ConnectedAccountProvider.GOOGLE: {
// Google's OAuth2Client handles token auto-refresh internally, no need to check token validity
return true;
}
case ConnectedAccountProvider.MICROSOFT: {
const isExpired = isAccessTokenExpiredOrInvalid(accessToken);
return !isExpired;
}
case ConnectedAccountProvider.IMAP_SMTP_CALDAV:
return true;
default:
return assertUnreachable(
connectedAccount.provider,
`Provider ${connectedAccount.provider} not supported`,
);
}
}
async refreshTokens(
connectedAccount: ConnectedAccountWorkspaceEntity,
refreshToken: string,
@@ -70,9 +113,10 @@ export class ConnectedAccountRefreshTokensService {
try {
switch (connectedAccount.provider) {
case ConnectedAccountProvider.GOOGLE:
return await this.googleAPIRefreshAccessTokenService.refreshAccessToken(
return {
accessToken: '',
refreshToken,
);
};
case ConnectedAccountProvider.MICROSOFT:
return await this.microsoftAPIRefreshAccessTokenService.refreshTokens(
refreshToken,