mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-27 19:25:15 +02:00
refactor(web): centralize OAuth callback cookie contract (fixes #1362)
Replace the duplicated `OAUTH_RESULT_COOKIE` constant and inline payload type across the callback route and connector dialog hook with a shared `contracts/types/oauth.types.ts` module that exports: - OAUTH_RESULT_COOKIE constant - oauthCallbackResultSchema Zod schema - OAuthCallbackResult type (inferred from the schema) - parseOAuthCallbackResult() helper that returns null on invalid JSON or shape mismatch The route handler now uses the shared type to constrain the cookie payload at compile time. The consumer hook validates the cookie value through the helper instead of an unchecked JSON.parse, removing the silent runtime risk when the cookie is tampered with or its shape drifts.
This commit is contained in:
parent
fc3b39483b
commit
aa86534a52
3 changed files with 39 additions and 18 deletions
|
|
@ -1,6 +1,5 @@
|
||||||
import { type NextRequest, NextResponse } from "next/server";
|
import { type NextRequest, NextResponse } from "next/server";
|
||||||
|
import { OAUTH_RESULT_COOKIE, type OAuthCallbackResult } from "@/contracts/types/oauth.types";
|
||||||
const OAUTH_RESULT_COOKIE = "connector_oauth_result";
|
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
|
|
@ -9,12 +8,13 @@ export async function GET(
|
||||||
const { search_space_id } = await params;
|
const { search_space_id } = await params;
|
||||||
const searchParams = request.nextUrl.searchParams;
|
const searchParams = request.nextUrl.searchParams;
|
||||||
|
|
||||||
const result = JSON.stringify({
|
const payload: OAuthCallbackResult = {
|
||||||
success: searchParams.get("success"),
|
success: searchParams.get("success"),
|
||||||
error: searchParams.get("error"),
|
error: searchParams.get("error"),
|
||||||
connector: searchParams.get("connector"),
|
connector: searchParams.get("connector"),
|
||||||
connectorId: searchParams.get("connectorId"),
|
connectorId: searchParams.get("connectorId"),
|
||||||
});
|
};
|
||||||
|
const result = JSON.stringify(payload);
|
||||||
|
|
||||||
const redirectUrl = new URL(`/dashboard/${search_space_id}/new-chat`, request.url);
|
const redirectUrl = new URL(`/dashboard/${search_space_id}/new-chat`, request.url);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-quer
|
||||||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||||
import { 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 { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
import { BACKEND_URL } from "@/lib/env-config";
|
||||||
import {
|
import {
|
||||||
trackConnectorConnected,
|
trackConnectorConnected,
|
||||||
trackConnectorDeleted,
|
trackConnectorDeleted,
|
||||||
|
|
@ -36,15 +38,12 @@ import {
|
||||||
OAUTH_CONNECTORS,
|
OAUTH_CONNECTORS,
|
||||||
OTHER_CONNECTORS,
|
OTHER_CONNECTORS,
|
||||||
} from "../constants/connector-constants";
|
} from "../constants/connector-constants";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
dateRangeSchema,
|
dateRangeSchema,
|
||||||
frequencyMinutesSchema,
|
frequencyMinutesSchema,
|
||||||
parseOAuthAuthResponse,
|
parseOAuthAuthResponse,
|
||||||
validateIndexingConfigState,
|
validateIndexingConfigState,
|
||||||
} from "../constants/connector-popup.schemas";
|
} from "../constants/connector-popup.schemas";
|
||||||
import { BACKEND_URL } from "@/lib/env-config";
|
|
||||||
const OAUTH_RESULT_COOKIE = "connector_oauth_result";
|
|
||||||
|
|
||||||
function readOAuthResultCookie(): string | null {
|
function readOAuthResultCookie(): string | null {
|
||||||
const match = document.cookie
|
const match = document.cookie
|
||||||
|
|
@ -211,17 +210,8 @@ export const useConnectorDialog = () => {
|
||||||
if (!raw || !searchSpaceId) return;
|
if (!raw || !searchSpaceId) return;
|
||||||
clearOAuthResultCookie();
|
clearOAuthResultCookie();
|
||||||
|
|
||||||
let result: {
|
const result = parseOAuthCallbackResult(raw);
|
||||||
success: string | null;
|
if (!result) return;
|
||||||
error: string | null;
|
|
||||||
connector: string | null;
|
|
||||||
connectorId: string | null;
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
result = JSON.parse(raw);
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
const oauthConnector = result.connector
|
const oauthConnector = result.connector
|
||||||
|
|
|
||||||
31
surfsense_web/contracts/types/oauth.types.ts
Normal file
31
surfsense_web/contracts/types/oauth.types.ts
Normal file
|
|
@ -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<typeof oauthCallbackResultSchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue