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.

1234 lines
36 KiB

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