Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import {
NetworkIcon,
PieChartIcon,
Rows3Icon,
SmilePlusIcon,
SmartphoneIcon,
SmilePlusIcon,
StarIcon,
User,
} from "lucide-react";
Expand Down
130 changes: 92 additions & 38 deletions apps/web/app/api/google-sheet/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,52 +10,125 @@ import {
WEBAPP_URL,
} from "@/lib/constants";
import { createOrUpdateIntegration, getIntegrationByType } from "@/lib/integration/service";
import {
IntegrationOAuthStateError,
consumeIntegrationOAuthState,
getSafeOAuthCallbackError,
} from "@/lib/oauth/integration-state";
import { capturePostHogEvent } from "@/lib/posthog";
import { getOrganizationIdFromWorkspaceId } from "@/lib/utils/helper";
import { hasUserWorkspaceAccess } from "@/lib/workspace/auth";
import { authOptions } from "@/modules/auth/lib/authOptions";

const getGoogleSheetsRedirectUrl = (workspaceId: string) =>
new URL(`/workspaces/${workspaceId}/settings/workspace/integrations/google-sheets`, WEBAPP_URL);

const getGoogleSheetsOAuthState = async (state: string | null, userId: string) => {
try {
return await consumeIntegrationOAuthState({
provider: "googleSheets",
userId,
state,
});
} catch (err) {
if (err instanceof IntegrationOAuthStateError) {
return null;
}

throw err;
}
};

const getGoogleSheetsOAuthClient = () => {
const client_id = GOOGLE_SHEETS_CLIENT_ID;
const client_secret = GOOGLE_SHEETS_CLIENT_SECRET;
const redirect_uri = GOOGLE_SHEETS_REDIRECT_URL;

if (!client_id) {
return { response: responses.internalServerErrorResponse("Google client id is missing") };
}

if (!client_secret) {
return { response: responses.internalServerErrorResponse("Google client secret is missing") };
}

if (!redirect_uri) {
return { response: responses.internalServerErrorResponse("Google redirect url is missing") };
}

return { client: new google.auth.OAuth2(client_id, client_secret, redirect_uri) };
};

const captureGoogleSheetsConnectedEvent = async (userId: string, workspaceId: string) => {
try {
const organizationId = await getOrganizationIdFromWorkspaceId(workspaceId);
capturePostHogEvent(userId, "integration_connected", {
integration_type: "googleSheets",
organization_id: organizationId,
});
capturePostHogEvent(
userId,
"integration_connected",
{
integration_type: "googleSheets",
organization_id: organizationId,
workspace_id: workspaceId,
},
{ organizationId, workspaceId }
);
} catch (err) {
logger.error({ error: err }, "Failed to capture PostHog integration_connected event for googleSheets");
}
};

export const GET = async (req: Request) => {
const url = new URL(req.url);
const workspaceId = url.searchParams.get("state");
const state = url.searchParams.get("state");
const code = url.searchParams.get("code");

if (!workspaceId) {
return responses.badRequestResponse("Invalid workspaceId");
}
const error = url.searchParams.get("error");

const session = await getServerSession(authOptions);
if (!session) {
return responses.notAuthenticatedResponse();
}

const oauthState = await getGoogleSheetsOAuthState(state, session.user.id);
if (!oauthState) {
return responses.badRequestResponse("Invalid OAuth state");
}

const workspaceId = oauthState.workspaceId;
const canUserAccessWorkspace = await hasUserWorkspaceAccess(session.user.id, workspaceId);
if (!canUserAccessWorkspace) {
return responses.unauthorizedResponse();
}

const basePath = `/workspaces/${workspaceId}/settings/workspace`;
const redirectUrl = getGoogleSheetsRedirectUrl(workspaceId);

const safeError = getSafeOAuthCallbackError(error);
if (safeError) {
redirectUrl.searchParams.set("error", safeError);
return Response.redirect(redirectUrl);
}

if (code && typeof code !== "string") {
return responses.badRequestResponse("`code` must be a string");
}

const client_id = GOOGLE_SHEETS_CLIENT_ID;
const client_secret = GOOGLE_SHEETS_CLIENT_SECRET;
const redirect_uri = GOOGLE_SHEETS_REDIRECT_URL;
if (!client_id) return responses.internalServerErrorResponse("Google client id is missing");
if (!client_secret) return responses.internalServerErrorResponse("Google client secret is missing");
if (!redirect_uri) return responses.internalServerErrorResponse("Google redirect url is missing");
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
const oAuth2ClientResult = getGoogleSheetsOAuthClient();
if ("response" in oAuth2ClientResult) {
return oAuth2ClientResult.response;
}
const oAuth2Client = oAuth2ClientResult.client;

if (!code) {
return Response.redirect(`${WEBAPP_URL}${basePath}/integrations/google-sheets`);
return Response.redirect(redirectUrl);
}

const token = await oAuth2Client.getToken(code);
const key = token.res?.data;
if (!key) {
return Response.redirect(`${WEBAPP_URL}${basePath}/integrations/google-sheets`);
return Response.redirect(redirectUrl);
}

oAuth2Client.setCredentials({ access_token: key.access_token });
Expand All @@ -81,29 +154,10 @@ export const GET = async (req: Request) => {
};

const result = await createOrUpdateIntegration(workspaceId, googleSheetIntegration);
if (result) {
try {
const organizationId = await getOrganizationIdFromWorkspaceId(workspaceId);
capturePostHogEvent(session.user.id, "integration_connected", {
integration_type: "googleSheets",
organization_id: organizationId,
});
capturePostHogEvent(
session.user.id,
"integration_connected",
{
integration_type: "googleSheets",
organization_id: organizationId,
workspace_id: workspaceId,
},
{ organizationId, workspaceId }
);
} catch (err) {
logger.error({ error: err }, "Failed to capture PostHog integration_connected event for googleSheets");
}

return Response.redirect(`${WEBAPP_URL}${basePath}/integrations/google-sheets`);
if (!result) {
return responses.internalServerErrorResponse("Failed to create or update Google Sheets integration");
}

return responses.internalServerErrorResponse("Failed to create or update Google Sheets integration");
await captureGoogleSheetsConnectedEvent(session.user.id, workspaceId);
return Response.redirect(redirectUrl);
};
18 changes: 17 additions & 1 deletion apps/web/app/api/google-sheet/route.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { google } from "googleapis";
import { getServerSession } from "next-auth";
import { NextRequest } from "next/server";
import { logger } from "@formbricks/logger";
import { responses } from "@/app/lib/api/response";
import {
GOOGLE_SHEETS_CLIENT_ID,
GOOGLE_SHEETS_CLIENT_SECRET,
GOOGLE_SHEETS_REDIRECT_URL,
} from "@/lib/constants";
import { createIntegrationOAuthState } from "@/lib/oauth/integration-state";
import { hasUserWorkspaceAccess } from "@/lib/workspace/auth";
import { authOptions } from "@/modules/auth/lib/authOptions";

Expand Down Expand Up @@ -39,12 +41,26 @@ export const GET = async (req: NextRequest) => {
if (!client_secret) return responses.internalServerErrorResponse("Google client secret is missing");
if (!redirect_uri) return responses.internalServerErrorResponse("Google redirect url is missing");
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
let state: string;
try {
state = await createIntegrationOAuthState({
provider: "googleSheets",
userId: session.user.id,
workspaceId,
});
} catch (error) {
logger.error(
{ error, provider: "googleSheets", userId: session.user.id, workspaceId },
"Failed to create Google Sheets OAuth state"
);
return responses.internalServerErrorResponse("Unable to start OAuth flow");
}

const authUrl = oAuth2Client.generateAuthUrl({
access_type: "offline",
scope: scopes,
prompt: "consent",
state: workspaceId,
state,
});

return responses.successResponse({ authUrl });
Expand Down
Loading
Loading