From b8219c273d14d0586bf0fe5d8f65932a1dc4258f Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Thu, 5 Dec 2024 21:51:48 -0500 Subject: [PATCH] feat: Reduce bundle size via better imports + install gcal + gvideo by default for google signups (#17810) * wip * use light imports * fix * perf: Reduce bundle size via better imports with `googleapis` (#17811) * fix test * refactor * update vite.config.js * fix * fix unit test failing --- .env.example | 2 +- .../src/ee/calendars/services/gcal.service.ts | 8 +- apps/api/v2/src/ee/gcal/gcal.controller.ts | 3 - .../src/modules/apps/services/gcal.service.ts | 4 +- apps/web/package.json | 5 +- .../pages/api/teams/googleworkspace/add.ts | 4 +- .../api/teams/googleworkspace/callback.ts | 4 +- .../_utils/oauth/updateProfilePhotoGoogle.ts | 17 +++ packages/app-store/googlecalendar/api/add.ts | 9 +- .../app-store/googlecalendar/api/callback.ts | 69 ++++++----- .../lib/CalendarService.test.ts | 20 ++- .../googlecalendar/lib/CalendarService.ts | 116 +++++++++++++++--- .../lib/__mocks__/googleapis.ts | 73 +++++------ .../app-store/googlecalendar/lib/constants.ts | 7 -- .../app-store/googlecalendar/lib/utils.ts | 29 ----- .../app-store/googlecalendar/package.json | 2 +- packages/app-store/googlevideo/package.json | 3 +- .../features/auth/lib/next-auth-options.ts | 77 +++++++++++- packages/lib/constants.ts | 8 ++ packages/lib/google.ts | 45 ------- packages/lib/server/repository/credential.ts | 9 ++ packages/lib/server/repository/google.ts | 48 -------- .../lib/server/repository/selectedCalendar.ts | 9 +- packages/platform/libraries/vite.config.js | 4 +- .../googleWorkspace.handler.ts | 10 +- packages/types/Calendar.d.ts | 2 +- yarn.lock | 75 ++++++++--- 27 files changed, 383 insertions(+), 279 deletions(-) create mode 100644 packages/app-store/_utils/oauth/updateProfilePhotoGoogle.ts delete mode 100644 packages/app-store/googlecalendar/lib/constants.ts delete mode 100644 packages/app-store/googlecalendar/lib/utils.ts delete mode 100644 packages/lib/google.ts delete mode 100644 packages/lib/server/repository/google.ts diff --git a/.env.example b/.env.example index 0d6caf64bd..1a9addf540 100644 --- a/.env.example +++ b/.env.example @@ -119,7 +119,7 @@ NEXT_PUBLIC_FRESHCHAT_HOST= # Google OAuth credentials # To enable Login with Google you need to: -# 1. Set `GOOGLE_API_CREDENTIALS` above +# 1. Set `GOOGLE_API_CREDENTIALS` below # 2. Set `GOOGLE_LOGIN_ENABLED` to `true` # When self-hosting please ensure you configure the Google integration as an Internal app so no one else can login to your instance # @see https://support.google.com/cloud/answer/6158849#public-and-internal&zippy=%2Cpublic-and-internal-applications diff --git a/apps/api/v2/src/ee/calendars/services/gcal.service.ts b/apps/api/v2/src/ee/calendars/services/gcal.service.ts index c7c282f028..8ddc8555a6 100644 --- a/apps/api/v2/src/ee/calendars/services/gcal.service.ts +++ b/apps/api/v2/src/ee/calendars/services/gcal.service.ts @@ -10,7 +10,8 @@ import { Injectable } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { Prisma } from "@prisma/client"; import { Request } from "express"; -import { google } from "googleapis"; +import { OAuth2Client } from "googleapis-common"; +import { calendar_v3 } from "@googleapis/calendar"; import { z } from "zod"; import { SUCCESS_STATUS, GOOGLE_CALENDAR_TYPE } from "@calcom/platform-constants"; @@ -78,7 +79,7 @@ export class GoogleCalendarService implements OAuthCalendarApp { const { client_id, client_secret } = this.gcalResponseSchema.parse(app.keys); - const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirectUri); + const oAuth2Client = new OAuth2Client(client_id, client_secret, redirectUri); return oAuth2Client; } @@ -134,8 +135,7 @@ export class GoogleCalendarService implements OAuthCalendarApp { oAuth2Client.setCredentials(key); - const calendar = google.calendar({ - version: "v3", + const calendar = new calendar_v3.Calendar({ auth: oAuth2Client, }); diff --git a/apps/api/v2/src/ee/gcal/gcal.controller.ts b/apps/api/v2/src/ee/gcal/gcal.controller.ts index 39398eace4..9978fb2de7 100644 --- a/apps/api/v2/src/ee/gcal/gcal.controller.ts +++ b/apps/api/v2/src/ee/gcal/gcal.controller.ts @@ -27,10 +27,7 @@ import { } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger"; -import { Prisma } from "@prisma/client"; import { Request } from "express"; -import { google } from "googleapis"; -import { z } from "zod"; import { APPS_READ, GOOGLE_CALENDAR_TYPE, SUCCESS_STATUS } from "@calcom/platform-constants"; diff --git a/apps/api/v2/src/modules/apps/services/gcal.service.ts b/apps/api/v2/src/modules/apps/services/gcal.service.ts index fa835f8ea6..73a7657d3d 100644 --- a/apps/api/v2/src/modules/apps/services/gcal.service.ts +++ b/apps/api/v2/src/modules/apps/services/gcal.service.ts @@ -1,6 +1,6 @@ import { AppsRepository } from "@/modules/apps/apps.repository"; import { Injectable, Logger, NotFoundException } from "@nestjs/common"; -import { google } from "googleapis"; +import { OAuth2Client } from "googleapis-common"; import { z } from "zod"; @Injectable() @@ -21,7 +21,7 @@ export class GCalService { const { client_id, client_secret } = this.gcalResponseSchema.parse(app.keys); - const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirectUri); + const oAuth2Client = new OAuth2Client(client_id, client_secret, redirectUri); return oAuth2Client; } } diff --git a/apps/web/package.json b/apps/web/package.json index 42ef8cbedb..26b12c3df4 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -48,6 +48,9 @@ "@dub/analytics": "^0.0.15", "@formkit/auto-animate": "1.0.0-beta.5", "@glidejs/glide": "^3.5.2", + "@googleapis/admin": "^23.0.0", + "@googleapis/calendar": "^9.7.9", + "@googleapis/oauth2": "^1.0.7", "@hookform/error-message": "^2.0.0", "@hookform/resolvers": "^2.9.7", "@next-auth/prisma-adapter": "^1.0.4", @@ -85,7 +88,6 @@ "dotenv-cli": "^6.0.0", "entities": "^4.4.0", "eslint-config-next": "^13.2.1", - "googleapis": "^84.0.0", "gray-matter": "^4.0.3", "handlebars": "^4.7.7", "ical.js": "^1.4.0", @@ -180,6 +182,7 @@ "deasync": "^0.1.30", "detect-port": "^1.3.0", "env-cmd": "^10.1.0", + "google-auth-library": "^9.15.0", "module-alias": "^2.2.2", "msw": "^0.42.3", "node-html-parser": "^6.1.10", diff --git a/apps/web/pages/api/teams/googleworkspace/add.ts b/apps/web/pages/api/teams/googleworkspace/add.ts index 7d2f3d4763..fd4f979058 100644 --- a/apps/web/pages/api/teams/googleworkspace/add.ts +++ b/apps/web/pages/api/teams/googleworkspace/add.ts @@ -1,4 +1,4 @@ -import { google } from "googleapis"; +import { OAuth2Client } from "googleapis-common"; import type { NextApiRequest, NextApiResponse } from "next"; import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug"; @@ -20,7 +20,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) // use differnt callback to normal calendar connection const redirect_uri = `${WEBAPP_URL}/api/teams/googleworkspace/callback`; - const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri); + const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri); const authUrl = oAuth2Client.generateAuthUrl({ access_type: "offline", diff --git a/apps/web/pages/api/teams/googleworkspace/callback.ts b/apps/web/pages/api/teams/googleworkspace/callback.ts index 8b44c5b827..865670b0c7 100644 --- a/apps/web/pages/api/teams/googleworkspace/callback.ts +++ b/apps/web/pages/api/teams/googleworkspace/callback.ts @@ -1,4 +1,4 @@ -import { google } from "googleapis"; +import { OAuth2Client } from "googleapis-common"; import type { NextApiRequest, NextApiResponse } from "next"; import { z } from "zod"; @@ -37,7 +37,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) return res.status(400).json({ message: "Google client_secret missing." }); const redirect_uri = `${WEBAPP_URL}/api/teams/googleworkspace/callback`; - const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri); + const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri); if (!code) { throw new Error("No code provided"); diff --git a/packages/app-store/_utils/oauth/updateProfilePhotoGoogle.ts b/packages/app-store/_utils/oauth/updateProfilePhotoGoogle.ts new file mode 100644 index 0000000000..6f59810d13 --- /dev/null +++ b/packages/app-store/_utils/oauth/updateProfilePhotoGoogle.ts @@ -0,0 +1,17 @@ +import { oauth2_v2 } from "@googleapis/oauth2"; +import type { OAuth2Client } from "googleapis-common"; + +import logger from "@calcom/lib/logger"; +import { UserRepository } from "@calcom/lib/server/repository/user"; + +export async function updateProfilePhotoGoogle(oAuth2Client: OAuth2Client, userId: number) { + try { + const oauth2 = new oauth2_v2.Oauth2({ auth: oAuth2Client }); + const userDetails = await oauth2.userinfo.get(); + if (userDetails.data?.picture) { + await UserRepository.updateAvatar({ id: userId, avatarUrl: userDetails.data.picture }); + } + } catch (error) { + logger.error("Error updating avatarUrl from google calendar connect", error); + } +} diff --git a/packages/app-store/googlecalendar/api/add.ts b/packages/app-store/googlecalendar/api/add.ts index e4e841f19b..4f7edcf4ae 100644 --- a/packages/app-store/googlecalendar/api/add.ts +++ b/packages/app-store/googlecalendar/api/add.ts @@ -1,22 +1,21 @@ -import { google } from "googleapis"; +import { OAuth2Client } from "googleapis-common"; import type { NextApiRequest, NextApiResponse } from "next"; -import { WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants"; +import { GOOGLE_CALENDAR_SCOPES, SCOPE_USERINFO_PROFILE, WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState"; -import { SCOPES } from "../lib/constants"; import { getGoogleAppKeys } from "../lib/getGoogleAppKeys"; async function getHandler(req: NextApiRequest, res: NextApiResponse) { // Get token from Google Calendar API const { client_id, client_secret } = await getGoogleAppKeys(); const redirect_uri = `${WEBAPP_URL_FOR_OAUTH}/api/integrations/googlecalendar/callback`; - const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri); + const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri); const authUrl = oAuth2Client.generateAuthUrl({ access_type: "offline", - scope: SCOPES, + scope: [SCOPE_USERINFO_PROFILE, ...GOOGLE_CALENDAR_SCOPES], // A refresh token is only returned the first time the user // consents to providing access. For illustration purposes, // setting the prompt to 'consent' will force this consent diff --git a/packages/app-store/googlecalendar/api/callback.ts b/packages/app-store/googlecalendar/api/callback.ts index ef7a514c62..0c309bf8a8 100644 --- a/packages/app-store/googlecalendar/api/callback.ts +++ b/packages/app-store/googlecalendar/api/callback.ts @@ -1,19 +1,24 @@ -import { google } from "googleapis"; +import { calendar_v3 } from "@googleapis/calendar"; +import { OAuth2Client } from "googleapis-common"; import type { NextApiRequest, NextApiResponse } from "next"; +import { updateProfilePhotoGoogle } from "@calcom/app-store/_utils/oauth/updateProfilePhotoGoogle"; +import GoogleCalendarService from "@calcom/app-store/googlecalendar/lib/CalendarService"; import { renewSelectedCalendarCredentialId } from "@calcom/lib/connectedCalendar"; -import { WEBAPP_URL, WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants"; +import { + GOOGLE_CALENDAR_SCOPES, + SCOPE_USERINFO_PROFILE, + WEBAPP_URL, + WEBAPP_URL_FOR_OAUTH, +} from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; -import { getAllCalendars, updateProfilePhoto } from "@calcom/lib/google"; import { HttpError } from "@calcom/lib/http-error"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; import { CredentialRepository } from "@calcom/lib/server/repository/credential"; -import { GoogleRepository } from "@calcom/lib/server/repository/google"; import { Prisma } from "@calcom/prisma/client"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState"; -import { REQUIRED_SCOPES, SCOPE_USERINFO_PROFILE } from "../lib/constants"; import { getGoogleAppKeys } from "../lib/getGoogleAppKeys"; async function getHandler(req: NextApiRequest, res: NextApiResponse) { @@ -40,14 +45,14 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { const redirect_uri = `${WEBAPP_URL_FOR_OAUTH}/api/integrations/googlecalendar/callback`; - const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri); + const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri); if (code) { const token = await oAuth2Client.getToken(code); const key = token.tokens; const grantedScopes = token.tokens.scope?.split(" ") ?? []; // Check if we have granted all required permissions - const hasMissingRequiredScopes = REQUIRED_SCOPES.some((scope) => !grantedScopes.includes(scope)); + const hasMissingRequiredScopes = GOOGLE_CALENDAR_SCOPES.some((scope) => !grantedScopes.includes(scope)); if (hasMissingRequiredScopes) { if (!state?.fromApp) { throw new HttpError({ @@ -63,28 +68,25 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { return; } - // Set the primary calendar as the first selected calendar - oAuth2Client.setCredentials(key); - const calendar = google.calendar({ - version: "v3", + const gcalCredential = await CredentialRepository.create({ + userId: req.session.user.id, + key, + appId: "google-calendar", + type: "google_calendar", + }); + + const gCalService = new GoogleCalendarService({ + ...gcalCredential, + user: null, + }); + + const calendar = new calendar_v3.Calendar({ auth: oAuth2Client, }); - const cals = await getAllCalendars(calendar); - - const primaryCal = cals.find((cal) => cal.primary) ?? cals[0]; - - // Only attempt to update the user's profile photo if the user has granted the required scope - if (grantedScopes.includes(SCOPE_USERINFO_PROFILE)) { - await updateProfilePhoto(oAuth2Client, req.session.user.id); - } - - const gcalCredential = await GoogleRepository.createGoogleCalendarCredential({ - key, - userId: req.session.user.id, - }); + const primaryCal = await gCalService.getPrimaryCalendar(calendar); // If we still don't have a primary calendar skip creating the selected calendar. // It can be toggled on later. @@ -96,6 +98,11 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { return; } + // Only attempt to update the user's profile photo if the user has granted the required scope + if (grantedScopes.includes(SCOPE_USERINFO_PROFILE)) { + await updateProfilePhotoGoogle(oAuth2Client, req.session.user.id); + } + const selectedCalendarWhereUnique = { userId: req.session.user.id, externalId: primaryCal.id, @@ -105,10 +112,8 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { // Wrapping in a try/catch to reduce chance of race conditions- // also this improves performance for most of the happy-paths. try { - await GoogleRepository.upsertSelectedCalendar({ - credentialId: gcalCredential.id, + await gCalService.upsertSelectedCalendar({ externalId: selectedCalendarWhereUnique.externalId, - userId: selectedCalendarWhereUnique.userId, }); } catch (error) { let errorMessage = "something_went_wrong"; @@ -145,8 +150,9 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { return; } - const existingGoogleMeetCredential = await GoogleRepository.findGoogleMeetCredential({ + const existingGoogleMeetCredential = await CredentialRepository.findFirstByUserIdAndType({ userId: req.session.user.id, + type: "google_video", }); // If the user already has a google meet credential, there's nothing to do in here @@ -159,7 +165,12 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { } // Create a new google meet credential - await GoogleRepository.createGoogleMeetsCredential({ userId: req.session.user.id }); + await CredentialRepository.create({ + userId: req.session.user.id, + type: "google_video", + key: {}, + appId: "google-meet", + }); res.redirect( getSafeRedirectUrl(`${WEBAPP_URL}/apps/installed/conferencing?hl=google-meet`) ?? getInstalledAppPath({ variant: "conferencing", slug: "google-meet" }) diff --git a/packages/app-store/googlecalendar/lib/CalendarService.test.ts b/packages/app-store/googlecalendar/lib/CalendarService.test.ts index dab13bb5be..72d9b95a29 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.test.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.test.ts @@ -1,8 +1,8 @@ import prismock from "../../../../tests/libs/__mocks__/prisma"; import oAuthManagerMock, { defaultMockOAuthManager } from "../../tests/__mocks__/OAuthManager"; -import { googleapisMock, setCredentialsMock } from "./__mocks__/googleapis"; +import { adminMock, calendarMock, setCredentialsMock } from "./__mocks__/googleapis"; -import { expect, test, vi } from "vitest"; +import { expect, test, beforeEach, vi } from "vitest"; import "vitest-fetch-mock"; import { CalendarCache } from "@calcom/features/calendar-cache/calendar-cache"; @@ -22,7 +22,21 @@ vi.mock("./getGoogleAppKeys", () => ({ redirect_uris: ["http://localhost:3000/api/integrations/googlecalendar/callback"], }), })); -googleapisMock.google; + +vi.mock("googleapis-common", () => ({ + OAuth2Client: vi.fn().mockImplementation(() => ({ + setCredentials: setCredentialsMock, + })), +})); +vi.mock("@googleapis/admin", () => adminMock); +vi.mock("@googleapis/calendar", () => calendarMock); + +beforeEach(() => { + vi.clearAllMocks(); + setCredentialsMock.mockClear(); + calendarMock.calendar_v3.Calendar.mockClear(); + adminMock.admin_directory_v1.Admin.mockClear(); +}); const googleTestCredentialKey = { scope: "https://www.googleapis.com/auth/calendar.events", diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts index e5e328f957..cdf8531033 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { calendar_v3 } from "@googleapis/calendar"; import type { Prisma } from "@prisma/client"; -import type { calendar_v3 } from "googleapis"; -import { google } from "googleapis"; +import { OAuth2Client } from "googleapis-common"; import { RRule } from "rrule"; import { v4 as uuid } from "uuid"; @@ -18,10 +18,9 @@ import { CREDENTIAL_SYNC_SECRET_HEADER_NAME, } from "@calcom/lib/constants"; import { formatCalEvent } from "@calcom/lib/formatCalendarEvent"; -import { getAllCalendars } from "@calcom/lib/google"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; -import { GoogleRepository } from "@calcom/lib/server/repository/google"; +import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; import prisma from "@calcom/prisma"; import type { Calendar, @@ -164,8 +163,7 @@ export default class GoogleCalendarService implements Calendar { public authedCalendar = async () => { const myGoogleAuth = await this.auth.getMyGoogleAuthWithRefreshedToken(); - const calendar = google.calendar({ - version: "v3", + const calendar = new calendar_v3.Calendar({ auth: myGoogleAuth, }); return calendar; @@ -541,12 +539,12 @@ export default class GoogleCalendarService implements Calendar { // Only calendars of other integrations selected return []; } - async function getCalIds() { + const getCalIds = async () => { if (selectedCalendarIds.length !== 0) return selectedCalendarIds; - const cals = await getAllCalendars(calendar, ["id"]); + const cals = await this.getAllCalendars(calendar, ["id"]); if (!cals.length) return []; return cals.reduce((c, cal) => (cal.id ? [...c, cal.id] : c), [] as string[]); - } + }; try { const calsIds = await getCalIds(); @@ -607,7 +605,7 @@ export default class GoogleCalendarService implements Calendar { status: 200, statusText: "OK", data: { - items: await getAllCalendars(calendar), + items: await this.getAllCalendars(calendar), }, }) ); @@ -652,10 +650,8 @@ export default class GoogleCalendarService implements Calendar { }, }); const response = res.data; - await GoogleRepository.upsertSelectedCalendar({ - userId: this.credential.userId!, + await this.upsertSelectedCalendar({ externalId: calendarId, - credentialId: this.credential.id, googleChannelId: response?.id, googleChannelKind: response?.kind, googleChannelResourceId: response?.resourceId, @@ -686,10 +682,8 @@ export default class GoogleCalendarService implements Calendar { .catch((err) => { console.warn(JSON.stringify(err)); }); - await GoogleRepository.upsertSelectedCalendar({ - userId: this.credential.userId!, + await this.upsertSelectedCalendar({ externalId: calendarId, - credentialId: this.credential.id, googleChannelId: null, googleChannelKind: null, googleChannelResourceId: null, @@ -715,9 +709,97 @@ export default class GoogleCalendarService implements Calendar { const data = await this.fetchAvailability(parsedArgs); await this.setAvailabilityInCache(parsedArgs, data); } + + async createSelectedCalendar( + data: Omit + ) { + return await SelectedCalendarRepository.create({ + ...data, + integration: this.integrationName, + credentialId: this.credential.id, + }); + } + + async upsertSelectedCalendar( + data: Omit + ) { + if (!this.credential.userId) { + logger.error("upsertSelectedCalendar failed. userId is missing."); + return; + } + return await SelectedCalendarRepository.upsert({ + ...data, + integration: this.integrationName, + credentialId: this.credential.id, + userId: this.credential.userId, + }); + } + + async getAllCalendars( + calendar: calendar_v3.Calendar, + fields: string[] = ["id", "summary", "primary", "accessRole"] + ): Promise { + let allCalendars: calendar_v3.Schema$CalendarListEntry[] = []; + let pageToken: string | undefined; + + try { + do { + const response: any = await calendar.calendarList.list({ + fields: `items(${fields.join(",")}),nextPageToken`, + pageToken, + maxResults: 250, // 250 is max + }); + + allCalendars = [...allCalendars, ...(response.data.items ?? [])]; + pageToken = response.data.nextPageToken; + } while (pageToken); + + return allCalendars; + } catch (error) { + logger.error("Error fetching all Google Calendars", { error }); + throw error; + } + } + + async getPrimaryCalendar( + calendar: calendar_v3.Calendar, + fields: string[] = ["id", "summary", "primary", "accessRole"] + ): Promise { + let pageToken: string | undefined; + let firstCalendar: calendar_v3.Schema$CalendarListEntry | undefined; + + try { + do { + const response: any = await calendar.calendarList.list({ + fields: `items(${fields.join(",")}),nextPageToken`, + pageToken, + maxResults: 250, // 250 is max + }); + + const cals = response.data.items ?? []; + const primaryCal = cals.find((cal: calendar_v3.Schema$CalendarListEntry) => cal.primary); + if (primaryCal) { + return primaryCal; + } + + // Store the first calendar in case no primary is found + if (cals.length > 0 && !firstCalendar) { + firstCalendar = cals[0]; + } + + pageToken = response.data.nextPageToken; + } while (pageToken); + + // should not be reached because Google Cal always has a primary cal + return firstCalendar ?? null; + } catch (error) { + logger.error("Error in `getPrimaryCalendar`", { error }); + throw error; + } + } } -class MyGoogleAuth extends google.auth.OAuth2 { +class MyGoogleAuth extends OAuth2Client { constructor(client_id: string, client_secret: string, redirect_uri: string) { super(client_id, client_secret, redirect_uri); } diff --git a/packages/app-store/googlecalendar/lib/__mocks__/googleapis.ts b/packages/app-store/googlecalendar/lib/__mocks__/googleapis.ts index 9e0f09b670..a7b883c6b3 100644 --- a/packages/app-store/googlecalendar/lib/__mocks__/googleapis.ts +++ b/packages/app-store/googlecalendar/lib/__mocks__/googleapis.ts @@ -1,50 +1,33 @@ -import type * as googleapis from "googleapis"; -import { beforeEach, vi } from "vitest"; -import { mockDeep, mockReset } from "vitest-mock-extended"; - -vi.mock("googleapis", () => googleapisMock); - -beforeEach(() => { - mockReset(googleapisMock); -}); - -const googleapisMock = mockDeep({ - fallbackMockImplementation: (...args) => { - console.log(args); - throw new Error("Unimplemented"); - }, -}); +import { vi } from "vitest"; const setCredentialsMock = vi.fn(); -googleapisMock.google = { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - calendar: vi.fn().mockReturnValue({ - channels: { - stop: vi.fn().mockResolvedValue(undefined), - }, - events: { - watch: vi.fn().mockResolvedValue({ - data: { - kind: "api#channel", - id: "mock-channel-id", - resourceId: "mock-resource-id", - resourceUri: "mock-resource-uri", - expiration: "1111111111", - }, - }), - }, - }), - auth: { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - OAuth2: function MockGoogleOAuth2() { - return { - setCredentials: setCredentialsMock, - }; - }, + +const calendarMockImplementation = { + channels: { + stop: vi.fn().mockResolvedValue(undefined), + }, + events: { + watch: vi.fn().mockResolvedValue({ + data: { + kind: "api#channel", + id: "mock-channel-id", + resourceId: "mock-resource-id", + resourceUri: "mock-resource-uri", + expiration: "1111111111", + }, + }), }, }; -export default googleapisMock; -export { googleapisMock, setCredentialsMock }; +const calendarMock = { + calendar_v3: { + Calendar: vi.fn().mockImplementation(() => calendarMockImplementation), + }, +}; +const adminMock = { + admin_directory_v1: { + Admin: vi.fn(), + }, +}; + +export { calendarMock, adminMock, setCredentialsMock }; diff --git a/packages/app-store/googlecalendar/lib/constants.ts b/packages/app-store/googlecalendar/lib/constants.ts deleted file mode 100644 index 5c285ce803..0000000000 --- a/packages/app-store/googlecalendar/lib/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const SCOPE_USERINFO_PROFILE = "https://www.googleapis.com/auth/userinfo.profile"; -export const SCOPE_CALENDAR_READONLY = "https://www.googleapis.com/auth/calendar.readonly"; -export const SCOPE_CALENDAR_EVENT = "https://www.googleapis.com/auth/calendar.events"; - -export const REQUIRED_SCOPES = [SCOPE_CALENDAR_READONLY, SCOPE_CALENDAR_EVENT]; - -export const SCOPES = [...REQUIRED_SCOPES, SCOPE_USERINFO_PROFILE]; diff --git a/packages/app-store/googlecalendar/lib/utils.ts b/packages/app-store/googlecalendar/lib/utils.ts deleted file mode 100644 index a9436827e9..0000000000 --- a/packages/app-store/googlecalendar/lib/utils.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { calendar_v3 } from "googleapis"; - -import logger from "@calcom/lib/logger"; - -export async function getAllCalendars( - calendar: calendar_v3.Calendar, - fields: string[] = ["id", "summary", "primary", "accessRole"] -): Promise { - let allCalendars: calendar_v3.Schema$CalendarListEntry[] = []; - let pageToken: string | undefined; - - try { - do { - const response: any = await calendar.calendarList.list({ - fields: `items(${fields.join(",")}),nextPageToken`, - pageToken, - maxResults: 250, // 250 is max - }); - - allCalendars = [...allCalendars, ...(response.data.items ?? [])]; - pageToken = response.data.nextPageToken; - } while (pageToken); - - return allCalendars; - } catch (error) { - logger.error("Error fetching all Google Calendars", { error }); - throw error; - } -} diff --git a/packages/app-store/googlecalendar/package.json b/packages/app-store/googlecalendar/package.json index c8fe2c97a7..12b226fa86 100644 --- a/packages/app-store/googlecalendar/package.json +++ b/packages/app-store/googlecalendar/package.json @@ -7,7 +7,7 @@ "description": "Google Calendar is a time management and scheduling service developed by Google. Allows users to create and edit events, with options available for type and time. Available to anyone that has a Gmail account on both mobile and web versions.", "dependencies": { "@calcom/prisma": "*", - "googleapis": "^84.0.0" + "@googleapis/calendar": "^9.7.9" }, "devDependencies": { "@calcom/types": "*" diff --git a/packages/app-store/googlevideo/package.json b/packages/app-store/googlevideo/package.json index 184a1ee8bc..ddc29e0a91 100644 --- a/packages/app-store/googlevideo/package.json +++ b/packages/app-store/googlevideo/package.json @@ -6,8 +6,7 @@ "main": "./index.ts", "description": "Google Meet is Google's web-based video conferencing platform, designed to compete with major conferencing platforms.", "dependencies": { - "@calcom/prisma": "*", - "googleapis": "^84.0.0" + "@calcom/prisma": "*" }, "devDependencies": { "@calcom/types": "*" diff --git a/packages/features/auth/lib/next-auth-options.ts b/packages/features/auth/lib/next-auth-options.ts index 3c51b393c7..90cae9c711 100644 --- a/packages/features/auth/lib/next-auth-options.ts +++ b/packages/features/auth/lib/next-auth-options.ts @@ -1,5 +1,7 @@ +import { calendar_v3 } from "@googleapis/calendar"; import type { Membership, Team, UserPermissionRole } from "@prisma/client"; import { waitUntil } from "@vercel/functions"; +import { OAuth2Client } from "googleapis-common"; import type { AuthOptions, Session, User } from "next-auth"; import type { JWT } from "next-auth/jwt"; import { encode } from "next-auth/jwt"; @@ -8,6 +10,8 @@ import CredentialsProvider from "next-auth/providers/credentials"; import EmailProvider from "next-auth/providers/email"; import GoogleProvider from "next-auth/providers/google"; +import { updateProfilePhotoGoogle } from "@calcom/app-store/_utils/oauth/updateProfilePhotoGoogle"; +import GoogleCalendarService from "@calcom/app-store/googlecalendar/lib/CalendarService"; import { LicenseKeySingleton } from "@calcom/ee/common/server/LicenseKeyService"; import createUsersAndConnectToOrg from "@calcom/features/ee/dsync/lib/users/createUsersAndConnectToOrg"; import postHogClient from "@calcom/features/ee/event-tracking/lib/posthog/postHogClient"; @@ -15,7 +19,12 @@ import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/Imperso import { getOrgFullOrigin, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains"; import { clientSecretVerifier, hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; -import { HOSTED_CAL_FEATURES, IS_CALCOM } from "@calcom/lib/constants"; +import { + GOOGLE_CALENDAR_SCOPES, + GOOGLE_OAUTH_SCOPES, + HOSTED_CAL_FEATURES, + IS_CALCOM, +} from "@calcom/lib/constants"; import { ENABLE_PROFILE_SWITCHER, IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants"; import { symmetricDecrypt, symmetricEncrypt } from "@calcom/lib/crypto"; import { defaultCookies } from "@calcom/lib/default-cookies"; @@ -23,6 +32,7 @@ import { isENVDev } from "@calcom/lib/env"; import logger from "@calcom/lib/logger"; import { randomString } from "@calcom/lib/random"; import { safeStringify } from "@calcom/lib/safeStringify"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import { ProfileRepository } from "@calcom/lib/server/repository/profile"; import { UserRepository } from "@calcom/lib/server/repository/user"; import slugify from "@calcom/lib/slugify"; @@ -251,6 +261,13 @@ if (IS_GOOGLE_LOGIN_ENABLED) { clientId: GOOGLE_CLIENT_ID, clientSecret: GOOGLE_CLIENT_SECRET, allowDangerousEmailAccountLinking: true, + authorization: { + params: { + scope: [...GOOGLE_OAUTH_SCOPES, ...GOOGLE_CALENDAR_SCOPES].join(" "), + access_type: "offline", + prompt: "consent", + }, + }, }) ); } @@ -608,6 +625,63 @@ export const getOptions = ({ return await autoMergeIdentities(); } + const grantedScopes = account.scope?.split(" ") ?? []; + if ( + account.provider === "google" && + !(await CredentialRepository.findFirstByAppIdAndUserId({ + userId: user.id as number, + appId: "google-calendar", + })) && + GOOGLE_CALENDAR_SCOPES.every((scope) => grantedScopes.includes(scope)) + ) { + // Installing Google Calendar by default + const credentialkey = { + access_token: account.access_token, + refresh_token: account.refresh_token, + id_token: account.id_token, + token_type: account.token_type, + expires_at: account.expires_at, + }; + const gcalCredential = await CredentialRepository.create({ + userId: user.id as number, + key: credentialkey, + appId: "google-calendar", + type: "google_calendar", + }); + const gCalService = new GoogleCalendarService({ + ...gcalCredential, + user: null, + }); + + if ( + !(await CredentialRepository.findFirstByUserIdAndType({ + userId: user.id as number, + type: "google_video", + })) + ) { + await CredentialRepository.create({ + type: "google_video", + key: {}, + userId: user.id as number, + appId: "google-meet", + }); + } + + const oAuth2Client = new OAuth2Client(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET); + oAuth2Client.setCredentials(credentialkey); + const calendar = new calendar_v3.Calendar({ + auth: oAuth2Client, + }); + const primaryCal = await gCalService.getPrimaryCalendar(calendar); + if (primaryCal?.id) { + await gCalService.createSelectedCalendar({ + externalId: primaryCal.id, + userId: user.id as number, + }); + } + await updateProfilePhotoGoogle(oAuth2Client, user.id as number); + } + return { ...token, id: existingUser.id, @@ -632,7 +706,6 @@ export const getOptions = ({ log.debug("callbacks:session - Session callback called", safeStringify({ session, token, user })); const licenseKeyService = await LicenseKeySingleton.getInstance(); const hasValidLicense = await licenseKeyService.checkLicense(); - const profileId = token.profileId; const calendsoSession: Session = { ...session, diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index 6c154a0a56..42fa9085f2 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -189,3 +189,11 @@ export const RECORDING_DEFAULT_ICON = IS_PRODUCTION export const RECORDING_IN_PROGRESS_ICON = IS_PRODUCTION ? `${WEBAPP_URL}/stop-recording.svg` : `https://app.cal.com/stop-recording.svg`; + +export const SCOPE_USERINFO_PROFILE = "https://www.googleapis.com/auth/userinfo.profile"; +export const SCOPE_USERINFO_EMAIL = "https://www.googleapis.com/auth/userinfo.email"; +export const GOOGLE_OAUTH_SCOPES = [SCOPE_USERINFO_PROFILE, SCOPE_USERINFO_EMAIL]; +export const GOOGLE_CALENDAR_SCOPES = [ + "https://www.googleapis.com/auth/calendar.events", + "https://www.googleapis.com/auth/calendar.readonly", +]; diff --git a/packages/lib/google.ts b/packages/lib/google.ts deleted file mode 100644 index df9882b559..0000000000 --- a/packages/lib/google.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { OAuth2Client } from "google-auth-library"; -import type { calendar_v3 } from "googleapis"; -import { google } from "googleapis"; - -import logger from "@calcom/lib/logger"; - -import { UserRepository } from "./server/repository/user"; - -export async function getAllCalendars( - calendar: calendar_v3.Calendar, - fields: string[] = ["id", "summary", "primary", "accessRole"] -): Promise { - let allCalendars: calendar_v3.Schema$CalendarListEntry[] = []; - let pageToken: string | undefined; - - try { - do { - const response: any = await calendar.calendarList.list({ - fields: `items(${fields.join(",")}),nextPageToken`, - pageToken, - maxResults: 250, // 250 is max - }); - - allCalendars = [...allCalendars, ...(response.data.items ?? [])]; - pageToken = response.data.nextPageToken; - } while (pageToken); - - return allCalendars; - } catch (error) { - logger.error("Error fetching all Google Calendars", { error }); - throw error; - } -} - -export async function updateProfilePhoto(oAuth2Client: OAuth2Client, userId: number) { - try { - const oauth2 = google.oauth2({ version: "v2", auth: oAuth2Client }); - const userDetails = await oauth2.userinfo.get(); - if (userDetails.data?.picture) { - await UserRepository.updateAvatar({ id: userId, avatarUrl: userDetails.data.picture }); - } - } catch (error) { - logger.error("Error updating avatarUrl from google calendar connect", error); - } -} diff --git a/packages/lib/server/repository/credential.ts b/packages/lib/server/repository/credential.ts index e858a3fc67..a4a244a8a7 100644 --- a/packages/lib/server/repository/credential.ts +++ b/packages/lib/server/repository/credential.ts @@ -30,6 +30,15 @@ export class CredentialRepository { }); } + static async findFirstByAppIdAndUserId({ appId, userId }: { appId: string; userId: number }) { + return await prisma.credential.findFirst({ + where: { + appId, + userId, + }, + }); + } + static async findFirstByUserIdAndType({ userId, type }: { userId: number; type: string }) { return await prisma.credential.findFirst({ where: { userId, type } }); } diff --git a/packages/lib/server/repository/google.ts b/packages/lib/server/repository/google.ts deleted file mode 100644 index 571a1b5fa7..0000000000 --- a/packages/lib/server/repository/google.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Prisma } from "@prisma/client"; -import type { Credentials } from "google-auth-library"; - -import { CredentialRepository } from "./credential"; -import { SelectedCalendarRepository } from "./selectedCalendar"; - -export class GoogleRepository { - static async createGoogleCalendarCredential({ userId, key }: { userId: number; key: Credentials }) { - return await CredentialRepository.create({ - type: "google_calendar", - key, - userId, - appId: "google-calendar", - }); - } - - static async createGoogleMeetsCredential({ userId }: { userId: number }) { - return await CredentialRepository.create({ - type: "google_video", - key: {}, - userId, - appId: "google-meet", - }); - } - - static async createSelectedCalendar(data: { credentialId: number; userId: number; externalId: string }) { - return await SelectedCalendarRepository.create({ - ...data, - integration: "google_calendar", - }); - } - - static async upsertSelectedCalendar( - data: Omit - ) { - return await SelectedCalendarRepository.upsert({ - ...data, - integration: "google_calendar", - }); - } - - static async findGoogleMeetCredential({ userId }: { userId: number }) { - return await CredentialRepository.findFirstByUserIdAndType({ - userId, - type: "google_video", - }); - } -} diff --git a/packages/lib/server/repository/selectedCalendar.ts b/packages/lib/server/repository/selectedCalendar.ts index d35fd5de91..e40a4fb087 100644 --- a/packages/lib/server/repository/selectedCalendar.ts +++ b/packages/lib/server/repository/selectedCalendar.ts @@ -3,15 +3,8 @@ import type { Prisma } from "@prisma/client"; import { prisma } from "@calcom/prisma"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; -type SelectedCalendarCreateInput = { - credentialId: number; - userId: number; - externalId: string; - integration: string; -}; - export class SelectedCalendarRepository { - static async create(data: SelectedCalendarCreateInput) { + static async create(data: Prisma.SelectedCalendarUncheckedCreateInput) { return await prisma.selectedCalendar.create({ data: { ...data, diff --git a/packages/platform/libraries/vite.config.js b/packages/platform/libraries/vite.config.js index 7f6505c2f2..c021702977 100644 --- a/packages/platform/libraries/vite.config.js +++ b/packages/platform/libraries/vite.config.js @@ -54,7 +54,7 @@ export default defineConfig({ "ical.js", "ics", "tsdav", - "googleapis", + "@googleapis/calendar", "rrule", "@hubspot/api-client", "querystring", @@ -125,7 +125,7 @@ export default defineConfig({ "ical.js": "ical.js", ics: "ics", tsdav: "tsdav", - googleapis: "googleapis", + "@googleapis/calendar": "@googleapis/calendar", rrule: "rrule", "@hubspot/api-client": "@hubspot/api-client", querystring: "querystring", diff --git a/packages/trpc/server/routers/viewer/googleWorkspace/googleWorkspace.handler.ts b/packages/trpc/server/routers/viewer/googleWorkspace/googleWorkspace.handler.ts index f4d8ca8ee9..c67e19f2bf 100644 --- a/packages/trpc/server/routers/viewer/googleWorkspace/googleWorkspace.handler.ts +++ b/packages/trpc/server/routers/viewer/googleWorkspace/googleWorkspace.handler.ts @@ -1,4 +1,5 @@ -import { google } from "googleapis"; +import { admin_directory_v1 } from "@googleapis/admin"; +import { OAuth2Client } from "googleapis-common"; import { z } from "zod"; import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug"; @@ -48,14 +49,15 @@ export const getUsersFromGWorkspace = async ({}: CheckForGCalOptions) => { const credentials = credentialsSchema.parse(hasExistingCredentials.key); - const oAuth2Client = new google.auth.OAuth2(client_id, client_secret); + const oAuth2Client = new OAuth2Client(client_id, client_secret); // Set users credentials instead of our app credentials - allowing us to make requests on their behalf oAuth2Client.setCredentials(credentials); // Create a new instance of the Admin SDK directory API - const directory = google.admin({ version: "directory_v1", auth: oAuth2Client }); - + const directory = new admin_directory_v1.Admin({ + auth: oAuth2Client as any, + }); const { data } = await directory.users.list({ maxResults: 200, // Up this if we ever need to get more than 200 users customer: "my_customer", // This only works for single domain setups - we'll need to change this if we ever support multi-domain setups (unlikely we'll ever need to) diff --git a/packages/types/Calendar.d.ts b/packages/types/Calendar.d.ts index 03b57c8dad..ea6af7dcf1 100644 --- a/packages/types/Calendar.d.ts +++ b/packages/types/Calendar.d.ts @@ -5,7 +5,7 @@ import type { SelectedCalendar as _SelectedCalendar, } from "@prisma/client"; import type { Dayjs } from "dayjs"; -import type { calendar_v3 } from "googleapis"; +import type { calendar_v3 } from "@googleapis/calendar"; import type { Time } from "ical.js"; import type { TFunction } from "next-i18next"; import type z from "zod"; diff --git a/yarn.lock b/yarn.lock index 3649318cc9..365da5ce3c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4195,7 +4195,7 @@ __metadata: dependencies: "@calcom/prisma": "*" "@calcom/types": "*" - googleapis: ^84.0.0 + "@googleapis/calendar": ^9.7.9 languageName: unknown linkType: soft @@ -4205,7 +4205,6 @@ __metadata: dependencies: "@calcom/prisma": "*" "@calcom/types": "*" - googleapis: ^84.0.0 languageName: unknown linkType: soft @@ -5016,6 +5015,9 @@ __metadata: "@dub/analytics": ^0.0.15 "@formkit/auto-animate": 1.0.0-beta.5 "@glidejs/glide": ^3.5.2 + "@googleapis/admin": ^23.0.0 + "@googleapis/calendar": ^9.7.9 + "@googleapis/oauth2": ^1.0.7 "@hookform/error-message": ^2.0.0 "@hookform/resolvers": ^2.9.7 "@microsoft/microsoft-graph-types-beta": 0.15.0-preview @@ -5084,7 +5086,7 @@ __metadata: entities: ^4.4.0 env-cmd: ^10.1.0 eslint-config-next: ^13.2.1 - googleapis: ^84.0.0 + google-auth-library: ^9.15.0 gray-matter: ^4.0.3 handlebars: ^4.7.7 ical.js: ^1.4.0 @@ -6593,6 +6595,33 @@ __metadata: languageName: node linkType: hard +"@googleapis/admin@npm:^23.0.0": + version: 23.0.0 + resolution: "@googleapis/admin@npm:23.0.0" + dependencies: + googleapis-common: ^7.0.0 + checksum: 385ec58507646c4ccbee7309cfbfbe3c6dcb8879ed8b8510d95b3877f2b4eb5340472e68dae8402cf90415cc636b9f64c3353faf63ec8d6ac88388a53a26290c + languageName: node + linkType: hard + +"@googleapis/calendar@npm:^9.7.9": + version: 9.7.9 + resolution: "@googleapis/calendar@npm:9.7.9" + dependencies: + googleapis-common: ^7.0.0 + checksum: f767e1d897b038a9ebada71dcb4fc3564813769299025877fb574c0716cb2cebfc4b5d88f8fbcfbc9ccc73c3eb7448ac0c20ceb94df4239dc0bd0f902657db9f + languageName: node + linkType: hard + +"@googleapis/oauth2@npm:^1.0.7": + version: 1.0.7 + resolution: "@googleapis/oauth2@npm:1.0.7" + dependencies: + googleapis-common: ^7.0.0 + checksum: e99227439409aff14f228d4cfb5316e284072fe51ab7b65e16c02d0b90e7e9c67cbc1c4d7a502aff7bf992410478fc5c29f06ad4f180e7d8923690e9c344f6de + languageName: node + linkType: hard + "@grpc/grpc-js@npm:^1.7.1": version: 1.9.14 resolution: "@grpc/grpc-js@npm:1.9.14" @@ -25579,9 +25608,9 @@ __metadata: linkType: hard "fast-text-encoding@npm:^1.0.0": - version: 1.0.3 - resolution: "fast-text-encoding@npm:1.0.3" - checksum: 3e51365896f06d0dcab128092d095a0037d274deec419fecbd2388bc236d7b387610e0c72f920c6126e00c885ab096fbfaa3645712f5b98f721bef6b064916a8 + version: 1.0.6 + resolution: "fast-text-encoding@npm:1.0.6" + checksum: 9d58f694314b3283e785bf61954902536da228607ad246905e30256f9ab8331f780ac987e7222c9f5eafd04168d07e12b8054c85cedb76a2c05af0e82387a903 languageName: node linkType: hard @@ -26593,15 +26622,15 @@ __metadata: linkType: hard "gaxios@npm:^4.0.0": - version: 4.3.2 - resolution: "gaxios@npm:4.3.2" + version: 4.3.3 + resolution: "gaxios@npm:4.3.3" dependencies: abort-controller: ^3.0.0 extend: ^3.0.2 https-proxy-agent: ^5.0.0 is-stream: ^2.0.0 - node-fetch: ^2.6.1 - checksum: 1305fc6a4b9f888a7424ed160084d1f253a54bec04b4986ecb7929d8aa52382e737fc65feb03f0b0b47c524575286d765806d829868ec49fd383a1be8a973870 + node-fetch: ^2.6.7 + checksum: 0b72a00875404e2c3d7aca9f32535e931d7b0ebb850dc92fafc1685b99a109b04205c63e4637a2d0d9a261ac50adf83f7d33435f73e256dcca32564ef9358fee languageName: node linkType: hard @@ -27216,14 +27245,28 @@ __metadata: languageName: node linkType: hard -"google-p12-pem@npm:^3.1.3": - version: 3.1.3 - resolution: "google-p12-pem@npm:3.1.3" +"google-auth-library@npm:^9.15.0": + version: 9.15.0 + resolution: "google-auth-library@npm:9.15.0" dependencies: - node-forge: ^1.0.0 + base64-js: ^1.3.0 + ecdsa-sig-formatter: ^1.0.11 + gaxios: ^6.1.1 + gcp-metadata: ^6.1.0 + gtoken: ^7.0.0 + jws: ^4.0.0 + checksum: 7ff63ff755ce43601a1780cfbe6d8b523792bfd2b653a27180e41735b9f0ba5689668eb8b079c915300041e412ec00b7635b5c26e0f2139fecacb346b6f9f12f + languageName: node + linkType: hard + +"google-p12-pem@npm:^3.1.3": + version: 3.1.4 + resolution: "google-p12-pem@npm:3.1.4" + dependencies: + node-forge: ^1.3.1 bin: gp12-pem: build/src/bin/gp12-pem.js - checksum: 8628f2bf9b4c9b3bfc7220906c15af2b306e2ef30c7c398327a9cff9d4a12285f104545f00c46b716dd23966615f446d7e5efde44d590f9483d345be78ea115e + checksum: 72ce13b9536c69f21584cb7477ed4c34674325639a5dac42e8d774b78d367cb196ae7d37a52bba868235205760df353ac678a5e1756a4f4ded82e16d29d6cbb1 languageName: node linkType: hard @@ -33871,7 +33914,7 @@ __metadata: languageName: node linkType: hard -"node-forge@npm:1.3.1, node-forge@npm:^1.0.0": +"node-forge@npm:1.3.1, node-forge@npm:^1.3.1": version: 1.3.1 resolution: "node-forge@npm:1.3.1" checksum: 08fb072d3d670599c89a1704b3e9c649ff1b998256737f0e06fbd1a5bf41cae4457ccaee32d95052d80bbafd9ffe01284e078c8071f0267dc9744e51c5ed42a9