perf: reuse Google webhook OAuth client (#23361)

## Context
A new client currently downloads signing certificates for every webhook

## Fix
reuse same OAuth client instance

## Impact
Probably small but not really risky to merge imho
This commit is contained in:
Weiko
2026-07-27 15:42:59 +02:00
committed by GitHub
parent dbbad1bffa
commit 0c57d7c108
2 changed files with 64 additions and 1 deletions
@@ -0,0 +1,62 @@
import { OAuth2Client } from 'google-auth-library';
import { GoogleMessagingNotificationHandler } from 'src/modules/connected-account-sync-webhooks/drivers/google/google-messaging-notification.handler';
const mockVerifyIdToken = jest.fn();
jest.mock('google-auth-library', () => ({
...jest.requireActual('google-auth-library'),
OAuth2Client: jest.fn().mockImplementation(() => ({
verifyIdToken: (...args: unknown[]) => mockVerifyIdToken(...args),
})),
}));
describe('GoogleMessagingNotificationHandler', () => {
it('should reuse the OAuth client while verifying every notification', async () => {
mockVerifyIdToken.mockResolvedValue({
getPayload: () => ({
email: 'pubsub@example.com',
email_verified: true,
}),
});
const handler = new GoogleMessagingNotificationHandler(
{
get: jest.fn((key: string) => {
if (key === 'MESSAGING_GMAIL_PUBSUB_VERIFICATION_EMAIL') {
return 'pubsub@example.com';
}
if (key === 'SERVER_URL') {
return 'https://example.com';
}
}),
} as never,
{
incrementCounterBy: jest.fn(),
} as never,
{} as never,
{} as never,
{} as never,
);
await handler.handle({
authorizationHeader: 'Bearer first-token',
body: {},
});
await handler.handle({
authorizationHeader: 'Bearer second-token',
body: {},
});
expect(OAuth2Client).toHaveBeenCalledTimes(1);
expect(mockVerifyIdToken).toHaveBeenNthCalledWith(1, {
idToken: 'first-token',
audience: 'https://example.com/webhooks/google/messaging',
});
expect(mockVerifyIdToken).toHaveBeenNthCalledWith(2, {
idToken: 'second-token',
audience: 'https://example.com/webhooks/google/messaging',
});
});
});
@@ -34,6 +34,7 @@ export type GoogleMessagingNotificationRequest = {
@Injectable()
export class GoogleMessagingNotificationHandler implements WebhookNotificationHandler<GoogleMessagingNotificationRequest> {
private readonly logger = new Logger(GoogleMessagingNotificationHandler.name);
private readonly oauth2Client = new OAuth2Client();
constructor(
private readonly twentyConfigService: TwentyConfigService,
@@ -125,7 +126,7 @@ export class GoogleMessagingNotificationHandler implements WebhookNotificationHa
const expectedAudience = `${this.twentyConfigService.get('SERVER_URL')}/webhooks/google/messaging`;
try {
const ticket = await new OAuth2Client().verifyIdToken({
const ticket = await this.oauth2Client.verifyIdToken({
idToken,
audience: expectedAudience,
});