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.

1233 lines
36 KiB

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. if (!uploadedFileName) {
  648. return response.status(400).json({ message: "File upload failed." });
  649. }
  650. const userRecord = await User.get({ id: user.id });
  651. const oldPfpFilename = userRecord.pfpFilename;
  652. if (oldPfpFilename) {
  653. const storagePath = path.join(__dirname, "../storage/assets/pfp");
  654. const oldPfpPath = path.join(
  655. storagePath,
  656. normalizePath(userRecord.pfpFilename)
  657. );
  658. if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath)))
  659. throw new Error("Invalid path name");
  660. if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath);
  661. }
  662. const { success, error } = await User.update(user.id, {
  663. pfpFilename: uploadedFileName,
  664. });
  665. return response.status(success ? 200 : 500).json({
  666. message: success
  667. ? "Profile picture uploaded successfully."
  668. : error || "Failed to update with new profile picture.",
  669. });
  670. } catch (error) {
  671. console.error("Error processing the profile picture upload:", error);
  672. response.status(500).json({ message: "Internal server error" });
  673. }
  674. }
  675. );
  676. app.delete(
  677. "/system/remove-pfp",
  678. [validatedRequest, flexUserRoleValid([ROLES.all])],
  679. async function (request, response) {
  680. try {
  681. const user = await userFromSession(request, response);
  682. const userRecord = await User.get({ id: user.id });
  683. const oldPfpFilename = userRecord.pfpFilename;
  684. if (oldPfpFilename) {
  685. const storagePath = path.join(__dirname, "../storage/assets/pfp");
  686. const oldPfpPath = path.join(
  687. storagePath,
  688. normalizePath(oldPfpFilename)
  689. );
  690. if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath)))
  691. throw new Error("Invalid path name");
  692. if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath);
  693. }
  694. const { success, error } = await User.update(user.id, {
  695. pfpFilename: null,
  696. });
  697. return response.status(success ? 200 : 500).json({
  698. message: success
  699. ? "Profile picture removed successfully."
  700. : error || "Failed to remove profile picture.",
  701. });
  702. } catch (error) {
  703. console.error("Error processing the profile picture removal:", error);
  704. response.status(500).json({ message: "Internal server error" });
  705. }
  706. }
  707. );
  708. app.post(
  709. "/system/upload-logo",
  710. [
  711. validatedRequest,
  712. flexUserRoleValid([ROLES.admin, ROLES.manager]),
  713. handleAssetUpload,
  714. ],
  715. async (request, response) => {
  716. if (!request?.file || !request?.file.originalname) {
  717. return response.status(400).json({ message: "No logo file provided." });
  718. }
  719. if (!validFilename(request.file.originalname)) {
  720. return response.status(400).json({
  721. message: "Invalid file name. Please choose a different file.",
  722. });
  723. }
  724. try {
  725. const newFilename = await renameLogoFile(request.file.originalname);
  726. const existingLogoFilename = await SystemSettings.currentLogoFilename();
  727. await removeCustomLogo(existingLogoFilename);
  728. const { success, error } = await SystemSettings._updateSettings({
  729. logo_filename: newFilename,
  730. });
  731. return response.status(success ? 200 : 500).json({
  732. message: success
  733. ? "Logo uploaded successfully."
  734. : error || "Failed to update with new logo.",
  735. });
  736. } catch (error) {
  737. console.error("Error processing the logo upload:", error);
  738. response.status(500).json({ message: "Error uploading the logo." });
  739. }
  740. }
  741. );
  742. app.get("/system/is-default-logo", async (_, response) => {
  743. try {
  744. const currentLogoFilename = await SystemSettings.currentLogoFilename();
  745. const isDefaultLogo = currentLogoFilename === LOGO_FILENAME;
  746. response.status(200).json({ isDefaultLogo });
  747. } catch (error) {
  748. console.error("Error processing the logo request:", error);
  749. response.status(500).json({ message: "Internal server error" });
  750. }
  751. });
  752. app.get(
  753. "/system/remove-logo",
  754. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  755. async (_request, response) => {
  756. try {
  757. const currentLogoFilename = await SystemSettings.currentLogoFilename();
  758. await removeCustomLogo(currentLogoFilename);
  759. const { success, error } = await SystemSettings._updateSettings({
  760. logo_filename: LOGO_FILENAME,
  761. });
  762. return response.status(success ? 200 : 500).json({
  763. message: success
  764. ? "Logo removed successfully."
  765. : error || "Failed to update with new logo.",
  766. });
  767. } catch (error) {
  768. console.error("Error processing the logo removal:", error);
  769. response.status(500).json({ message: "Error removing the logo." });
  770. }
  771. }
  772. );
  773. app.get(
  774. "/system/welcome-messages",
  775. [validatedRequest, flexUserRoleValid([ROLES.all])],
  776. async function (_, response) {
  777. try {
  778. const welcomeMessages = await WelcomeMessages.getMessages();
  779. response.status(200).json({ success: true, welcomeMessages });
  780. } catch (error) {
  781. console.error("Error fetching welcome messages:", error);
  782. response
  783. .status(500)
  784. .json({ success: false, message: "Internal server error" });
  785. }
  786. }
  787. );
  788. app.post(
  789. "/system/set-welcome-messages",
  790. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  791. async (request, response) => {
  792. try {
  793. const { messages = [] } = reqBody(request);
  794. if (!Array.isArray(messages)) {
  795. return response.status(400).json({
  796. success: false,
  797. message: "Invalid message format. Expected an array of messages.",
  798. });
  799. }
  800. await WelcomeMessages.saveAll(messages);
  801. return response.status(200).json({
  802. success: true,
  803. message: "Welcome messages saved successfully.",
  804. });
  805. } catch (error) {
  806. console.error("Error processing the welcome messages:", error);
  807. response.status(500).json({
  808. success: true,
  809. message: "Error saving the welcome messages.",
  810. });
  811. }
  812. }
  813. );
  814. app.get("/system/api-keys", [validatedRequest], async (_, response) => {
  815. try {
  816. if (response.locals.multiUserMode) {
  817. return response.sendStatus(401).end();
  818. }
  819. const apiKeys = await ApiKey.where({});
  820. return response.status(200).json({
  821. apiKeys,
  822. error: null,
  823. });
  824. } catch (error) {
  825. console.error(error);
  826. response.status(500).json({
  827. apiKey: null,
  828. error: "Could not find an API Key.",
  829. });
  830. }
  831. });
  832. app.post(
  833. "/system/generate-api-key",
  834. [validatedRequest],
  835. async (_, response) => {
  836. try {
  837. if (response.locals.multiUserMode) {
  838. return response.sendStatus(401).end();
  839. }
  840. const { apiKey, error } = await ApiKey.create();
  841. await Telemetry.sendTelemetry("api_key_created");
  842. await EventLogs.logEvent(
  843. "api_key_created",
  844. {},
  845. response?.locals?.user?.id
  846. );
  847. return response.status(200).json({
  848. apiKey,
  849. error,
  850. });
  851. } catch (error) {
  852. console.error(error);
  853. response.status(500).json({
  854. apiKey: null,
  855. error: "Error generating api key.",
  856. });
  857. }
  858. }
  859. );
  860. app.delete("/system/api-key", [validatedRequest], async (_, response) => {
  861. try {
  862. if (response.locals.multiUserMode) {
  863. return response.sendStatus(401).end();
  864. }
  865. await ApiKey.delete();
  866. await EventLogs.logEvent(
  867. "api_key_deleted",
  868. { deletedBy: response.locals?.user?.username },
  869. response?.locals?.user?.id
  870. );
  871. return response.status(200).end();
  872. } catch (error) {
  873. console.error(error);
  874. response.status(500).end();
  875. }
  876. });
  877. app.post(
  878. "/system/custom-models",
  879. [validatedRequest, flexUserRoleValid([ROLES.admin])],
  880. async (request, response) => {
  881. try {
  882. const { provider, apiKey = null, basePath = null } = reqBody(request);
  883. const { models, error } = await getCustomModels(
  884. provider,
  885. apiKey,
  886. basePath
  887. );
  888. return response.status(200).json({
  889. models,
  890. error,
  891. });
  892. } catch (error) {
  893. console.error(error);
  894. response.status(500).end();
  895. }
  896. }
  897. );
  898. app.post(
  899. "/system/event-logs",
  900. [validatedRequest, flexUserRoleValid([ROLES.admin])],
  901. async (request, response) => {
  902. try {
  903. const { offset = 0, limit = 10 } = reqBody(request);
  904. const logs = await EventLogs.whereWithData({}, limit, offset * limit, {
  905. id: "desc",
  906. });
  907. const totalLogs = await EventLogs.count();
  908. const hasPages = totalLogs > (offset + 1) * limit;
  909. response.status(200).json({ logs: logs, hasPages, totalLogs });
  910. } catch (e) {
  911. console.error(e);
  912. response.sendStatus(500).end();
  913. }
  914. }
  915. );
  916. app.delete(
  917. "/system/event-logs",
  918. [validatedRequest, flexUserRoleValid([ROLES.admin])],
  919. async (_, response) => {
  920. try {
  921. await EventLogs.delete();
  922. await EventLogs.logEvent(
  923. "event_logs_cleared",
  924. {},
  925. response?.locals?.user?.id
  926. );
  927. response.json({ success: true });
  928. } catch (e) {
  929. console.error(e);
  930. response.sendStatus(500).end();
  931. }
  932. }
  933. );
  934. app.post(
  935. "/system/workspace-chats",
  936. [
  937. chatHistoryViewable,
  938. validatedRequest,
  939. flexUserRoleValid([ROLES.admin, ROLES.manager]),
  940. ],
  941. async (request, response) => {
  942. try {
  943. const { offset = 0, limit = 20 } = reqBody(request);
  944. const chats = await WorkspaceChats.whereWithData(
  945. {},
  946. limit,
  947. offset * limit,
  948. { id: "desc" }
  949. );
  950. const totalChats = await WorkspaceChats.count();
  951. const hasPages = totalChats > (offset + 1) * limit;
  952. response.status(200).json({ chats: chats, hasPages, totalChats });
  953. } catch (e) {
  954. console.error(e);
  955. response.sendStatus(500).end();
  956. }
  957. }
  958. );
  959. app.delete(
  960. "/system/workspace-chats/:id",
  961. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  962. async (request, response) => {
  963. try {
  964. const { id } = request.params;
  965. Number(id) === -1
  966. ? await WorkspaceChats.delete({}, true)
  967. : await WorkspaceChats.delete({ id: Number(id) });
  968. response.json({ success: true, error: null });
  969. } catch (e) {
  970. console.error(e);
  971. response.sendStatus(500).end();
  972. }
  973. }
  974. );
  975. app.get(
  976. "/system/export-chats",
  977. [
  978. chatHistoryViewable,
  979. validatedRequest,
  980. flexUserRoleValid([ROLES.manager, ROLES.admin]),
  981. ],
  982. async (request, response) => {
  983. try {
  984. const { type = "jsonl", chatType = "workspace" } = request.query;
  985. const { contentType, data } = await exportChatsAsType(type, chatType);
  986. await EventLogs.logEvent(
  987. "exported_chats",
  988. {
  989. type,
  990. chatType,
  991. },
  992. response.locals.user?.id
  993. );
  994. response.setHeader("Content-Type", contentType);
  995. response.status(200).send(data);
  996. } catch (e) {
  997. console.error(e);
  998. response.sendStatus(500).end();
  999. }
  1000. }
  1001. );
  1002. // Used for when a user in multi-user updates their own profile
  1003. // from the UI.
  1004. app.post("/system/user", [validatedRequest], async (request, response) => {
  1005. try {
  1006. const sessionUser = await userFromSession(request, response);
  1007. const { username, password } = reqBody(request);
  1008. const id = Number(sessionUser.id);
  1009. if (!id) {
  1010. response.status(400).json({ success: false, error: "Invalid user ID" });
  1011. return;
  1012. }
  1013. const updates = {};
  1014. if (username) {
  1015. updates.username = User.validations.username(String(username));
  1016. }
  1017. if (password) {
  1018. updates.password = String(password);
  1019. }
  1020. if (Object.keys(updates).length === 0) {
  1021. response
  1022. .status(400)
  1023. .json({ success: false, error: "No updates provided" });
  1024. return;
  1025. }
  1026. const { success, error } = await User.update(id, updates);
  1027. response.status(200).json({ success, error });
  1028. } catch (e) {
  1029. console.error(e);
  1030. response.sendStatus(500).end();
  1031. }
  1032. });
  1033. app.get(
  1034. "/system/slash-command-presets",
  1035. [validatedRequest, flexUserRoleValid([ROLES.all])],
  1036. async (request, response) => {
  1037. try {
  1038. const user = await userFromSession(request, response);
  1039. const userPresets = await SlashCommandPresets.getUserPresets(user?.id);
  1040. response.status(200).json({ presets: userPresets });
  1041. } catch (error) {
  1042. console.error("Error fetching slash command presets:", error);
  1043. response.status(500).json({ message: "Internal server error" });
  1044. }
  1045. }
  1046. );
  1047. app.post(
  1048. "/system/slash-command-presets",
  1049. [validatedRequest, flexUserRoleValid([ROLES.all])],
  1050. async (request, response) => {
  1051. try {
  1052. const user = await userFromSession(request, response);
  1053. const { command, prompt, description } = reqBody(request);
  1054. const presetData = {
  1055. command: SlashCommandPresets.formatCommand(String(command)),
  1056. prompt: String(prompt),
  1057. description: String(description),
  1058. };
  1059. const preset = await SlashCommandPresets.create(user?.id, presetData);
  1060. if (!preset) {
  1061. return response
  1062. .status(500)
  1063. .json({ message: "Failed to create preset" });
  1064. }
  1065. response.status(201).json({ preset });
  1066. } catch (error) {
  1067. console.error("Error creating slash command preset:", error);
  1068. response.status(500).json({ message: "Internal server error" });
  1069. }
  1070. }
  1071. );
  1072. app.post(
  1073. "/system/slash-command-presets/:slashCommandId",
  1074. [validatedRequest, flexUserRoleValid([ROLES.all])],
  1075. async (request, response) => {
  1076. try {
  1077. const user = await userFromSession(request, response);
  1078. const { slashCommandId } = request.params;
  1079. const { command, prompt, description } = reqBody(request);
  1080. // Valid user running owns the preset if user session is valid.
  1081. const ownsPreset = await SlashCommandPresets.get({
  1082. userId: user?.id ?? null,
  1083. id: Number(slashCommandId),
  1084. });
  1085. if (!ownsPreset)
  1086. return response.status(404).json({ message: "Preset not found" });
  1087. const updates = {
  1088. command: SlashCommandPresets.formatCommand(String(command)),
  1089. prompt: String(prompt),
  1090. description: String(description),
  1091. };
  1092. const preset = await SlashCommandPresets.update(
  1093. Number(slashCommandId),
  1094. updates
  1095. );
  1096. if (!preset) return response.sendStatus(422);
  1097. response.status(200).json({ preset: { ...ownsPreset, ...updates } });
  1098. } catch (error) {
  1099. console.error("Error updating slash command preset:", error);
  1100. response.status(500).json({ message: "Internal server error" });
  1101. }
  1102. }
  1103. );
  1104. app.delete(
  1105. "/system/slash-command-presets/:slashCommandId",
  1106. [validatedRequest, flexUserRoleValid([ROLES.all])],
  1107. async (request, response) => {
  1108. try {
  1109. const { slashCommandId } = request.params;
  1110. const user = await userFromSession(request, response);
  1111. // Valid user running owns the preset if user session is valid.
  1112. const ownsPreset = await SlashCommandPresets.get({
  1113. userId: user?.id ?? null,
  1114. id: Number(slashCommandId),
  1115. });
  1116. if (!ownsPreset)
  1117. return response
  1118. .status(403)
  1119. .json({ message: "Failed to delete preset" });
  1120. await SlashCommandPresets.delete(Number(slashCommandId));
  1121. response.sendStatus(204);
  1122. } catch (error) {
  1123. console.error("Error deleting slash command preset:", error);
  1124. response.status(500).json({ message: "Internal server error" });
  1125. }
  1126. }
  1127. );
  1128. }
  1129. module.exports = { systemEndpoints };