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

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year 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 };