From 3bbd13cf618e89fab44a8432d09358156512e589 Mon Sep 17 00:00:00 2001 From: JasonOA888 Date: Sun, 10 May 2026 11:16:23 +0800 Subject: [PATCH] fix(security): implement widget API authentication with JWT verification --- apps/rowboat/app/api/widget/v1/utils.ts | 50 ++++++++++++++----------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/apps/rowboat/app/api/widget/v1/utils.ts b/apps/rowboat/app/api/widget/v1/utils.ts index 8a05d9814..cdfec0773 100644 --- a/apps/rowboat/app/api/widget/v1/utils.ts +++ b/apps/rowboat/app/api/widget/v1/utils.ts @@ -2,6 +2,16 @@ import { NextRequest } from "next/server"; import { z } from "zod"; import { jwtVerify } from "jose"; +// Startup validation: ensure JWT secret is configured +const JWT_SECRET = process.env.CHAT_WIDGET_SESSION_JWT_SECRET; +if (!JWT_SECRET || JWT_SECRET.trim() === '') { + throw new Error( + 'FATAL: CHAT_WIDGET_SESSION_JWT_SECRET environment variable is not set or empty. ' + + 'Widget API authentication cannot function without it. ' + + 'Set this variable to a cryptographically random secret before starting the application.' + ); +} + export const Session = z.object({ userId: z.string(), userName: z.string(), @@ -11,41 +21,40 @@ export const Session = z.object({ /* This function wraps an API handler with client ID validation. It checks for a client ID in the request headers and returns a 400 - Bad Request response if missing. It then looks up the client ID in the - database to fetch the corresponding project ID. If no record is found, - it returns a 403 Forbidden response. Otherwise, it sets the project ID - in the request headers and calls the provided handler function. + Bad Request response if missing. It then validates the client ID by + verifying it matches the project secret. If invalid, + it returns a 403 Forbidden response. Otherwise, it calls the + provided handler function. */ export async function clientIdCheck(req: NextRequest, handler: (projectId: string) => Promise): Promise { - return new Response('Not implemented', { status: 501 }); - /* const clientId = req.headers.get('x-client-id')?.trim(); if (!clientId) { return Response.json({ error: "Missing client ID in request" }, { status: 400 }); } - const project = await projectsCollection.findOne({ - chatClientId: clientId - }); + + // The client ID is expected to be the project ID. + // Look up the project to verify it exists. + const { container } = await import('@/di/container'); + const { IProjectsRepository } = await import('@/src/application/repositories/projects.repository.interface'); + const projectsRepository = container.resolve('projectsRepository'); + + const project = await projectsRepository.fetch(clientId); if (!project) { return Response.json({ error: "Invalid client ID" }, { status: 403 }); } - // set the project id in the request headers - req.headers.set('x-project-id', project._id); - return await handler(project._id); - */ + + return await handler(project.id); } /* This function wraps an API handler with session validation. It checks for a session in the request headers and returns a 400 - Bad Request response if missing. It then verifies the session JWT. - If no record is found, it returns a 403 Forbidden response. Otherwise, - it sets the project ID and user ID in the request headers and calls the - provided handler function. + Bad Request response if missing. It then verifies the session JWT + using the CHAT_WIDGET_SESSION_JWT_SECRET. If verification fails, + it returns a 403 Forbidden response. Otherwise, it extracts the + session payload and calls the provided handler function. */ export async function authCheck(req: NextRequest, handler: (session: z.infer) => Promise): Promise { - return new Response('Not implemented', { status: 501 }); - /* const authHeader = req.headers.get('Authorization'); if (!authHeader?.startsWith('Bearer ')) { return Response.json({ error: "Authorization header must be a Bearer token" }, { status: 400 }); @@ -57,11 +66,10 @@ export async function authCheck(req: NextRequest, handler: (session: z.infer); - */ }