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.

1275 lines
38 KiB

11 months ago
11 months ago
11 months ago
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. // console.log("names", names);
  371. // for await (const name of names) await purgeDocument(name);
  372. // response.sendStatus(200).end();
  373. // } catch (e) {
  374. // console.error(e.message, e);
  375. // response.sendStatus(500).end();
  376. // }
  377. // }
  378. // );
  379. app.delete(
  380. "/system/remove-documents",
  381. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  382. async (request, response) => {
  383. try {
  384. const { names } = reqBody(request); // 获取请求体中的文件名列表
  385. console.log("names", names);
  386. // 遍历文件名列表,调用 purgeDocument 方法
  387. for (const name of names) {
  388. await purgeDocument(name);
  389. }
  390. // 返回成功响应
  391. response.status(200).json({ success: true, message: "Documents removed successfully." });
  392. } catch (e) {
  393. console.error(e.message, e);
  394. response.status(500).json({ success: false, error: e.message });
  395. }
  396. }
  397. );
  398. app.delete(
  399. "/system/remove-folder",
  400. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  401. async (request, response) => {
  402. try {
  403. const { name } = reqBody(request);
  404. await purgeFolder(name);
  405. response.sendStatus(200).end();
  406. } catch (e) {
  407. console.error(e.message, e);
  408. response.sendStatus(500).end();
  409. }
  410. }
  411. );
  412. // app.get(
  413. // "/system/local-files",
  414. // [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  415. // async (_, response) => {
  416. // try {
  417. // const localFiles = await viewLocalFiles();
  418. // response.status(200).json({ localFiles });
  419. // } catch (e) {
  420. // console.error(e.message, e);
  421. // response.sendStatus(500).end();
  422. // }
  423. // }
  424. // );
  425. app.get(
  426. "/system/local-files",
  427. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  428. async (request, response) => {
  429. const currUser = await userFromSession(request, response);
  430. const deptusers = DeptUsers.get({ userId: currUser.id });
  431. const deptId = (await deptusers).deptUser.deptId;
  432. try {
  433. const localFiles = await viewLocalFiles(deptId);
  434. response.status(200).json({ localFiles });
  435. } catch (e) {
  436. console.error(e.message, e);
  437. response.sendStatus(500).end();
  438. }
  439. }
  440. );
  441. app.get(
  442. "/system/document-processing-status",
  443. [validatedRequest],
  444. async (_, response) => {
  445. try {
  446. const online = await new CollectorApi().online();
  447. response.sendStatus(online ? 200 : 503);
  448. } catch (e) {
  449. console.error(e.message, e);
  450. response.sendStatus(500).end();
  451. }
  452. }
  453. );
  454. app.get(
  455. "/system/accepted-document-types",
  456. [validatedRequest],
  457. async (_, response) => {
  458. try {
  459. const types = await new CollectorApi().acceptedFileTypes();
  460. if (!types) {
  461. response.sendStatus(404).end();
  462. return;
  463. }
  464. response.status(200).json({ types });
  465. } catch (e) {
  466. console.error(e.message, e);
  467. response.sendStatus(500).end();
  468. }
  469. }
  470. );
  471. app.post(
  472. "/system/update-env",
  473. [validatedRequest, flexUserRoleValid([ROLES.admin])],
  474. async (request, response) => {
  475. try {
  476. const body = reqBody(request);
  477. const { newValues, error } = await updateENV(
  478. body,
  479. false,
  480. response?.locals?.user?.id
  481. );
  482. response.status(200).json({ newValues, error });
  483. } catch (e) {
  484. console.error(e.message, e);
  485. response.sendStatus(500).end();
  486. }
  487. }
  488. );
  489. app.post(
  490. "/system/update-password",
  491. [validatedRequest],
  492. async (request, response) => {
  493. try {
  494. // Cannot update password in multi - user mode.
  495. if (multiUserMode(response)) {
  496. response.sendStatus(401).end();
  497. return;
  498. }
  499. let error = null;
  500. const { usePassword, newPassword } = reqBody(request);
  501. if (!usePassword) {
  502. // Password is being disabled so directly unset everything to bypass validation.
  503. process.env.AUTH_TOKEN = "";
  504. process.env.JWT_SECRET = "";
  505. } else {
  506. error = await updateENV(
  507. {
  508. AuthToken: newPassword,
  509. JWTSecret: v4(),
  510. },
  511. true
  512. )?.error;
  513. }
  514. response.status(200).json({ success: !error, error });
  515. } catch (e) {
  516. console.error(e.message, e);
  517. response.sendStatus(500).end();
  518. }
  519. }
  520. );
  521. app.post(
  522. "/system/enable-multi-user",
  523. [validatedRequest],
  524. async (request, response) => {
  525. try {
  526. if (response.locals.multiUserMode) {
  527. response.status(200).json({
  528. success: false,
  529. error: "Multi-user mode is already enabled.",
  530. });
  531. return;
  532. }
  533. const { username, password } = reqBody(request);
  534. const { user, error } = await User.create({
  535. username,
  536. password,
  537. role: ROLES.admin,
  538. });
  539. if (error || !user) {
  540. response.status(400).json({
  541. success: false,
  542. error: error || "Failed to enable multi-user mode.",
  543. });
  544. return;
  545. }
  546. await SystemSettings._updateSettings({
  547. multi_user_mode: true,
  548. });
  549. await BrowserExtensionApiKey.migrateApiKeysToMultiUser(user.id);
  550. await updateENV(
  551. {
  552. JWTSecret: process.env.JWT_SECRET || v4(),
  553. },
  554. true
  555. );
  556. await Telemetry.sendTelemetry("enabled_multi_user_mode", {
  557. multiUserMode: true,
  558. });
  559. await EventLogs.logEvent("multi_user_mode_enabled", {}, user?.id);
  560. response.status(200).json({ success: !!user, error });
  561. } catch (e) {
  562. await User.delete({});
  563. await SystemSettings._updateSettings({
  564. multi_user_mode: false,
  565. });
  566. console.error(e.message, e);
  567. response.sendStatus(500).end();
  568. }
  569. }
  570. );
  571. app.get("/system/multi-user-mode", async (_, response) => {
  572. try {
  573. const multiUserMode = await SystemSettings.isMultiUserMode();
  574. response.status(200).json({ multiUserMode });
  575. } catch (e) {
  576. console.error(e.message, e);
  577. response.sendStatus(500).end();
  578. }
  579. });
  580. app.get("/system/logo", async function (request, response) {
  581. try {
  582. const darkMode =
  583. !request?.query?.theme || request?.query?.theme === "default";
  584. const defaultFilename = getDefaultFilename(darkMode);
  585. const logoPath = await determineLogoFilepath(defaultFilename);
  586. const { found, buffer, size, mime } = fetchLogo(logoPath);
  587. if (!found) {
  588. response.sendStatus(204).end();
  589. return;
  590. }
  591. const currentLogoFilename = await SystemSettings.currentLogoFilename();
  592. response.writeHead(200, {
  593. "Access-Control-Expose-Headers":
  594. "Content-Disposition,X-Is-Custom-Logo,Content-Type,Content-Length",
  595. "Content-Type": mime || "image/png",
  596. "Content-Disposition": `attachment; filename=${path.basename(
  597. logoPath
  598. )}`,
  599. "Content-Length": size,
  600. "X-Is-Custom-Logo":
  601. currentLogoFilename !== null &&
  602. currentLogoFilename !== defaultFilename &&
  603. !isDefaultFilename(currentLogoFilename),
  604. });
  605. response.end(Buffer.from(buffer, "base64"));
  606. return;
  607. } catch (error) {
  608. console.error("Error processing the logo request:", error);
  609. response.status(500).json({ message: "Internal server error" });
  610. }
  611. });
  612. app.get("/system/footer-data", [validatedRequest], async (_, response) => {
  613. try {
  614. const footerData =
  615. (await SystemSettings.get({ label: "footer_data" }))?.value ??
  616. JSON.stringify([]);
  617. response.status(200).json({ footerData: footerData });
  618. } catch (error) {
  619. console.error("Error fetching footer data:", error);
  620. response.status(500).json({ message: "Internal server error" });
  621. }
  622. });
  623. app.get("/system/support-email", [validatedRequest], async (_, response) => {
  624. try {
  625. const supportEmail =
  626. (
  627. await SystemSettings.get({
  628. label: "support_email",
  629. })
  630. )?.value ?? null;
  631. response.status(200).json({ supportEmail: supportEmail });
  632. } catch (error) {
  633. console.error("Error fetching support email:", error);
  634. response.status(500).json({ message: "Internal server error" });
  635. }
  636. });
  637. // No middleware protection in order to get this on the login page
  638. app.get("/system/custom-app-name", async (_, response) => {
  639. try {
  640. const customAppName =
  641. (
  642. await SystemSettings.get({
  643. label: "custom_app_name",
  644. })
  645. )?.value ?? null;
  646. response.status(200).json({ customAppName: customAppName });
  647. } catch (error) {
  648. console.error("Error fetching custom app name:", error);
  649. response.status(500).json({ message: "Internal server error" });
  650. }
  651. });
  652. app.get(
  653. "/system/pfp/:id",
  654. [validatedRequest, flexUserRoleValid([ROLES.all])],
  655. async function (request, response) {
  656. try {
  657. const { id } = request.params;
  658. if (response.locals?.user?.id !== Number(id))
  659. return response.sendStatus(204).end();
  660. const pfpPath = await determinePfpFilepath(id);
  661. if (!pfpPath) return response.sendStatus(204).end();
  662. const { found, buffer, size, mime } = fetchPfp(pfpPath);
  663. if (!found) return response.sendStatus(204).end();
  664. response.writeHead(200, {
  665. "Content-Type": mime || "image/png",
  666. "Content-Disposition": `attachment; filename=${path.basename(pfpPath)}`,
  667. "Content-Length": size,
  668. });
  669. response.end(Buffer.from(buffer, "base64"));
  670. return;
  671. } catch (error) {
  672. console.error("Error processing the logo request:", error);
  673. response.status(500).json({ message: "Internal server error" });
  674. }
  675. }
  676. );
  677. app.post(
  678. "/system/upload-pfp",
  679. [validatedRequest, flexUserRoleValid([ROLES.all]), handlePfpUpload],
  680. async function (request, response) {
  681. try {
  682. const user = await userFromSession(request, response);
  683. const uploadedFileName = request.randomFileName;
  684. console.log("Uploaded File::::", uploadedFileName);
  685. if (!uploadedFileName) {
  686. return response.status(400).json({ message: "File upload failed." });
  687. }
  688. const userRecord = await User.get({ id: user.id });
  689. const oldPfpFilename = userRecord.pfpFilename;
  690. if (oldPfpFilename) {
  691. const storagePath = path.join(__dirname, "../storage/assets/pfp");
  692. const oldPfpPath = path.join(
  693. storagePath,
  694. normalizePath(userRecord.pfpFilename)
  695. );
  696. if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath)))
  697. throw new Error("Invalid path name");
  698. if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath);
  699. }
  700. const { success, error } = await User.update(user.id, {
  701. pfpFilename: uploadedFileName,
  702. });
  703. return response.status(success ? 200 : 500).json({
  704. message: success
  705. ? "Profile picture uploaded successfully."
  706. : error || "Failed to update with new profile picture.",
  707. });
  708. } catch (error) {
  709. console.error("Error processing the profile picture upload:", error);
  710. response.status(500).json({ message: "Internal server error" });
  711. }
  712. }
  713. );
  714. app.delete(
  715. "/system/remove-pfp",
  716. [validatedRequest, flexUserRoleValid([ROLES.all])],
  717. async function (request, response) {
  718. try {
  719. const user = await userFromSession(request, response);
  720. const userRecord = await User.get({ id: user.id });
  721. const oldPfpFilename = userRecord.pfpFilename;
  722. if (oldPfpFilename) {
  723. const storagePath = path.join(__dirname, "../storage/assets/pfp");
  724. const oldPfpPath = path.join(
  725. storagePath,
  726. normalizePath(oldPfpFilename)
  727. );
  728. if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath)))
  729. throw new Error("Invalid path name");
  730. if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath);
  731. }
  732. const { success, error } = await User.update(user.id, {
  733. pfpFilename: null,
  734. });
  735. return response.status(success ? 200 : 500).json({
  736. message: success
  737. ? "Profile picture removed successfully."
  738. : error || "Failed to remove profile picture.",
  739. });
  740. } catch (error) {
  741. console.error("Error processing the profile picture removal:", error);
  742. response.status(500).json({ message: "Internal server error" });
  743. }
  744. }
  745. );
  746. app.post(
  747. "/system/upload-logo",
  748. [
  749. validatedRequest,
  750. flexUserRoleValid([ROLES.admin, ROLES.manager]),
  751. handleAssetUpload,
  752. ],
  753. async (request, response) => {
  754. if (!request?.file || !request?.file.originalname) {
  755. return response.status(400).json({ message: "No logo file provided." });
  756. }
  757. if (!validFilename(request.file.originalname)) {
  758. return response.status(400).json({
  759. message: "Invalid file name. Please choose a different file.",
  760. });
  761. }
  762. try {
  763. const newFilename = await renameLogoFile(request.file.originalname);
  764. const existingLogoFilename = await SystemSettings.currentLogoFilename();
  765. await removeCustomLogo(existingLogoFilename);
  766. const { success, error } = await SystemSettings._updateSettings({
  767. logo_filename: newFilename,
  768. });
  769. return response.status(success ? 200 : 500).json({
  770. message: success
  771. ? "Logo uploaded successfully."
  772. : error || "Failed to update with new logo.",
  773. });
  774. } catch (error) {
  775. console.error("Error processing the logo upload:", error);
  776. response.status(500).json({ message: "Error uploading the logo." });
  777. }
  778. }
  779. );
  780. app.get("/system/is-default-logo", async (_, response) => {
  781. try {
  782. const currentLogoFilename = await SystemSettings.currentLogoFilename();
  783. const isDefaultLogo = currentLogoFilename === LOGO_FILENAME;
  784. response.status(200).json({ isDefaultLogo });
  785. } catch (error) {
  786. console.error("Error processing the logo request:", error);
  787. response.status(500).json({ message: "Internal server error" });
  788. }
  789. });
  790. app.get(
  791. "/system/remove-logo",
  792. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  793. async (_request, response) => {
  794. try {
  795. const currentLogoFilename = await SystemSettings.currentLogoFilename();
  796. await removeCustomLogo(currentLogoFilename);
  797. const { success, error } = await SystemSettings._updateSettings({
  798. logo_filename: LOGO_FILENAME,
  799. });
  800. return response.status(success ? 200 : 500).json({
  801. message: success
  802. ? "Logo removed successfully."
  803. : error || "Failed to update with new logo.",
  804. });
  805. } catch (error) {
  806. console.error("Error processing the logo removal:", error);
  807. response.status(500).json({ message: "Error removing the logo." });
  808. }
  809. }
  810. );
  811. app.get(
  812. "/system/welcome-messages",
  813. [validatedRequest, flexUserRoleValid([ROLES.all])],
  814. async function (_, response) {
  815. try {
  816. const welcomeMessages = await WelcomeMessages.getMessages();
  817. response.status(200).json({ success: true, welcomeMessages });
  818. } catch (error) {
  819. console.error("Error fetching welcome messages:", error);
  820. response
  821. .status(500)
  822. .json({ success: false, message: "Internal server error" });
  823. }
  824. }
  825. );
  826. app.post(
  827. "/system/set-welcome-messages",
  828. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  829. async (request, response) => {
  830. try {
  831. const { messages = [] } = reqBody(request);
  832. if (!Array.isArray(messages)) {
  833. return response.status(400).json({
  834. success: false,
  835. message: "Invalid message format. Expected an array of messages.",
  836. });
  837. }
  838. await WelcomeMessages.saveAll(messages);
  839. return response.status(200).json({
  840. success: true,
  841. message: "Welcome messages saved successfully.",
  842. });
  843. } catch (error) {
  844. console.error("Error processing the welcome messages:", error);
  845. response.status(500).json({
  846. success: true,
  847. message: "Error saving the welcome messages.",
  848. });
  849. }
  850. }
  851. );
  852. app.get("/system/api-keys", [validatedRequest], async (_, response) => {
  853. try {
  854. if (response.locals.multiUserMode) {
  855. return response.sendStatus(401).end();
  856. }
  857. const apiKeys = await ApiKey.where({});
  858. return response.status(200).json({
  859. apiKeys,
  860. error: null,
  861. });
  862. } catch (error) {
  863. console.error(error);
  864. response.status(500).json({
  865. apiKey: null,
  866. error: "Could not find an API Key.",
  867. });
  868. }
  869. });
  870. app.post(
  871. "/system/generate-api-key",
  872. [validatedRequest],
  873. async (_, response) => {
  874. try {
  875. if (response.locals.multiUserMode) {
  876. return response.sendStatus(401).end();
  877. }
  878. const { apiKey, error } = await ApiKey.create();
  879. await Telemetry.sendTelemetry("api_key_created");
  880. await EventLogs.logEvent(
  881. "api_key_created",
  882. {},
  883. response?.locals?.user?.id
  884. );
  885. return response.status(200).json({
  886. apiKey,
  887. error,
  888. });
  889. } catch (error) {
  890. console.error(error);
  891. response.status(500).json({
  892. apiKey: null,
  893. error: "Error generating api key.",
  894. });
  895. }
  896. }
  897. );
  898. app.delete("/system/api-key", [validatedRequest], async (_, response) => {
  899. try {
  900. if (response.locals.multiUserMode) {
  901. return response.sendStatus(401).end();
  902. }
  903. await ApiKey.delete();
  904. await EventLogs.logEvent(
  905. "api_key_deleted",
  906. { deletedBy: response.locals?.user?.username },
  907. response?.locals?.user?.id
  908. );
  909. return response.status(200).end();
  910. } catch (error) {
  911. console.error(error);
  912. response.status(500).end();
  913. }
  914. });
  915. app.post(
  916. "/system/custom-models",
  917. [validatedRequest, flexUserRoleValid([ROLES.admin])],
  918. async (request, response) => {
  919. try {
  920. const { provider, apiKey = null, basePath = null } = reqBody(request);
  921. const { models, error } = await getCustomModels(
  922. provider,
  923. apiKey,
  924. basePath
  925. );
  926. return response.status(200).json({
  927. models,
  928. error,
  929. });
  930. } catch (error) {
  931. console.error(error);
  932. response.status(500).end();
  933. }
  934. }
  935. );
  936. app.post(
  937. "/system/event-logs",
  938. [validatedRequest, flexUserRoleValid([ROLES.admin])],
  939. async (request, response) => {
  940. try {
  941. const { offset = 0, limit = 10 } = reqBody(request);
  942. const logs = await EventLogs.whereWithData({}, limit, offset * limit, {
  943. id: "desc",
  944. });
  945. const totalLogs = await EventLogs.count();
  946. const hasPages = totalLogs > (offset + 1) * limit;
  947. response.status(200).json({ logs: logs, hasPages, totalLogs });
  948. } catch (e) {
  949. console.error(e);
  950. response.sendStatus(500).end();
  951. }
  952. }
  953. );
  954. app.delete(
  955. "/system/event-logs",
  956. [validatedRequest, flexUserRoleValid([ROLES.admin])],
  957. async (_, response) => {
  958. try {
  959. await EventLogs.delete();
  960. await EventLogs.logEvent(
  961. "event_logs_cleared",
  962. {},
  963. response?.locals?.user?.id
  964. );
  965. response.json({ success: true });
  966. } catch (e) {
  967. console.error(e);
  968. response.sendStatus(500).end();
  969. }
  970. }
  971. );
  972. app.post(
  973. "/system/workspace-chats",
  974. [
  975. chatHistoryViewable,
  976. validatedRequest,
  977. flexUserRoleValid([ROLES.admin, ROLES.manager]),
  978. ],
  979. async (request, response) => {
  980. try {
  981. const { offset = 0, limit = 20 } = reqBody(request);
  982. const chats = await WorkspaceChats.whereWithData(
  983. {},
  984. limit,
  985. offset * limit,
  986. { id: "desc" }
  987. );
  988. const totalChats = await WorkspaceChats.count();
  989. const hasPages = totalChats > (offset + 1) * limit;
  990. response.status(200).json({ chats: chats, hasPages, totalChats });
  991. } catch (e) {
  992. console.error(e);
  993. response.sendStatus(500).end();
  994. }
  995. }
  996. );
  997. app.delete(
  998. "/system/workspace-chats/:id",
  999. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  1000. async (request, response) => {
  1001. try {
  1002. const { id } = request.params;
  1003. Number(id) === -1
  1004. ? await WorkspaceChats.delete({}, true)
  1005. : await WorkspaceChats.delete({ id: Number(id) });
  1006. response.json({ success: true, error: null });
  1007. } catch (e) {
  1008. console.error(e);
  1009. response.sendStatus(500).end();
  1010. }
  1011. }
  1012. );
  1013. app.get(
  1014. "/system/export-chats",
  1015. [
  1016. chatHistoryViewable,
  1017. validatedRequest,
  1018. flexUserRoleValid([ROLES.manager, ROLES.admin]),
  1019. ],
  1020. async (request, response) => {
  1021. try {
  1022. const { type = "jsonl", chatType = "workspace" } = request.query;
  1023. const { contentType, data } = await exportChatsAsType(type, chatType);
  1024. await EventLogs.logEvent(
  1025. "exported_chats",
  1026. {
  1027. type,
  1028. chatType,
  1029. },
  1030. response.locals.user?.id
  1031. );
  1032. response.setHeader("Content-Type", contentType);
  1033. response.status(200).send(data);
  1034. } catch (e) {
  1035. console.error(e);
  1036. response.sendStatus(500).end();
  1037. }
  1038. }
  1039. );
  1040. // Used for when a user in multi-user updates their own profile
  1041. // from the UI.
  1042. app.post("/system/user", [validatedRequest], async (request, response) => {
  1043. try {
  1044. const sessionUser = await userFromSession(request, response);
  1045. const { username, password } = reqBody(request);
  1046. const id = Number(sessionUser.id);
  1047. if (!id) {
  1048. response.status(400).json({ success: false, error: "Invalid user ID" });
  1049. return;
  1050. }
  1051. const updates = {};
  1052. if (username) {
  1053. updates.username = User.validations.username(String(username));
  1054. }
  1055. if (password) {
  1056. updates.password = String(password);
  1057. }
  1058. if (Object.keys(updates).length === 0) {
  1059. response
  1060. .status(400)
  1061. .json({ success: false, error: "No updates provided" });
  1062. return;
  1063. }
  1064. const { success, error } = await User.update(id, updates);
  1065. response.status(200).json({ success, error });
  1066. } catch (e) {
  1067. console.error(e);
  1068. response.sendStatus(500).end();
  1069. }
  1070. });
  1071. app.get(
  1072. "/system/slash-command-presets",
  1073. [validatedRequest, flexUserRoleValid([ROLES.all])],
  1074. async (request, response) => {
  1075. try {
  1076. const user = await userFromSession(request, response);
  1077. const userPresets = await SlashCommandPresets.getUserPresets(user?.id);
  1078. response.status(200).json({ presets: userPresets });
  1079. } catch (error) {
  1080. console.error("Error fetching slash command presets:", error);
  1081. response.status(500).json({ message: "Internal server error" });
  1082. }
  1083. }
  1084. );
  1085. app.post(
  1086. "/system/slash-command-presets",
  1087. [validatedRequest, flexUserRoleValid([ROLES.all])],
  1088. async (request, response) => {
  1089. try {
  1090. const user = await userFromSession(request, response);
  1091. const { command, prompt, description } = reqBody(request);
  1092. const presetData = {
  1093. command: SlashCommandPresets.formatCommand(String(command)),
  1094. prompt: String(prompt),
  1095. description: String(description),
  1096. };
  1097. const preset = await SlashCommandPresets.create(user?.id, presetData);
  1098. if (!preset) {
  1099. return response
  1100. .status(500)
  1101. .json({ message: "Failed to create preset" });
  1102. }
  1103. response.status(201).json({ preset });
  1104. } catch (error) {
  1105. console.error("Error creating slash command preset:", error);
  1106. response.status(500).json({ message: "Internal server error" });
  1107. }
  1108. }
  1109. );
  1110. app.post(
  1111. "/system/slash-command-presets/:slashCommandId",
  1112. [validatedRequest, flexUserRoleValid([ROLES.all])],
  1113. async (request, response) => {
  1114. try {
  1115. const user = await userFromSession(request, response);
  1116. const { slashCommandId } = request.params;
  1117. const { command, prompt, description } = reqBody(request);
  1118. // Valid user running owns the preset if user session is valid.
  1119. const ownsPreset = await SlashCommandPresets.get({
  1120. userId: user?.id ?? null,
  1121. id: Number(slashCommandId),
  1122. });
  1123. if (!ownsPreset)
  1124. return response.status(404).json({ message: "Preset not found" });
  1125. const updates = {
  1126. command: SlashCommandPresets.formatCommand(String(command)),
  1127. prompt: String(prompt),
  1128. description: String(description),
  1129. };
  1130. const preset = await SlashCommandPresets.update(
  1131. Number(slashCommandId),
  1132. updates
  1133. );
  1134. if (!preset) return response.sendStatus(422);
  1135. response.status(200).json({ preset: { ...ownsPreset, ...updates } });
  1136. } catch (error) {
  1137. console.error("Error updating slash command preset:", error);
  1138. response.status(500).json({ message: "Internal server error" });
  1139. }
  1140. }
  1141. );
  1142. app.delete(
  1143. "/system/slash-command-presets/:slashCommandId",
  1144. [validatedRequest, flexUserRoleValid([ROLES.all])],
  1145. async (request, response) => {
  1146. try {
  1147. const { slashCommandId } = request.params;
  1148. const user = await userFromSession(request, response);
  1149. // Valid user running owns the preset if user session is valid.
  1150. const ownsPreset = await SlashCommandPresets.get({
  1151. userId: user?.id ?? null,
  1152. id: Number(slashCommandId),
  1153. });
  1154. if (!ownsPreset)
  1155. return response
  1156. .status(403)
  1157. .json({ message: "Failed to delete preset" });
  1158. await SlashCommandPresets.delete(Number(slashCommandId));
  1159. response.sendStatus(204);
  1160. } catch (error) {
  1161. console.error("Error deleting slash command preset:", error);
  1162. response.status(500).json({ message: "Internal server error" });
  1163. }
  1164. }
  1165. );
  1166. }
  1167. module.exports = { systemEndpoints };