You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1252 lines
37 KiB

11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
  1. process.env.NODE_ENV === "development"
  2. ? require("dotenv").config({ path: `.env.${process.env.NODE_ENV}` })
  3. : require("dotenv").config();
  4. const { viewLocalFiles, normalizePath, isWithin } = require("../utils/files");
  5. const { purgeDocument, purgeFolder } = require("../utils/files/purgeDocument");
  6. const { getVectorDbClass } = require("../utils/helpers");
  7. const { updateENV, dumpENV } = require("../utils/helpers/updateENV");
  8. const {
  9. reqBody,
  10. makeJWT,
  11. userFromSession,
  12. multiUserMode,
  13. queryParams,
  14. } = require("../utils/http");
  15. const { handleAssetUpload, handlePfpUpload } = require("../utils/files/multer");
  16. const { v4 } = require("uuid");
  17. const { SystemSettings } = require("../models/systemSettings");
  18. const { User } = require("../models/user");
  19. const { validatedRequest } = require("../utils/middleware/validatedRequest");
  20. const fs = require("fs");
  21. const path = require("path");
  22. const {
  23. getDefaultFilename,
  24. determineLogoFilepath,
  25. fetchLogo,
  26. validFilename,
  27. renameLogoFile,
  28. removeCustomLogo,
  29. LOGO_FILENAME,
  30. isDefaultFilename,
  31. } = require("../utils/files/logo");
  32. const { Telemetry } = require("../models/telemetry");
  33. const { WelcomeMessages } = require("../models/welcomeMessages");
  34. const { ApiKey } = require("../models/apiKeys");
  35. const { getCustomModels } = require("../utils/helpers/customModels");
  36. const { WorkspaceChats } = require("../models/workspaceChats");
  37. const {
  38. flexUserRoleValid,
  39. ROLES,
  40. isMultiUserSetup,
  41. } = require("../utils/middleware/multiUserProtected");
  42. const { fetchPfp, determinePfpFilepath } = require("../utils/files/pfp");
  43. const { exportChatsAsType } = require("../utils/helpers/chat/convertTo");
  44. const { EventLogs } = require("../models/eventLogs");
  45. const { CollectorApi } = require("../utils/collectorApi");
  46. const {
  47. recoverAccount,
  48. resetPassword,
  49. generateRecoveryCodes,
  50. } = require("../utils/PasswordRecovery");
  51. const { SlashCommandPresets } = require("../models/slashCommandsPresets");
  52. const { EncryptionManager } = require("../utils/EncryptionManager");
  53. const { BrowserExtensionApiKey } = require("../models/browserExtensionApiKey");
  54. const {
  55. chatHistoryViewable,
  56. } = require("../utils/middleware/chatHistoryViewable");
  57. const { simpleSSOEnabled } = require("../utils/middleware/simpleSSOEnabled");
  58. const { TemporaryAuthToken } = require("../models/temporaryAuthToken");
  59. const { DeptUsers } = require("../models/deptUsers");
  60. function systemEndpoints(app) {
  61. if (!app) return;
  62. app.get("/ping", (_, response) => {
  63. response.status(200).json({ online: true });
  64. });
  65. app.get("/migrate", async (_, response) => {
  66. response.sendStatus(200);
  67. });
  68. app.get("/env-dump", async (_, response) => {
  69. if (process.env.NODE_ENV !== "production")
  70. return response.sendStatus(200).end();
  71. dumpENV();
  72. response.sendStatus(200).end();
  73. });
  74. app.get("/setup-complete", async (_, response) => {
  75. try {
  76. const results = await SystemSettings.currentSettings();
  77. response.status(200).json({ results });
  78. } catch (e) {
  79. console.error(e.message, e);
  80. response.sendStatus(500).end();
  81. }
  82. });
  83. app.get(
  84. "/system/check-token",
  85. [validatedRequest],
  86. async (request, response) => {
  87. try {
  88. if (multiUserMode(response)) {
  89. const user = await userFromSession(request, response);
  90. if (!user || user.suspended) {
  91. response.sendStatus(403).end();
  92. return;
  93. }
  94. response.sendStatus(200).end();
  95. return;
  96. }
  97. response.sendStatus(200).end();
  98. } catch (e) {
  99. console.error(e.message, e);
  100. response.sendStatus(500).end();
  101. }
  102. }
  103. );
  104. app.post("/request-token", async (request, response) => {
  105. try {
  106. const bcrypt = require("bcrypt");
  107. // 判断单用户还是多用户
  108. if (await SystemSettings.isMultiUserMode()) {
  109. const { username, password } = reqBody(request);
  110. // 通过一个实例查询用户去了,用户实体字段如下
  111. // [
  112. // // Used for generic updates so we can validate keys in request body
  113. // "username",
  114. // "password",
  115. // "pfpFilename",
  116. // "role",
  117. // "suspended",
  118. // "dailyMessageLimit",
  119. // ],
  120. const existingUser = await User._get({ username: String(username) });
  121. if (!existingUser) {
  122. await EventLogs.logEvent(
  123. "failed_login_invalid_username",
  124. {
  125. ip: request.ip || "Unknown IP",
  126. username: username || "Unknown user",
  127. },
  128. existingUser?.id
  129. );
  130. response.status(200).json({
  131. user: null,
  132. valid: false,
  133. token: null,
  134. message: "[001] Invalid login credentials.",
  135. });
  136. return;
  137. }
  138. if (!bcrypt.compareSync(String(password), existingUser.password)) {
  139. await EventLogs.logEvent(
  140. "failed_login_invalid_password",
  141. {
  142. ip: request.ip || "Unknown IP",
  143. username: username || "Unknown user",
  144. },
  145. existingUser?.id
  146. );
  147. response.status(200).json({
  148. user: null,
  149. valid: false,
  150. token: null,
  151. message: "[002] Invalid login credentials.",
  152. });
  153. return;
  154. }
  155. if (existingUser.suspended) {
  156. await EventLogs.logEvent(
  157. "failed_login_account_suspended",
  158. {
  159. ip: request.ip || "Unknown IP",
  160. username: username || "Unknown user",
  161. },
  162. existingUser?.id
  163. );
  164. response.status(200).json({
  165. user: null,
  166. valid: false,
  167. token: null,
  168. message: "[004] Account suspended by admin.",
  169. });
  170. return;
  171. }
  172. await Telemetry.sendTelemetry(
  173. "login_event",
  174. { multiUserMode: false },
  175. existingUser?.id
  176. );
  177. await EventLogs.logEvent(
  178. "login_event",
  179. {
  180. ip: request.ip || "Unknown IP",
  181. username: existingUser.username || "Unknown user",
  182. },
  183. existingUser?.id
  184. );
  185. // Check if the user has seen the recovery codes
  186. if (!existingUser.seen_recovery_codes) {
  187. const plainTextCodes = await generateRecoveryCodes(existingUser.id);
  188. // Return recovery codes to frontend
  189. response.status(200).json({
  190. valid: true,
  191. user: User.filterFields(existingUser),
  192. token: makeJWT(
  193. { id: existingUser.id, username: existingUser.username },
  194. "30d"
  195. ),
  196. message: null,
  197. recoveryCodes: plainTextCodes,
  198. });
  199. return;
  200. }
  201. response.status(200).json({
  202. valid: true,
  203. user: User.filterFields(existingUser),
  204. token: makeJWT(
  205. { id: existingUser.id, username: existingUser.username },
  206. "30d"
  207. ),
  208. message: null,
  209. });
  210. return;
  211. } else {
  212. const { password } = reqBody(request);
  213. if (
  214. !bcrypt.compareSync(
  215. password,
  216. bcrypt.hashSync(process.env.AUTH_TOKEN, 10)
  217. )
  218. ) {
  219. await EventLogs.logEvent("failed_login_invalid_password", {
  220. ip: request.ip || "Unknown IP",
  221. multiUserMode: false,
  222. });
  223. response.status(401).json({
  224. valid: false,
  225. token: null,
  226. message: "[003] Invalid password provided",
  227. });
  228. return;
  229. }
  230. await Telemetry.sendTelemetry("login_event", { multiUserMode: false });
  231. await EventLogs.logEvent("login_event", {
  232. ip: request.ip || "Unknown IP",
  233. multiUserMode: false,
  234. });
  235. response.status(200).json({
  236. valid: true,
  237. token: makeJWT(
  238. { p: new EncryptionManager().encrypt(password) },
  239. "30d"
  240. ),
  241. message: null,
  242. });
  243. }
  244. } catch (e) {
  245. console.error(e.message, e);
  246. response.sendStatus(500).end();
  247. }
  248. });
  249. app.get(
  250. "/request-token/sso/simple",
  251. [simpleSSOEnabled],
  252. async (request, response) => {
  253. const { token: tempAuthToken } = request.query;
  254. const { sessionToken, token, error } =
  255. await TemporaryAuthToken.validate(tempAuthToken);
  256. if (error) {
  257. await EventLogs.logEvent("failed_login_invalid_temporary_auth_token", {
  258. ip: request.ip || "Unknown IP",
  259. multiUserMode: true,
  260. });
  261. return response.status(401).json({
  262. valid: false,
  263. token: null,
  264. message: `[001] An error occurred while validating the token: ${error}`,
  265. });
  266. }
  267. await Telemetry.sendTelemetry(
  268. "login_event",
  269. { multiUserMode: true },
  270. token.user.id
  271. );
  272. await EventLogs.logEvent(
  273. "login_event",
  274. {
  275. ip: request.ip || "Unknown IP",
  276. username: token.user.username || "Unknown user",
  277. },
  278. token.user.id
  279. );
  280. response.status(200).json({
  281. valid: true,
  282. user: User.filterFields(token.user),
  283. token: sessionToken,
  284. message: null,
  285. });
  286. }
  287. );
  288. app.post(
  289. "/system/recover-account",
  290. [isMultiUserSetup],
  291. async (request, response) => {
  292. try {
  293. const { username, recoveryCodes } = reqBody(request);
  294. const { success, resetToken, error } = await recoverAccount(
  295. username,
  296. recoveryCodes
  297. );
  298. if (success) {
  299. response.status(200).json({ success, resetToken });
  300. } else {
  301. response.status(400).json({ success, message: error });
  302. }
  303. } catch (error) {
  304. console.error("Error recovering account:", error);
  305. response
  306. .status(500)
  307. .json({ success: false, message: "Internal server error" });
  308. }
  309. }
  310. );
  311. app.post(
  312. "/system/reset-password",
  313. [isMultiUserSetup],
  314. async (request, response) => {
  315. try {
  316. const { token, newPassword, confirmPassword } = reqBody(request);
  317. const { success, message, error } = await resetPassword(
  318. token,
  319. newPassword,
  320. confirmPassword
  321. );
  322. if (success) {
  323. response.status(200).json({ success, message });
  324. } else {
  325. response.status(400).json({ success, error });
  326. }
  327. } catch (error) {
  328. console.error("Error resetting password:", error);
  329. response.status(500).json({ success: false, message: error.message });
  330. }
  331. }
  332. );
  333. app.get(
  334. "/system/system-vectors",
  335. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  336. async (request, response) => {
  337. try {
  338. const query = queryParams(request);
  339. const VectorDb = getVectorDbClass();
  340. const vectorCount = !!query.slug
  341. ? await VectorDb.namespaceCount(query.slug)
  342. : await VectorDb.totalVectors();
  343. response.status(200).json({ vectorCount });
  344. } catch (e) {
  345. console.error(e.message, e);
  346. response.sendStatus(500).end();
  347. }
  348. }
  349. );
  350. app.delete(
  351. "/system/remove-document",
  352. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  353. async (request, response) => {
  354. try {
  355. const { name } = reqBody(request);
  356. await purgeDocument(name);
  357. response.sendStatus(200).end();
  358. } catch (e) {
  359. console.error(e.message, e);
  360. response.sendStatus(500).end();
  361. }
  362. }
  363. );
  364. app.delete(
  365. "/system/remove-documents",
  366. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  367. async (request, response) => {
  368. try {
  369. const { names } = reqBody(request);
  370. for await (const name of names) await purgeDocument(name);
  371. response.sendStatus(200).end();
  372. } catch (e) {
  373. console.error(e.message, e);
  374. response.sendStatus(500).end();
  375. }
  376. }
  377. );
  378. app.delete(
  379. "/system/remove-folder",
  380. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  381. async (request, response) => {
  382. try {
  383. const { name } = reqBody(request);
  384. await purgeFolder(name);
  385. response.sendStatus(200).end();
  386. } catch (e) {
  387. console.error(e.message, e);
  388. response.sendStatus(500).end();
  389. }
  390. }
  391. );
  392. // app.get(
  393. // "/system/local-files",
  394. // [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  395. // async (_, response) => {
  396. // try {
  397. // const localFiles = await viewLocalFiles();
  398. // response.status(200).json({ localFiles });
  399. // } catch (e) {
  400. // console.error(e.message, e);
  401. // response.sendStatus(500).end();
  402. // }
  403. // }
  404. // );
  405. app.get(
  406. "/system/local-files",
  407. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  408. async (request, response) => {
  409. const currUser = await userFromSession(request, response);
  410. const deptusers = DeptUsers.get({ userId: currUser.id });
  411. const deptId = (await deptusers).deptUser.deptId;
  412. try {
  413. const localFiles = await viewLocalFiles(deptId);
  414. response.status(200).json({ localFiles });
  415. } catch (e) {
  416. console.error(e.message, e);
  417. response.sendStatus(500).end();
  418. }
  419. }
  420. );
  421. app.get(
  422. "/system/document-processing-status",
  423. [validatedRequest],
  424. async (_, response) => {
  425. try {
  426. const online = await new CollectorApi().online();
  427. response.sendStatus(online ? 200 : 503);
  428. } catch (e) {
  429. console.error(e.message, e);
  430. response.sendStatus(500).end();
  431. }
  432. }
  433. );
  434. app.get(
  435. "/system/accepted-document-types",
  436. [validatedRequest],
  437. async (_, response) => {
  438. try {
  439. const types = await new CollectorApi().acceptedFileTypes();
  440. if (!types) {
  441. response.sendStatus(404).end();
  442. return;
  443. }
  444. response.status(200).json({ types });
  445. } catch (e) {
  446. console.error(e.message, e);
  447. response.sendStatus(500).end();
  448. }
  449. }
  450. );
  451. app.post(
  452. "/system/update-env",
  453. [validatedRequest, flexUserRoleValid([ROLES.admin])],
  454. async (request, response) => {
  455. try {
  456. const body = reqBody(request);
  457. const { newValues, error } = await updateENV(
  458. body,
  459. false,
  460. response?.locals?.user?.id
  461. );
  462. response.status(200).json({ newValues, error });
  463. } catch (e) {
  464. console.error(e.message, e);
  465. response.sendStatus(500).end();
  466. }
  467. }
  468. );
  469. app.post(
  470. "/system/update-password",
  471. [validatedRequest],
  472. async (request, response) => {
  473. try {
  474. // Cannot update password in multi - user mode.
  475. if (multiUserMode(response)) {
  476. response.sendStatus(401).end();
  477. return;
  478. }
  479. let error = null;
  480. const { usePassword, newPassword } = reqBody(request);
  481. if (!usePassword) {
  482. // Password is being disabled so directly unset everything to bypass validation.
  483. process.env.AUTH_TOKEN = "";
  484. process.env.JWT_SECRET = "";
  485. } else {
  486. error = await updateENV(
  487. {
  488. AuthToken: newPassword,
  489. JWTSecret: v4(),
  490. },
  491. true
  492. )?.error;
  493. }
  494. response.status(200).json({ success: !error, error });
  495. } catch (e) {
  496. console.error(e.message, e);
  497. response.sendStatus(500).end();
  498. }
  499. }
  500. );
  501. app.post(
  502. "/system/enable-multi-user",
  503. [validatedRequest],
  504. async (request, response) => {
  505. try {
  506. if (response.locals.multiUserMode) {
  507. response.status(200).json({
  508. success: false,
  509. error: "Multi-user mode is already enabled.",
  510. });
  511. return;
  512. }
  513. const { username, password } = reqBody(request);
  514. const { user, error } = await User.create({
  515. username,
  516. password,
  517. role: ROLES.admin,
  518. });
  519. if (error || !user) {
  520. response.status(400).json({
  521. success: false,
  522. error: error || "Failed to enable multi-user mode.",
  523. });
  524. return;
  525. }
  526. await SystemSettings._updateSettings({
  527. multi_user_mode: true,
  528. });
  529. await BrowserExtensionApiKey.migrateApiKeysToMultiUser(user.id);
  530. await updateENV(
  531. {
  532. JWTSecret: process.env.JWT_SECRET || v4(),
  533. },
  534. true
  535. );
  536. await Telemetry.sendTelemetry("enabled_multi_user_mode", {
  537. multiUserMode: true,
  538. });
  539. await EventLogs.logEvent("multi_user_mode_enabled", {}, user?.id);
  540. response.status(200).json({ success: !!user, error });
  541. } catch (e) {
  542. await User.delete({});
  543. await SystemSettings._updateSettings({
  544. multi_user_mode: false,
  545. });
  546. console.error(e.message, e);
  547. response.sendStatus(500).end();
  548. }
  549. }
  550. );
  551. app.get("/system/multi-user-mode", async (_, response) => {
  552. try {
  553. const multiUserMode = await SystemSettings.isMultiUserMode();
  554. response.status(200).json({ multiUserMode });
  555. } catch (e) {
  556. console.error(e.message, e);
  557. response.sendStatus(500).end();
  558. }
  559. });
  560. app.get("/system/logo", async function (request, response) {
  561. try {
  562. const darkMode =
  563. !request?.query?.theme || request?.query?.theme === "default";
  564. const defaultFilename = getDefaultFilename(darkMode);
  565. const logoPath = await determineLogoFilepath(defaultFilename);
  566. const { found, buffer, size, mime } = fetchLogo(logoPath);
  567. if (!found) {
  568. response.sendStatus(204).end();
  569. return;
  570. }
  571. const currentLogoFilename = await SystemSettings.currentLogoFilename();
  572. response.writeHead(200, {
  573. "Access-Control-Expose-Headers":
  574. "Content-Disposition,X-Is-Custom-Logo,Content-Type,Content-Length",
  575. "Content-Type": mime || "image/png",
  576. "Content-Disposition": `attachment; filename=${path.basename(
  577. logoPath
  578. )}`,
  579. "Content-Length": size,
  580. "X-Is-Custom-Logo":
  581. currentLogoFilename !== null &&
  582. currentLogoFilename !== defaultFilename &&
  583. !isDefaultFilename(currentLogoFilename),
  584. });
  585. response.end(Buffer.from(buffer, "base64"));
  586. return;
  587. } catch (error) {
  588. console.error("Error processing the logo request:", error);
  589. response.status(500).json({ message: "Internal server error" });
  590. }
  591. });
  592. app.get("/system/footer-data", [validatedRequest], async (_, response) => {
  593. try {
  594. const footerData =
  595. (await SystemSettings.get({ label: "footer_data" }))?.value ??
  596. JSON.stringify([]);
  597. response.status(200).json({ footerData: footerData });
  598. } catch (error) {
  599. console.error("Error fetching footer data:", error);
  600. response.status(500).json({ message: "Internal server error" });
  601. }
  602. });
  603. app.get("/system/support-email", [validatedRequest], async (_, response) => {
  604. try {
  605. const supportEmail =
  606. (
  607. await SystemSettings.get({
  608. label: "support_email",
  609. })
  610. )?.value ?? null;
  611. response.status(200).json({ supportEmail: supportEmail });
  612. } catch (error) {
  613. console.error("Error fetching support email:", error);
  614. response.status(500).json({ message: "Internal server error" });
  615. }
  616. });
  617. // No middleware protection in order to get this on the login page
  618. app.get("/system/custom-app-name", async (_, response) => {
  619. try {
  620. const customAppName =
  621. (
  622. await SystemSettings.get({
  623. label: "custom_app_name",
  624. })
  625. )?.value ?? null;
  626. response.status(200).json({ customAppName: customAppName });
  627. } catch (error) {
  628. console.error("Error fetching custom app name:", error);
  629. response.status(500).json({ message: "Internal server error" });
  630. }
  631. });
  632. app.get(
  633. "/system/pfp/:id",
  634. [validatedRequest, flexUserRoleValid([ROLES.all])],
  635. async function (request, response) {
  636. try {
  637. const { id } = request.params;
  638. if (response.locals?.user?.id !== Number(id))
  639. return response.sendStatus(204).end();
  640. const pfpPath = await determinePfpFilepath(id);
  641. if (!pfpPath) return response.sendStatus(204).end();
  642. const { found, buffer, size, mime } = fetchPfp(pfpPath);
  643. if (!found) return response.sendStatus(204).end();
  644. response.writeHead(200, {
  645. "Content-Type": mime || "image/png",
  646. "Content-Disposition": `attachment; filename=${path.basename(pfpPath)}`,
  647. "Content-Length": size,
  648. });
  649. response.end(Buffer.from(buffer, "base64"));
  650. return;
  651. } catch (error) {
  652. console.error("Error processing the logo request:", error);
  653. response.status(500).json({ message: "Internal server error" });
  654. }
  655. }
  656. );
  657. app.post(
  658. "/system/upload-pfp",
  659. [validatedRequest, flexUserRoleValid([ROLES.all]), handlePfpUpload],
  660. async function (request, response) {
  661. try {
  662. const user = await userFromSession(request, response);
  663. const uploadedFileName = request.randomFileName;
  664. console.log("Uploaded File::::", uploadedFileName);
  665. if (!uploadedFileName) {
  666. return response.status(400).json({ message: "File upload failed." });
  667. }
  668. const userRecord = await User.get({ id: user.id });
  669. const oldPfpFilename = userRecord.pfpFilename;
  670. if (oldPfpFilename) {
  671. const storagePath = path.join(__dirname, "../storage/assets/pfp");
  672. const oldPfpPath = path.join(
  673. storagePath,
  674. normalizePath(userRecord.pfpFilename)
  675. );
  676. if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath)))
  677. throw new Error("Invalid path name");
  678. if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath);
  679. }
  680. const { success, error } = await User.update(user.id, {
  681. pfpFilename: uploadedFileName,
  682. });
  683. return response.status(success ? 200 : 500).json({
  684. message: success
  685. ? "Profile picture uploaded successfully."
  686. : error || "Failed to update with new profile picture.",
  687. });
  688. } catch (error) {
  689. console.error("Error processing the profile picture upload:", error);
  690. response.status(500).json({ message: "Internal server error" });
  691. }
  692. }
  693. );
  694. app.delete(
  695. "/system/remove-pfp",
  696. [validatedRequest, flexUserRoleValid([ROLES.all])],
  697. async function (request, response) {
  698. try {
  699. const user = await userFromSession(request, response);
  700. const userRecord = await User.get({ id: user.id });
  701. const oldPfpFilename = userRecord.pfpFilename;
  702. if (oldPfpFilename) {
  703. const storagePath = path.join(__dirname, "../storage/assets/pfp");
  704. const oldPfpPath = path.join(
  705. storagePath,
  706. normalizePath(oldPfpFilename)
  707. );
  708. if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath)))
  709. throw new Error("Invalid path name");
  710. if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath);
  711. }
  712. const { success, error } = await User.update(user.id, {
  713. pfpFilename: null,
  714. });
  715. return response.status(success ? 200 : 500).json({
  716. message: success
  717. ? "Profile picture removed successfully."
  718. : error || "Failed to remove profile picture.",
  719. });
  720. } catch (error) {
  721. console.error("Error processing the profile picture removal:", error);
  722. response.status(500).json({ message: "Internal server error" });
  723. }
  724. }
  725. );
  726. app.post(
  727. "/system/upload-logo",
  728. [
  729. validatedRequest,
  730. flexUserRoleValid([ROLES.admin, ROLES.manager]),
  731. handleAssetUpload,
  732. ],
  733. async (request, response) => {
  734. if (!request?.file || !request?.file.originalname) {
  735. return response.status(400).json({ message: "No logo file provided." });
  736. }
  737. if (!validFilename(request.file.originalname)) {
  738. return response.status(400).json({
  739. message: "Invalid file name. Please choose a different file.",
  740. });
  741. }
  742. try {
  743. const newFilename = await renameLogoFile(request.file.originalname);
  744. const existingLogoFilename = await SystemSettings.currentLogoFilename();
  745. await removeCustomLogo(existingLogoFilename);
  746. const { success, error } = await SystemSettings._updateSettings({
  747. logo_filename: newFilename,
  748. });
  749. return response.status(success ? 200 : 500).json({
  750. message: success
  751. ? "Logo uploaded successfully."
  752. : error || "Failed to update with new logo.",
  753. });
  754. } catch (error) {
  755. console.error("Error processing the logo upload:", error);
  756. response.status(500).json({ message: "Error uploading the logo." });
  757. }
  758. }
  759. );
  760. app.get("/system/is-default-logo", async (_, response) => {
  761. try {
  762. const currentLogoFilename = await SystemSettings.currentLogoFilename();
  763. const isDefaultLogo = currentLogoFilename === LOGO_FILENAME;
  764. response.status(200).json({ isDefaultLogo });
  765. } catch (error) {
  766. console.error("Error processing the logo request:", error);
  767. response.status(500).json({ message: "Internal server error" });
  768. }
  769. });
  770. app.get(
  771. "/system/remove-logo",
  772. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  773. async (_request, response) => {
  774. try {
  775. const currentLogoFilename = await SystemSettings.currentLogoFilename();
  776. await removeCustomLogo(currentLogoFilename);
  777. const { success, error } = await SystemSettings._updateSettings({
  778. logo_filename: LOGO_FILENAME,
  779. });
  780. return response.status(success ? 200 : 500).json({
  781. message: success
  782. ? "Logo removed successfully."
  783. : error || "Failed to update with new logo.",
  784. });
  785. } catch (error) {
  786. console.error("Error processing the logo removal:", error);
  787. response.status(500).json({ message: "Error removing the logo." });
  788. }
  789. }
  790. );
  791. app.get(
  792. "/system/welcome-messages",
  793. [validatedRequest, flexUserRoleValid([ROLES.all])],
  794. async function (_, response) {
  795. try {
  796. const welcomeMessages = await WelcomeMessages.getMessages();
  797. response.status(200).json({ success: true, welcomeMessages });
  798. } catch (error) {
  799. console.error("Error fetching welcome messages:", error);
  800. response
  801. .status(500)
  802. .json({ success: false, message: "Internal server error" });
  803. }
  804. }
  805. );
  806. app.post(
  807. "/system/set-welcome-messages",
  808. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  809. async (request, response) => {
  810. try {
  811. const { messages = [] } = reqBody(request);
  812. if (!Array.isArray(messages)) {
  813. return response.status(400).json({
  814. success: false,
  815. message: "Invalid message format. Expected an array of messages.",
  816. });
  817. }
  818. await WelcomeMessages.saveAll(messages);
  819. return response.status(200).json({
  820. success: true,
  821. message: "Welcome messages saved successfully.",
  822. });
  823. } catch (error) {
  824. console.error("Error processing the welcome messages:", error);
  825. response.status(500).json({
  826. success: true,
  827. message: "Error saving the welcome messages.",
  828. });
  829. }
  830. }
  831. );
  832. app.get("/system/api-keys", [validatedRequest], async (_, response) => {
  833. try {
  834. if (response.locals.multiUserMode) {
  835. return response.sendStatus(401).end();
  836. }
  837. const apiKeys = await ApiKey.where({});
  838. return response.status(200).json({
  839. apiKeys,
  840. error: null,
  841. });
  842. } catch (error) {
  843. console.error(error);
  844. response.status(500).json({
  845. apiKey: null,
  846. error: "Could not find an API Key.",
  847. });
  848. }
  849. });
  850. app.post(
  851. "/system/generate-api-key",
  852. [validatedRequest],
  853. async (_, response) => {
  854. try {
  855. if (response.locals.multiUserMode) {
  856. return response.sendStatus(401).end();
  857. }
  858. const { apiKey, error } = await ApiKey.create();
  859. await Telemetry.sendTelemetry("api_key_created");
  860. await EventLogs.logEvent(
  861. "api_key_created",
  862. {},
  863. response?.locals?.user?.id
  864. );
  865. return response.status(200).json({
  866. apiKey,
  867. error,
  868. });
  869. } catch (error) {
  870. console.error(error);
  871. response.status(500).json({
  872. apiKey: null,
  873. error: "Error generating api key.",
  874. });
  875. }
  876. }
  877. );
  878. app.delete("/system/api-key", [validatedRequest], async (_, response) => {
  879. try {
  880. if (response.locals.multiUserMode) {
  881. return response.sendStatus(401).end();
  882. }
  883. await ApiKey.delete();
  884. await EventLogs.logEvent(
  885. "api_key_deleted",
  886. { deletedBy: response.locals?.user?.username },
  887. response?.locals?.user?.id
  888. );
  889. return response.status(200).end();
  890. } catch (error) {
  891. console.error(error);
  892. response.status(500).end();
  893. }
  894. });
  895. app.post(
  896. "/system/custom-models",
  897. [validatedRequest, flexUserRoleValid([ROLES.admin])],
  898. async (request, response) => {
  899. try {
  900. const { provider, apiKey = null, basePath = null } = reqBody(request);
  901. const { models, error } = await getCustomModels(
  902. provider,
  903. apiKey,
  904. basePath
  905. );
  906. return response.status(200).json({
  907. models,
  908. error,
  909. });
  910. } catch (error) {
  911. console.error(error);
  912. response.status(500).end();
  913. }
  914. }
  915. );
  916. app.post(
  917. "/system/event-logs",
  918. [validatedRequest, flexUserRoleValid([ROLES.admin])],
  919. async (request, response) => {
  920. try {
  921. const { offset = 0, limit = 10 } = reqBody(request);
  922. const logs = await EventLogs.whereWithData({}, limit, offset * limit, {
  923. id: "desc",
  924. });
  925. const totalLogs = await EventLogs.count();
  926. const hasPages = totalLogs > (offset + 1) * limit;
  927. response.status(200).json({ logs: logs, hasPages, totalLogs });
  928. } catch (e) {
  929. console.error(e);
  930. response.sendStatus(500).end();
  931. }
  932. }
  933. );
  934. app.delete(
  935. "/system/event-logs",
  936. [validatedRequest, flexUserRoleValid([ROLES.admin])],
  937. async (_, response) => {
  938. try {
  939. await EventLogs.delete();
  940. await EventLogs.logEvent(
  941. "event_logs_cleared",
  942. {},
  943. response?.locals?.user?.id
  944. );
  945. response.json({ success: true });
  946. } catch (e) {
  947. console.error(e);
  948. response.sendStatus(500).end();
  949. }
  950. }
  951. );
  952. app.post(
  953. "/system/workspace-chats",
  954. [
  955. chatHistoryViewable,
  956. validatedRequest,
  957. flexUserRoleValid([ROLES.admin, ROLES.manager]),
  958. ],
  959. async (request, response) => {
  960. try {
  961. const { offset = 0, limit = 20 } = reqBody(request);
  962. const chats = await WorkspaceChats.whereWithData(
  963. {},
  964. limit,
  965. offset * limit,
  966. { id: "desc" }
  967. );
  968. const totalChats = await WorkspaceChats.count();
  969. const hasPages = totalChats > (offset + 1) * limit;
  970. response.status(200).json({ chats: chats, hasPages, totalChats });
  971. } catch (e) {
  972. console.error(e);
  973. response.sendStatus(500).end();
  974. }
  975. }
  976. );
  977. app.delete(
  978. "/system/workspace-chats/:id",
  979. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  980. async (request, response) => {
  981. try {
  982. const { id } = request.params;
  983. Number(id) === -1
  984. ? await WorkspaceChats.delete({}, true)
  985. : await WorkspaceChats.delete({ id: Number(id) });
  986. response.json({ success: true, error: null });
  987. } catch (e) {
  988. console.error(e);
  989. response.sendStatus(500).end();
  990. }
  991. }
  992. );
  993. app.get(
  994. "/system/export-chats",
  995. [
  996. chatHistoryViewable,
  997. validatedRequest,
  998. flexUserRoleValid([ROLES.manager, ROLES.admin]),
  999. ],
  1000. async (request, response) => {
  1001. try {
  1002. const { type = "jsonl", chatType = "workspace" } = request.query;
  1003. const { contentType, data } = await exportChatsAsType(type, chatType);
  1004. await EventLogs.logEvent(
  1005. "exported_chats",
  1006. {
  1007. type,
  1008. chatType,
  1009. },
  1010. response.locals.user?.id
  1011. );
  1012. response.setHeader("Content-Type", contentType);
  1013. response.status(200).send(data);
  1014. } catch (e) {
  1015. console.error(e);
  1016. response.sendStatus(500).end();
  1017. }
  1018. }
  1019. );
  1020. // Used for when a user in multi-user updates their own profile
  1021. // from the UI.
  1022. app.post("/system/user", [validatedRequest], async (request, response) => {
  1023. try {
  1024. const sessionUser = await userFromSession(request, response);
  1025. const { username, password } = reqBody(request);
  1026. const id = Number(sessionUser.id);
  1027. if (!id) {
  1028. response.status(400).json({ success: false, error: "Invalid user ID" });
  1029. return;
  1030. }
  1031. const updates = {};
  1032. if (username) {
  1033. updates.username = User.validations.username(String(username));
  1034. }
  1035. if (password) {
  1036. updates.password = String(password);
  1037. }
  1038. if (Object.keys(updates).length === 0) {
  1039. response
  1040. .status(400)
  1041. .json({ success: false, error: "No updates provided" });
  1042. return;
  1043. }
  1044. const { success, error } = await User.update(id, updates);
  1045. response.status(200).json({ success, error });
  1046. } catch (e) {
  1047. console.error(e);
  1048. response.sendStatus(500).end();
  1049. }
  1050. });
  1051. app.get(
  1052. "/system/slash-command-presets",
  1053. [validatedRequest, flexUserRoleValid([ROLES.all])],
  1054. async (request, response) => {
  1055. try {
  1056. const user = await userFromSession(request, response);
  1057. const userPresets = await SlashCommandPresets.getUserPresets(user?.id);
  1058. response.status(200).json({ presets: userPresets });
  1059. } catch (error) {
  1060. console.error("Error fetching slash command presets:", error);
  1061. response.status(500).json({ message: "Internal server error" });
  1062. }
  1063. }
  1064. );
  1065. app.post(
  1066. "/system/slash-command-presets",
  1067. [validatedRequest, flexUserRoleValid([ROLES.all])],
  1068. async (request, response) => {
  1069. try {
  1070. const user = await userFromSession(request, response);
  1071. const { command, prompt, description } = reqBody(request);
  1072. const presetData = {
  1073. command: SlashCommandPresets.formatCommand(String(command)),
  1074. prompt: String(prompt),
  1075. description: String(description),
  1076. };
  1077. const preset = await SlashCommandPresets.create(user?.id, presetData);
  1078. if (!preset) {
  1079. return response
  1080. .status(500)
  1081. .json({ message: "Failed to create preset" });
  1082. }
  1083. response.status(201).json({ preset });
  1084. } catch (error) {
  1085. console.error("Error creating slash command preset:", error);
  1086. response.status(500).json({ message: "Internal server error" });
  1087. }
  1088. }
  1089. );
  1090. app.post(
  1091. "/system/slash-command-presets/:slashCommandId",
  1092. [validatedRequest, flexUserRoleValid([ROLES.all])],
  1093. async (request, response) => {
  1094. try {
  1095. const user = await userFromSession(request, response);
  1096. const { slashCommandId } = request.params;
  1097. const { command, prompt, description } = reqBody(request);
  1098. // Valid user running owns the preset if user session is valid.
  1099. const ownsPreset = await SlashCommandPresets.get({
  1100. userId: user?.id ?? null,
  1101. id: Number(slashCommandId),
  1102. });
  1103. if (!ownsPreset)
  1104. return response.status(404).json({ message: "Preset not found" });
  1105. const updates = {
  1106. command: SlashCommandPresets.formatCommand(String(command)),
  1107. prompt: String(prompt),
  1108. description: String(description),
  1109. };
  1110. const preset = await SlashCommandPresets.update(
  1111. Number(slashCommandId),
  1112. updates
  1113. );
  1114. if (!preset) return response.sendStatus(422);
  1115. response.status(200).json({ preset: { ...ownsPreset, ...updates } });
  1116. } catch (error) {
  1117. console.error("Error updating slash command preset:", error);
  1118. response.status(500).json({ message: "Internal server error" });
  1119. }
  1120. }
  1121. );
  1122. app.delete(
  1123. "/system/slash-command-presets/:slashCommandId",
  1124. [validatedRequest, flexUserRoleValid([ROLES.all])],
  1125. async (request, response) => {
  1126. try {
  1127. const { slashCommandId } = request.params;
  1128. const user = await userFromSession(request, response);
  1129. // Valid user running owns the preset if user session is valid.
  1130. const ownsPreset = await SlashCommandPresets.get({
  1131. userId: user?.id ?? null,
  1132. id: Number(slashCommandId),
  1133. });
  1134. if (!ownsPreset)
  1135. return response
  1136. .status(403)
  1137. .json({ message: "Failed to delete preset" });
  1138. await SlashCommandPresets.delete(Number(slashCommandId));
  1139. response.sendStatus(204);
  1140. } catch (error) {
  1141. console.error("Error deleting slash command preset:", error);
  1142. response.status(500).json({ message: "Internal server error" });
  1143. }
  1144. }
  1145. );
  1146. }
  1147. module.exports = { systemEndpoints };