Fix admin authorization and harden chat requests
This commit is contained in:
+24
-24
@@ -2,6 +2,8 @@
|
||||
* @file Stellt Chat-Routen für Nachrichten, Streaming, Antwortvarianten, Verzweigungen und Einstellungen bereit.
|
||||
*/
|
||||
import express from "express";
|
||||
import {createTextStreamEvents} from "../services/streamEvents.js";
|
||||
import {asyncHandler} from "../middleware/asyncHandler.js";
|
||||
import multer from "multer";
|
||||
import {requireLogin} from "../middleware/auth.js";
|
||||
import {validateBody} from "../middleware/validate.js";
|
||||
@@ -139,14 +141,14 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
next();
|
||||
});
|
||||
|
||||
router.get("/models", async (req, res) => {
|
||||
router.get("/models", asyncHandler(async (req, res) => {
|
||||
try {
|
||||
return res.json(await modelService.listModels());
|
||||
} catch (error) {
|
||||
const mapped = modelService.mapError(error);
|
||||
return res.status(mapped.status).json(mapped.body);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
router.get("/chats", (req, res) => res.json({chats: repositories.listChats(req.session.user)}));
|
||||
router.get("/chats/search", (req, res) => res.json({chats: repositories.searchChats(req.session.user, req.query.q || "")}));
|
||||
@@ -262,7 +264,7 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
return res.json(result);
|
||||
});
|
||||
|
||||
router.post("/chats/:id/messages/:messageId/edit-resend", validateBody(sendMessageValidator), async (req, res) => {
|
||||
router.post("/chats/:id/messages/:messageId/edit-resend", validateBody(sendMessageValidator), asyncHandler(async (req, res) => {
|
||||
if (req.body?.confirmed !== true) return res.status(400).json({error: "Confirmation is required before creating an edited branch"});
|
||||
const chatId = Number(req.params.id);
|
||||
const targetMessage = repositories.getChatMessage(chatId, Number(req.params.messageId));
|
||||
@@ -285,7 +287,7 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
const mapped = modelService.mapError(error);
|
||||
return res.status(mapped.status).json(mapped.body);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
router.put("/chats/:id/messages/:messageId", validateBody(messageValidator), (req, res) => {
|
||||
const chatId = Number(req.params.id);
|
||||
@@ -446,7 +448,7 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
return res.json({state});
|
||||
});
|
||||
|
||||
router.post("/chat", ...chatRateLimiters, validateBody(sendMessageValidator), async (req, res) => {
|
||||
router.post("/chat", ...chatRateLimiters, validateBody(sendMessageValidator), asyncHandler(async (req, res) => {
|
||||
try {
|
||||
const result = await chatService.sendChatMessage({
|
||||
user: req.session.user,
|
||||
@@ -464,9 +466,9 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
const mapped = modelService.mapError(error);
|
||||
return res.status(mapped.status).json(mapped.body);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
router.post("/chat/stream", ...chatRateLimiters, validateBody(sendMessageValidator), async (req, res) => {
|
||||
router.post("/chat/stream", ...chatRateLimiters, validateBody(sendMessageValidator), asyncHandler(async (req, res) => {
|
||||
const generationController = new AbortController();
|
||||
let generationFinished = false;
|
||||
res.once("close", () => {
|
||||
@@ -493,26 +495,25 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
message: req.validatedBody.message,
|
||||
attachments: req.validatedBody.attachments,
|
||||
model: req.validatedBody.model,
|
||||
onChunk: (chunk, fullReply) => sendEvent("chunk", {chunk, fullReply}),
|
||||
onThinking: (chunk, fullThinking) => sendEvent("thinking", {chunk, fullThinking}),
|
||||
...createTextStreamEvents(sendEvent),
|
||||
webSearch: req.validatedBody.webSearch,
|
||||
reasoning: req.validatedBody.reasoning,
|
||||
onStatus: (status) => sendEvent("status", status),
|
||||
signal: generationController.signal
|
||||
});
|
||||
if (result.error) {
|
||||
sendEvent("error", {error: result.error, ...(result.searchState ? {searchState: result.searchState} : {})});
|
||||
sendEvent("error", {error: result.error, status: result.status, requestId: req.id, ...(result.searchState ? {searchState: result.searchState} : {})});
|
||||
} else {
|
||||
sendEvent("done", {reply: result.reply, searchState: result.searchState, ...(result.sources ? {sources: result.sources} : {})});
|
||||
}
|
||||
} catch (error) {
|
||||
const mapped = modelService.mapError(error);
|
||||
sendEvent("error", mapped.body);
|
||||
sendEvent("error", {...mapped.body, status: mapped.status, requestId: req.id});
|
||||
} finally {
|
||||
generationFinished = true;
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
router.post("/chats/:id/messages/:messageId/versions/:index", (req, res) => {
|
||||
const chatId = Number(req.params.id), messageId = Number(req.params.messageId);
|
||||
@@ -528,7 +529,7 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
res.json({message: chatService.formatChatMessage(repositories.getChatMessage(chatId, messageId))});
|
||||
});
|
||||
|
||||
router.post(["/chats/:id/messages/:messageId/retry", "/chats/:id/messages/:messageId/retry/stream"], ...chatRateLimiters, async (req, res) => {
|
||||
router.post(["/chats/:id/messages/:messageId/retry", "/chats/:id/messages/:messageId/retry/stream"], ...chatRateLimiters, asyncHandler(async (req, res) => {
|
||||
const chatId = Number(req.params.id);
|
||||
const messageId = Number(req.params.messageId);
|
||||
const chat = repositories.getChat(chatId, req.session.user);
|
||||
@@ -559,28 +560,27 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
reasoning: req.body?.reasoning === true,
|
||||
signal: controller.signal,
|
||||
...(streaming ? {
|
||||
onChunk: (chunk, fullReply) => sendEvent("chunk", {chunk, fullReply}),
|
||||
onThinking: (chunk, fullThinking) => sendEvent("thinking", {chunk, fullThinking}),
|
||||
...createTextStreamEvents(sendEvent),
|
||||
onStatus: status => sendEvent("status", status)
|
||||
} : {})
|
||||
});
|
||||
if (streaming) {
|
||||
sendEvent(result.error ? "error" : "done", result.error ? {error: result.error, searchState: result.searchState} : {...result, reply: result.message.content});
|
||||
sendEvent(result.error ? "error" : "done", result.error ? {error: result.error, status: result.status, requestId: req.id, searchState: result.searchState} : {...result, reply: result.message.content});
|
||||
return;
|
||||
}
|
||||
if (result.error) return res.status(result.status).json({error: result.error});
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
const mapped = modelService.mapError(error);
|
||||
if (streaming) { sendEvent("error", mapped.body); return; }
|
||||
if (streaming) { sendEvent("error", {...mapped.body, status: mapped.status, requestId: req.id}); return; }
|
||||
return res.status(mapped.status).json(mapped.body);
|
||||
} finally {
|
||||
finished = true;
|
||||
if (streaming) res.end();
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
router.post("/chats/:id/messages/by-index/:index/retry", ...chatRateLimiters, async (req, res) => {
|
||||
router.post("/chats/:id/messages/by-index/:index/retry", ...chatRateLimiters, asyncHandler(async (req, res) => {
|
||||
const chatId = Number(req.params.id);
|
||||
const chat = repositories.getChat(chatId, req.session.user);
|
||||
if (!chat) return res.status(404).json({error: "Chat not found"});
|
||||
@@ -605,7 +605,7 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
const mapped = modelService.mapError(error);
|
||||
return res.status(mapped.status).json(mapped.body);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
router.get("/exports/chats/:id", (req, res) => {
|
||||
const chatId = Number(req.params.id);
|
||||
@@ -627,7 +627,7 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
});
|
||||
|
||||
router.get("/exports/workspace", (req, res) => res.json({workspace: repositories.exportWorkspace(req.session.user)}));
|
||||
router.get("/exports/workspace/archive", async (req, res) => {
|
||||
router.get("/exports/workspace/archive", asyncHandler(async (req, res) => {
|
||||
try {
|
||||
const workspace = repositories.exportWorkspace(req.session.user);
|
||||
const images = new Map();
|
||||
@@ -642,7 +642,7 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
} catch (error) {
|
||||
return res.status(error.message.includes("250 MB") ? 413 : 500).json({error: "Workspace archive could not be created"});
|
||||
}
|
||||
});
|
||||
}));
|
||||
router.post("/imports/workspace/archive/preview", readArchive, (req, res) => {
|
||||
const validation = validateWorkspaceImport(req.archive.workspace, repositories.exportWorkspace(req.session.user));
|
||||
if (validation.ok) {
|
||||
@@ -651,7 +651,7 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
}
|
||||
return res.status(validation.ok ? 200 : 400).json({ok: validation.ok, errors: validation.errors, warnings: validation.warnings, preview: validation.preview});
|
||||
});
|
||||
router.post("/imports/workspace/archive", readArchive, async (req, res) => {
|
||||
router.post("/imports/workspace/archive", readArchive, asyncHandler(async (req, res) => {
|
||||
const validation = validateWorkspaceImport(req.archive.workspace, repositories.exportWorkspace(req.session.user));
|
||||
if (!validation.ok) return res.status(400).json({error: "Workspace import validation failed", errors: validation.errors});
|
||||
if (validation.preview.duplicates.length && req.body?.allowDuplicates !== "true") {
|
||||
@@ -677,7 +677,7 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
for (const id of restored) await attachmentService.remove(id, req.session.user).catch(() => {});
|
||||
return res.status(error.status || 400).json({error: error.message || "Workspace archive import failed"});
|
||||
}
|
||||
});
|
||||
}));
|
||||
router.post("/imports/workspace/preview", (req, res) => {
|
||||
const validation = validateWorkspaceImport(req.body?.workspace, repositories.exportWorkspace(req.session.user));
|
||||
return res.status(validation.ok ? 200 : 400).json({
|
||||
|
||||
Reference in New Issue
Block a user