From f38c6386a3d7c8cfd4e87c0b0451741a9b461691 Mon Sep 17 00:00:00 2001 From: Universe Date: Thu, 5 Feb 2026 22:15:04 +0900 Subject: [PATCH] Refactor API hooks to use dynamic base URL for hosted environments Update the API hooks in the submission process to utilize a dynamic base URL that adapts based on the hosting environment. This change prevents potential 401 errors when accessing protected domains and ensures the GRIDA S2S private API key is safely handled. Additionally, clean up the handling of the API key in the notification email route for improved security. --- editor/app/(api)/(public)/v1/submit/[id]/hooks.ts | 11 +++++++---- .../[id]/hooks/notification-respondent-email/route.ts | 5 ++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/editor/app/(api)/(public)/v1/submit/[id]/hooks.ts b/editor/app/(api)/(public)/v1/submit/[id]/hooks.ts index 2ac00fd26..a8dc0874b 100644 --- a/editor/app/(api)/(public)/v1/submit/[id]/hooks.ts +++ b/editor/app/(api)/(public)/v1/submit/[id]/hooks.ts @@ -4,7 +4,10 @@ import { Env } from "@/env"; import { resend } from "@/clients/resend"; import EmailTemplate from "@/theme/templates-email/formcomplete/default"; -const GRIDA_S2S_PRIVATE_API_KEY = process.env.GRIDA_S2S_PRIVATE_API_KEY; +// In hosted env, avoid calling the deployment domain (`*.vercel.app`) since it +// can be protected upstream (401) even when our app routes would allow it. +const HOOK_BASE_URL = Env.server.IS_HOSTED ? Env.web.HOST : Env.server.HOST; +const GRIDA_S2S_PRIVATE_API_KEY = process.env.GRIDA_S2S_PRIVATE_API_KEY ?? null; const bird = new Bird( process.env.BIRD_WORKSPACE_ID as string, @@ -24,7 +27,7 @@ export namespace OnSubmit { response_id: string; session_id: string; }) { - return fetch(`${Env.server.HOST}/v1/submit/${form_id}/hooks/clearsession`, { + return fetch(`${HOOK_BASE_URL}/v1/submit/${form_id}/hooks/clearsession`, { headers: { "Content-Type": "application/json", }, @@ -43,7 +46,7 @@ export namespace OnSubmit { form_id: string; response_id: string; }) { - return fetch(`${Env.server.HOST}/v1/submit/${form_id}/hooks/postindexing`, { + return fetch(`${HOOK_BASE_URL}/v1/submit/${form_id}/hooks/postindexing`, { headers: { "Content-Type": "application/json", }, @@ -62,7 +65,7 @@ export namespace OnSubmit { response_id: string; }) { return fetch( - `${Env.server.HOST}/v1/submit/${form_id}/hooks/notification-respondent-email`, + `${HOOK_BASE_URL}/v1/submit/${form_id}/hooks/notification-respondent-email`, { headers: { "Content-Type": "application/json", diff --git a/editor/app/(api)/(public)/v1/submit/[id]/hooks/notification-respondent-email/route.ts b/editor/app/(api)/(public)/v1/submit/[id]/hooks/notification-respondent-email/route.ts index c565a2ae7..5c79bb448 100644 --- a/editor/app/(api)/(public)/v1/submit/[id]/hooks/notification-respondent-email/route.ts +++ b/editor/app/(api)/(public)/v1/submit/[id]/hooks/notification-respondent-email/route.ts @@ -14,7 +14,7 @@ type Params = { id: string }; * This route uses `service_role` and can send emails, so it must not be * callable by arbitrary third-parties. */ -const GRIDA_S2S_PRIVATE_API_KEY = process.env.GRIDA_S2S_PRIVATE_API_KEY; +const GRIDA_S2S_PRIVATE_API_KEY = process.env.GRIDA_S2S_PRIVATE_API_KEY ?? null; export async function POST( req: NextRequest, @@ -22,8 +22,7 @@ export async function POST( params: Promise; } ) { - const provided = - req.headers.get("x-grida-s2s-key") ?? req.headers.get("x-hook-secret"); + const provided = req.headers.get("x-grida-s2s-key"); if (!GRIDA_S2S_PRIVATE_API_KEY) { console.error( "notification-respondent-email/err/misconfigured: GRIDA_S2S_PRIVATE_API_KEY missing"