diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/callback/route.ts b/surfsense_web/app/dashboard/[search_space_id]/connectors/callback/route.ts index 14573066d..304f33a33 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/callback/route.ts +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/callback/route.ts @@ -1,6 +1,5 @@ import { type NextRequest, NextResponse } from "next/server"; - -const OAUTH_RESULT_COOKIE = "connector_oauth_result"; +import { OAUTH_RESULT_COOKIE, type OAuthCallbackResult } from "@/contracts/types/oauth.types"; export async function GET( request: NextRequest, @@ -9,12 +8,13 @@ export async function GET( const { search_space_id } = await params; const searchParams = request.nextUrl.searchParams; - const result = JSON.stringify({ + const payload: OAuthCallbackResult = { success: searchParams.get("success"), error: searchParams.get("error"), connector: searchParams.get("connector"), connectorId: searchParams.get("connectorId"), - }); + }; + const result = JSON.stringify(payload); const redirectUrl = new URL(`/dashboard/${search_space_id}/new-chat`, request.url); diff --git a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts index d1d675ad1..25ab82e2e 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts @@ -14,7 +14,9 @@ import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-quer import { EnumConnectorName } from "@/contracts/enums/connector"; import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import { searchSourceConnector } from "@/contracts/types/connector.types"; +import { OAUTH_RESULT_COOKIE, parseOAuthCallbackResult } from "@/contracts/types/oauth.types"; import { authenticatedFetch } from "@/lib/auth-utils"; +import { BACKEND_URL } from "@/lib/env-config"; import { trackConnectorConnected, trackConnectorDeleted, @@ -36,15 +38,12 @@ import { OAUTH_CONNECTORS, OTHER_CONNECTORS, } from "../constants/connector-constants"; - import { dateRangeSchema, frequencyMinutesSchema, parseOAuthAuthResponse, validateIndexingConfigState, } from "../constants/connector-popup.schemas"; -import { BACKEND_URL } from "@/lib/env-config"; -const OAUTH_RESULT_COOKIE = "connector_oauth_result"; function readOAuthResultCookie(): string | null { const match = document.cookie @@ -211,17 +210,8 @@ export const useConnectorDialog = () => { if (!raw || !searchSpaceId) return; clearOAuthResultCookie(); - let result: { - success: string | null; - error: string | null; - connector: string | null; - connectorId: string | null; - }; - try { - result = JSON.parse(raw); - } catch { - return; - } + const result = parseOAuthCallbackResult(raw); + if (!result) return; if (result.error) { const oauthConnector = result.connector diff --git a/surfsense_web/contracts/types/oauth.types.ts b/surfsense_web/contracts/types/oauth.types.ts new file mode 100644 index 000000000..8b3854cf8 --- /dev/null +++ b/surfsense_web/contracts/types/oauth.types.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; + +export const OAUTH_RESULT_COOKIE = "connector_oauth_result"; + +/** + * Schema for the payload written to the `connector_oauth_result` cookie by the + * OAuth callback route and read back by the connector dialog hook. + */ +export const oauthCallbackResultSchema = z.object({ + success: z.string().nullable(), + error: z.string().nullable(), + connector: z.string().nullable(), + connectorId: z.string().nullable(), +}); + +export type OAuthCallbackResult = z.infer; + +/** + * Safely decode and validate the OAuth callback cookie value. Returns `null` + * when the value is not valid JSON or does not match the expected shape. + */ +export function parseOAuthCallbackResult(raw: string): OAuthCallbackResult | null { + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch { + return null; + } + const result = oauthCallbackResultSchema.safeParse(parsed); + return result.success ? result.data : null; +}