mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-27 17:36:25 +02:00
add rowboat app
This commit is contained in:
parent
b83b5f8a07
commit
10f76ef49f
117 changed files with 25370 additions and 0 deletions
3
apps/rowboat/app/api/auth/[auth0]/route.ts
Normal file
3
apps/rowboat/app/api/auth/[auth0]/route.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { handleAuth } from '@auth0/nextjs-auth0';
|
||||
|
||||
export const GET = handleAuth();
|
||||
40
apps/rowboat/app/api/v1/chats/[chatId]/close/route.ts
Normal file
40
apps/rowboat/app/api/v1/chats/[chatId]/close/route.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { NextRequest } from "next/server";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
import { db } from "@/app/lib/mongodb";
|
||||
import { z } from "zod";
|
||||
import { ObjectId } from "mongodb";
|
||||
import { authCheck } from "../../../utils";
|
||||
|
||||
const chatsCollection = db.collection<z.infer<typeof apiV1.Chat>>("chats");
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { chatId: string } }
|
||||
): Promise<Response> {
|
||||
return await authCheck(request, async (session) => {
|
||||
const { chatId } = params;
|
||||
|
||||
const result = await chatsCollection.findOneAndUpdate(
|
||||
{
|
||||
_id: new ObjectId(chatId),
|
||||
projectId: session.projectId,
|
||||
userId: session.userId,
|
||||
closed: { $exists: false },
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
closed: true,
|
||||
closedAt: new Date().toISOString(),
|
||||
closeReason: "user-closed-chat",
|
||||
},
|
||||
},
|
||||
{ returnDocument: 'after' }
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
return Response.json({ error: "Chat not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
return Response.json(result);
|
||||
});
|
||||
}
|
||||
94
apps/rowboat/app/api/v1/chats/[chatId]/messages/route.ts
Normal file
94
apps/rowboat/app/api/v1/chats/[chatId]/messages/route.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import { NextRequest } from "next/server";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
import { db } from "@/app/lib/mongodb";
|
||||
import { z } from "zod";
|
||||
import { Filter, ObjectId } from "mongodb";
|
||||
import { authCheck } from "../../../utils";
|
||||
|
||||
const chatsCollection = db.collection<z.infer<typeof apiV1.Chat>>("chats");
|
||||
const chatMessagesCollection = db.collection<z.infer<typeof apiV1.ChatMessage>>("chatMessages");
|
||||
|
||||
// list messages
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: { chatId: string } }
|
||||
): Promise<Response> {
|
||||
return await authCheck(req, async (session) => {
|
||||
const { chatId } = params;
|
||||
|
||||
// Check if chat exists
|
||||
const chat = await chatsCollection.findOne({
|
||||
_id: new ObjectId(chatId),
|
||||
projectId: session.projectId,
|
||||
userId: session.userId
|
||||
});
|
||||
if (!chat) {
|
||||
return Response.json({ error: "Chat not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
// Parse query parameters
|
||||
const searchParams = req.nextUrl.searchParams;
|
||||
const limit = 10; // Hardcoded limit
|
||||
const next = searchParams.get('next');
|
||||
const previous = searchParams.get('previous');
|
||||
|
||||
// Construct the query
|
||||
const query: Filter<z.infer<typeof apiV1.ChatMessage>> = {
|
||||
chatId,
|
||||
$or: [
|
||||
{ role: 'user' },
|
||||
{ role: 'assistant', agenticResponseType: { $eq: 'external' } }
|
||||
],
|
||||
};
|
||||
|
||||
// Add cursor condition to the query
|
||||
if (previous) {
|
||||
query._id = { $lt: new ObjectId(previous) };
|
||||
} else if (next) {
|
||||
query._id = { $gt: new ObjectId(next) };
|
||||
}
|
||||
|
||||
// Fetch messages from the database
|
||||
let messages = await chatMessagesCollection
|
||||
.find(query)
|
||||
.sort({ _id: previous ? -1 : 1 }) // Sort based on direction
|
||||
.limit(limit + 1) // Fetch one extra to determine if there are more results
|
||||
.toArray();
|
||||
|
||||
// Determine if there are more results
|
||||
const hasMore = messages.length > limit;
|
||||
if (hasMore) {
|
||||
messages.pop();
|
||||
}
|
||||
|
||||
// Reverse the array if we're paginating backwards
|
||||
if (previous) {
|
||||
messages.reverse();
|
||||
}
|
||||
|
||||
let nextCursor: string | undefined;
|
||||
let previousCursor: string | undefined;
|
||||
if (messages.length > 0) {
|
||||
if (hasMore || previous) {
|
||||
nextCursor = messages[messages.length - 1]._id.toString();
|
||||
}
|
||||
if (next || (previous && hasMore)) {
|
||||
previousCursor = messages[0]._id.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the response
|
||||
const response: z.infer<typeof apiV1.ApiGetChatMessagesResponse> = {
|
||||
messages: messages.map(message => ({
|
||||
...message,
|
||||
id: message._id.toString(),
|
||||
_id: undefined
|
||||
})),
|
||||
next: nextCursor,
|
||||
previous: previousCursor,
|
||||
};
|
||||
|
||||
// Return response
|
||||
return Response.json(response);
|
||||
});
|
||||
}
|
||||
43
apps/rowboat/app/api/v1/chats/[chatId]/route.ts
Normal file
43
apps/rowboat/app/api/v1/chats/[chatId]/route.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { NextRequest } from "next/server";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
import { db } from "@/app/lib/mongodb";
|
||||
import { z } from "zod";
|
||||
import { ObjectId } from "mongodb";
|
||||
import { authCheck } from "../../utils";
|
||||
|
||||
const chatsCollection = db.collection<z.infer<typeof apiV1.Chat>>("chats");
|
||||
|
||||
// get chat
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ chatId: string }> }
|
||||
): Promise<Response> {
|
||||
return await authCheck(req, async (session) => {
|
||||
const { chatId } = await params;
|
||||
|
||||
// fetch the chat from the database
|
||||
let chatIdObj: ObjectId;
|
||||
try {
|
||||
chatIdObj = new ObjectId(chatId);
|
||||
} catch (e) {
|
||||
return Response.json({ error: "Invalid chat ID" }, { status: 400 });
|
||||
}
|
||||
|
||||
const chat = await chatsCollection.findOne({
|
||||
projectId: session.projectId,
|
||||
userId: session.userId,
|
||||
_id: chatIdObj
|
||||
});
|
||||
|
||||
if (!chat) {
|
||||
return Response.json({ error: "Chat not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
// return the chat
|
||||
return Response.json({
|
||||
...chat,
|
||||
id: chat._id.toString(),
|
||||
_id: undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
152
apps/rowboat/app/api/v1/chats/[chatId]/turn/route.ts
Normal file
152
apps/rowboat/app/api/v1/chats/[chatId]/turn/route.ts
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import { NextRequest } from "next/server";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
import { agentWorkflowsCollection, db, projectsCollection } from "@/app/lib/mongodb";
|
||||
import { z } from "zod";
|
||||
import { ObjectId, WithId } from "mongodb";
|
||||
import { authCheck } from "../../../utils";
|
||||
import { AgenticAPIChatRequest, convertToAgenticAPIChatMessages, convertToCoreMessages, convertWorkflowToAgenticAPI } from "@/app/lib/types";
|
||||
import { executeClientTool, getAssistantResponse } from "@/app/actions";
|
||||
|
||||
const chatsCollection = db.collection<z.infer<typeof apiV1.Chat>>("chats");
|
||||
const chatMessagesCollection = db.collection<z.infer<typeof apiV1.ChatMessage>>("chatMessages");
|
||||
|
||||
// get next turn / agent response
|
||||
export async function POST(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ chatId: string }> }
|
||||
): Promise<Response> {
|
||||
return await authCheck(req, async (session) => {
|
||||
const { chatId } = await params;
|
||||
|
||||
// parse and validate the request body
|
||||
let body;
|
||||
try {
|
||||
body = await req.json();
|
||||
} catch (e) {
|
||||
return Response.json({ error: "Invalid JSON in request body" }, { status: 400 });
|
||||
}
|
||||
const result = apiV1.ApiChatTurnRequest.safeParse(body);
|
||||
if (!result.success) {
|
||||
return Response.json({ error: `Invalid request body: ${result.error.message}` }, { status: 400 });
|
||||
}
|
||||
const userMessage: z.infer<typeof apiV1.ChatMessage> = {
|
||||
version: 'v1',
|
||||
createdAt: new Date().toISOString(),
|
||||
chatId,
|
||||
role: 'user',
|
||||
content: result.data.message,
|
||||
};
|
||||
|
||||
// ensure chat exists
|
||||
const chat = await chatsCollection.findOne({
|
||||
projectId: session.projectId,
|
||||
userId: session.userId,
|
||||
_id: new ObjectId(chatId)
|
||||
});
|
||||
if (!chat) {
|
||||
return Response.json({ error: "Chat not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
// prepare system message which will contain user data
|
||||
const systemMessage: z.infer<typeof apiV1.ChatMessage> = {
|
||||
version: 'v1',
|
||||
createdAt: new Date().toISOString(),
|
||||
chatId,
|
||||
role: 'system',
|
||||
content: `The following user data is available to you: ${JSON.stringify(chat.userData)}`,
|
||||
};
|
||||
|
||||
// fetch existing chat messages
|
||||
const messages = await chatMessagesCollection.find({ chatId: chatId }).toArray();
|
||||
|
||||
// fetch project settings
|
||||
const projectSettings = await projectsCollection.findOne({
|
||||
"_id": session.projectId,
|
||||
});
|
||||
if (!projectSettings) {
|
||||
throw new Error("Project settings not found");
|
||||
}
|
||||
|
||||
// fetch workflow
|
||||
const workflow = await agentWorkflowsCollection.findOne({
|
||||
projectId: session.projectId,
|
||||
_id: new ObjectId(projectSettings.publishedWorkflowId),
|
||||
});
|
||||
if (!workflow) {
|
||||
throw new Error("Workflow not found");
|
||||
}
|
||||
|
||||
// get assistant response
|
||||
const { agents, tools, prompts, startAgent } = convertWorkflowToAgenticAPI(workflow);
|
||||
const unsavedMessages: z.infer<typeof apiV1.ChatMessage>[] = [userMessage];
|
||||
let resolvingToolCalls = true;
|
||||
let state: unknown = chat.agenticState ?? {last_agent_name: startAgent};
|
||||
while (resolvingToolCalls) {
|
||||
const request: z.infer<typeof AgenticAPIChatRequest> = {
|
||||
messages: convertToAgenticAPIChatMessages([systemMessage, ...messages, ...unsavedMessages]),
|
||||
state,
|
||||
agents,
|
||||
tools,
|
||||
prompts,
|
||||
startAgent,
|
||||
};
|
||||
console.log("turn: sending agentic request", JSON.stringify(request, null, 2));
|
||||
const response = await getAssistantResponse(session.projectId, request);
|
||||
state = response.state;
|
||||
if (response.messages.length === 0) {
|
||||
throw new Error("No messages returned from assistant");
|
||||
}
|
||||
unsavedMessages.push(...response.messages.map(m => ({
|
||||
...m,
|
||||
version: 'v1' as const,
|
||||
chatId,
|
||||
createdAt: new Date().toISOString(),
|
||||
})));
|
||||
|
||||
// if the last messages is tool call, execute them
|
||||
const lastMessage = response.messages[response.messages.length - 1];
|
||||
if (lastMessage.role === 'assistant' && 'tool_calls' in lastMessage) {
|
||||
// execute tool calls
|
||||
console.log("Executing tool calls", lastMessage.tool_calls);
|
||||
const toolCallResults = await Promise.all(lastMessage.tool_calls.map(async toolCall => {
|
||||
console.log('executing tool call', toolCall);
|
||||
try {
|
||||
return await executeClientTool(toolCall, session.projectId);
|
||||
} catch (error) {
|
||||
console.error(`Error executing tool call ${toolCall.id}:`, error);
|
||||
return { error: "Tool execution failed" };
|
||||
}
|
||||
}));
|
||||
unsavedMessages.push(...toolCallResults.map((result, index) => ({
|
||||
version: 'v1' as const,
|
||||
chatId,
|
||||
createdAt: new Date().toISOString(),
|
||||
role: 'tool' as const,
|
||||
tool_call_id: lastMessage.tool_calls[index].id,
|
||||
tool_name: lastMessage.tool_calls[index].function.name,
|
||||
content: JSON.stringify(result),
|
||||
})));
|
||||
} else {
|
||||
// ensure that the last message is from an assistant
|
||||
// and is of an external type
|
||||
if (lastMessage.role !== 'assistant' || lastMessage.agenticResponseType !== 'external') {
|
||||
throw new Error("Last message is not from an assistant and is not of an external type");
|
||||
}
|
||||
resolvingToolCalls = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// save unsaved messages and update chat state
|
||||
await chatMessagesCollection.insertMany(unsavedMessages);
|
||||
await chatsCollection.updateOne({ _id: new ObjectId(chatId) }, { $set: { agenticState: state } });
|
||||
|
||||
// send back the last message
|
||||
const lastMessage = unsavedMessages[unsavedMessages.length - 1] as WithId<z.infer<typeof apiV1.ChatMessage>>;
|
||||
return Response.json({
|
||||
...lastMessage,
|
||||
id: lastMessage._id.toString(),
|
||||
_id: undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
116
apps/rowboat/app/api/v1/chats/route.ts
Normal file
116
apps/rowboat/app/api/v1/chats/route.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import { NextRequest } from "next/server";
|
||||
import { db } from "@/app/lib/mongodb";
|
||||
import { z } from "zod";
|
||||
import { ObjectId } from "mongodb";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
import { authCheck } from "../utils";
|
||||
|
||||
const chatsCollection = db.collection<z.infer<typeof apiV1.Chat>>("chats");
|
||||
|
||||
// create a chat
|
||||
export async function POST(
|
||||
req: NextRequest,
|
||||
): Promise<Response> {
|
||||
return await authCheck(req, async (session) => {
|
||||
// parse and validate the request body
|
||||
let body;
|
||||
try {
|
||||
body = await req.json();
|
||||
} catch (e) {
|
||||
return Response.json({ error: "Invalid JSON in request body" }, { status: 400 });
|
||||
}
|
||||
const result = apiV1.ApiCreateChatRequest.safeParse(body);
|
||||
if (!result.success) {
|
||||
return new Response(JSON.stringify({ error: `Invalid request body: ${result.error.message}` }), { status: 400 });
|
||||
}
|
||||
|
||||
// insert the chat into the database
|
||||
const id = new ObjectId();
|
||||
const chat: z.infer<typeof apiV1.Chat> = {
|
||||
version: "v1",
|
||||
projectId: session.projectId,
|
||||
userId: session.userId,
|
||||
createdAt: new Date().toISOString(),
|
||||
userData: {
|
||||
userId: session.userId,
|
||||
userName: session.userName,
|
||||
},
|
||||
}
|
||||
await chatsCollection.insertOne({
|
||||
...chat,
|
||||
_id: id,
|
||||
});
|
||||
|
||||
// return response
|
||||
const response: z.infer<typeof apiV1.ApiCreateChatResponse> = {
|
||||
...chat,
|
||||
id: id.toString(),
|
||||
};
|
||||
return Response.json(response);
|
||||
});
|
||||
}
|
||||
|
||||
// list chats
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
): Promise<Response> {
|
||||
return await authCheck(req, async (session) => {
|
||||
// Parse query parameters
|
||||
const searchParams = req.nextUrl.searchParams;
|
||||
const limit = 10; // Hardcoded limit
|
||||
const next = searchParams.get('next');
|
||||
const previous = searchParams.get('previous');
|
||||
|
||||
// Add userId to query to only show chats for current user
|
||||
const query: { projectId: string; userId: string; _id?: { $lt?: ObjectId; $gt?: ObjectId } } = {
|
||||
projectId: session.projectId,
|
||||
userId: session.userId
|
||||
};
|
||||
|
||||
// Add cursor condition to the query
|
||||
if (next) {
|
||||
query._id = { $lt: new ObjectId(next) };
|
||||
} else if (previous) {
|
||||
query._id = { $gt: new ObjectId(previous) };
|
||||
}
|
||||
|
||||
// Fetch chats from the database
|
||||
let chats = await chatsCollection
|
||||
.find(query)
|
||||
.sort({ _id: -1 }) // Sort in descending order
|
||||
.limit(limit + 1) // Fetch one extra to determine if there are more results
|
||||
.toArray();
|
||||
|
||||
// Determine if there are more results
|
||||
const hasMore = chats.length > limit;
|
||||
if (hasMore) {
|
||||
chats.pop();
|
||||
}
|
||||
let nextCursor: string | undefined;
|
||||
let previousCursor: string | undefined;
|
||||
if (chats.length > 0) {
|
||||
if (hasMore || previous) {
|
||||
nextCursor = chats[chats.length - 1]._id.toString();
|
||||
}
|
||||
if (next || (previous && hasMore)) {
|
||||
previousCursor = chats[0]._id.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the response
|
||||
const response: z.infer<typeof apiV1.ApiGetChatsResponse> = {
|
||||
chats: chats
|
||||
.slice(0, limit)
|
||||
.map(chat => ({
|
||||
...chat,
|
||||
id: chat._id.toString(),
|
||||
_id: undefined
|
||||
})),
|
||||
next: nextCursor,
|
||||
previous: previousCursor,
|
||||
};
|
||||
|
||||
// Return response
|
||||
return Response.json(response);
|
||||
});
|
||||
}
|
||||
30
apps/rowboat/app/api/v1/session/guest/route.ts
Normal file
30
apps/rowboat/app/api/v1/session/guest/route.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { NextRequest } from "next/server";
|
||||
import { clientIdCheck } from "../../utils";
|
||||
import { SignJWT } from "jose";
|
||||
import { z } from "zod";
|
||||
import { Session } from "../../utils";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
|
||||
export async function POST(req: NextRequest): Promise<Response> {
|
||||
return await clientIdCheck(req, async (projectId) => {
|
||||
// create a new guest user
|
||||
const session: z.infer<typeof Session> = {
|
||||
userId: `guest-${crypto.randomUUID()}`,
|
||||
userName: 'Guest User',
|
||||
projectId: projectId
|
||||
};
|
||||
|
||||
// Create and sign JWT
|
||||
const token = await new SignJWT(session)
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('24h')
|
||||
.sign(new TextEncoder().encode(process.env.CHAT_WIDGET_SESSION_JWT_SECRET));
|
||||
|
||||
const response: z.infer<typeof apiV1.ApiCreateGuestSessionResponse> = {
|
||||
sessionId: token,
|
||||
};
|
||||
|
||||
return Response.json(response);
|
||||
});
|
||||
}
|
||||
55
apps/rowboat/app/api/v1/session/user/route.ts
Normal file
55
apps/rowboat/app/api/v1/session/user/route.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { NextRequest } from "next/server";
|
||||
import { clientIdCheck } from "../../utils";
|
||||
import { SignJWT, jwtVerify } from "jose";
|
||||
import { z } from "zod";
|
||||
import { Session } from "../../utils";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
import { projectsCollection } from "@/app/lib/mongodb";
|
||||
|
||||
export async function POST(req: NextRequest): Promise<Response> {
|
||||
return await clientIdCheck(req, async (projectId) => {
|
||||
// decode and validate JWT
|
||||
const json = await req.json();
|
||||
const parsedRequest = apiV1.ApiCreateUserSessionRequest.parse(json);
|
||||
|
||||
// fetch client signing key from db
|
||||
const project = await projectsCollection.findOne({
|
||||
_id: projectId
|
||||
});
|
||||
if (!project) {
|
||||
return Response.json({ error: 'Project not found' }, { status: 404 });
|
||||
}
|
||||
const clientSigningKey = project.secret;
|
||||
|
||||
// verify client signing key
|
||||
let verified;
|
||||
try {
|
||||
verified = await jwtVerify<{
|
||||
userId: string;
|
||||
userName?: string;
|
||||
}>(parsedRequest.userDataJwt, new TextEncoder().encode(clientSigningKey));
|
||||
} catch (e) {
|
||||
return Response.json({ error: 'Invalid jwt' }, { status: 403 });
|
||||
}
|
||||
|
||||
// create new user session
|
||||
const session: z.infer<typeof Session> = {
|
||||
userId: verified.payload.userId,
|
||||
userName: verified.payload.userName ?? 'Unknown',
|
||||
projectId: projectId
|
||||
};
|
||||
|
||||
// Create and sign JWT
|
||||
const token = await new SignJWT(session)
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('24h')
|
||||
.sign(new TextEncoder().encode(process.env.CHAT_WIDGET_SESSION_JWT_SECRET));
|
||||
|
||||
const response: z.infer<typeof apiV1.ApiCreateGuestSessionResponse> = {
|
||||
sessionId: token,
|
||||
};
|
||||
|
||||
return Response.json(response);
|
||||
});
|
||||
}
|
||||
62
apps/rowboat/app/api/v1/utils.ts
Normal file
62
apps/rowboat/app/api/v1/utils.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { NextRequest } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { jwtVerify } from "jose";
|
||||
import { projectsCollection } from "@/app/lib/mongodb";
|
||||
|
||||
export const Session = z.object({
|
||||
userId: z.string(),
|
||||
userName: z.string(),
|
||||
projectId: z.string(),
|
||||
});
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
export async function clientIdCheck(req: NextRequest, handler: (projectId: string) => Promise<Response>): Promise<Response> {
|
||||
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
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
export async function authCheck(req: NextRequest, handler: (session: z.infer<typeof Session>) => Promise<Response>): Promise<Response> {
|
||||
const authHeader = req.headers.get('Authorization');
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return Response.json({ error: "Authorization header must be a Bearer token" }, { status: 400 });
|
||||
}
|
||||
const token = authHeader.split(' ')[1];
|
||||
if (!token) {
|
||||
return Response.json({ error: "Missing session token in request" }, { status: 400 });
|
||||
}
|
||||
|
||||
let session;
|
||||
try {
|
||||
session = await jwtVerify(token, new TextEncoder().encode(process.env.CHAT_WIDGET_SESSION_JWT_SECRET));
|
||||
} catch (error) {
|
||||
return Response.json({ error: "Invalid session token" }, { status: 403 });
|
||||
}
|
||||
|
||||
return await handler(session.payload as z.infer<typeof Session>);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue