diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis-service-availability.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis-service-availability.service.spec.ts index 223f9270924..af629b3a2ba 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis-service-availability.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis-service-availability.service.spec.ts @@ -211,6 +211,112 @@ describe('GoogleApisServiceAvailabilityService', () => { }); }); + it('should return messaging unavailable when Google returns generic precondition check failed error', async () => { + mockTwentyConfigService.get.mockImplementation((key) => { + if (key === 'AUTH_GOOGLE_CLIENT_ID') return 'client-id'; + if (key === 'AUTH_GOOGLE_CLIENT_SECRET') return 'client-secret'; + if (key === 'MESSAGING_PROVIDER_GMAIL_ENABLED') return true; + if (key === 'CALENDAR_PROVIDER_GOOGLE_ENABLED') return true; + + return undefined; + }); + + const preconditionCheckFailedError = { + response: { + status: 400, + data: { + error: { + code: 400, + message: 'Precondition check failed.', + errors: [ + { + message: 'Precondition check failed.', + domain: 'global', + reason: 'failedPrecondition', + }, + ], + status: 'FAILED_PRECONDITION', + }, + }, + }, + }; + + const mockGmailClient = { + users: { + getProfile: jest.fn().mockRejectedValue(preconditionCheckFailedError), + }, + }; + + const mockCalendarClient = { + events: { + list: jest.fn().mockResolvedValue({ data: {} }), + }, + }; + + (google.gmail as jest.Mock).mockReturnValue(mockGmailClient); + (google.calendar as jest.Mock).mockReturnValue(mockCalendarClient); + + const result = await service.checkServicesAvailability('access-token'); + + expect(result).toEqual({ + isMessagingAvailable: false, + isCalendarAvailable: true, + }); + }); + + it('should return Calendar unavailable when Google returns generic precondition check failed error', async () => { + mockTwentyConfigService.get.mockImplementation((key) => { + if (key === 'AUTH_GOOGLE_CLIENT_ID') return 'client-id'; + if (key === 'AUTH_GOOGLE_CLIENT_SECRET') return 'client-secret'; + if (key === 'MESSAGING_PROVIDER_GMAIL_ENABLED') return true; + if (key === 'CALENDAR_PROVIDER_GOOGLE_ENABLED') return true; + + return undefined; + }); + + const preconditionCheckFailedError = { + response: { + status: 400, + data: { + error: { + code: 400, + message: 'Precondition check failed.', + errors: [ + { + message: 'Precondition check failed.', + domain: 'global', + reason: 'failedPrecondition', + }, + ], + status: 'FAILED_PRECONDITION', + }, + }, + }, + }; + + const mockGmailClient = { + users: { + getProfile: jest.fn().mockResolvedValue({ data: {} }), + }, + }; + + const mockCalendarClient = { + events: { + list: jest.fn().mockRejectedValue(preconditionCheckFailedError), + }, + }; + + (google.gmail as jest.Mock).mockReturnValue(mockGmailClient); + (google.calendar as jest.Mock).mockReturnValue(mockCalendarClient); + + const result = await service.checkServicesAvailability('access-token'); + + expect(result).toEqual({ + isMessagingAvailable: true, + isCalendarAvailable: false, + }); + }); + it('should throw error for non-service-availability related errors', async () => { mockTwentyConfigService.get.mockImplementation((key) => { if (key === 'AUTH_GOOGLE_CLIENT_ID') return 'client-id'; diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis-service-availability.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis-service-availability.service.ts index 78f96aea8ae..35c89916f5b 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis-service-availability.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis-service-availability.service.ts @@ -125,9 +125,18 @@ export class GoogleApisServiceAvailabilityService { } const isFailedPrecondition = firstError.reason === 'failedPrecondition'; - const isServiceNotEnabled = - firstError.message?.includes('service not enabled') ?? false; - return isFailedPrecondition && isServiceNotEnabled; + const isServiceNotEnabled = + firstError.message?.toLowerCase()?.includes('service not enabled') ?? + false; + + const isPreconditionCheckFailed = + firstError.message + ?.toLowerCase() + ?.includes('precondition check failed') ?? false; + + return ( + isFailedPrecondition && (isServiceNotEnabled || isPreconditionCheckFailed) + ); } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts index dd0618a6a52..3e62a49eb93 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts @@ -118,6 +118,13 @@ export class GoogleAPIsService { input.accessToken, ); + if (!isMessagingAvailable && !isCalendarAvailable) { + throw new AuthException( + 'Unable to connect: Your Google account does not have access to Gmail or Calendar. Please contact your workspace administrator.', + AuthExceptionCode.INSUFFICIENT_SCOPES, + ); + } + const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext(