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.

178 lines
4.5 KiB

11 months ago
  1. const { v4: uuidv4, validate } = require("uuid");
  2. const { VALID_CHAT_MODE } = require("../chats/stream");
  3. const { EmbedChats } = require("../../models/embedChats");
  4. const { EmbedConfig } = require("../../models/embedConfig");
  5. const { reqBody } = require("../http");
  6. // Finds or Aborts request for a /:embedId/ url. This should always
  7. // be the first middleware and the :embedID should be in the URL.
  8. async function validEmbedConfig(request, response, next) {
  9. const { embedId } = request.params;
  10. const embed = await EmbedConfig.getWithWorkspace({ uuid: embedId });
  11. if (!embed) {
  12. response.sendStatus(404).end();
  13. return;
  14. }
  15. response.locals.embedConfig = embed;
  16. next();
  17. }
  18. function setConnectionMeta(request, response, next) {
  19. response.locals.connection = {
  20. host: request.headers?.origin,
  21. ip: request?.ip,
  22. };
  23. next();
  24. }
  25. async function validEmbedConfigId(request, response, next) {
  26. const { embedId } = request.params;
  27. const embed = await EmbedConfig.get({ id: Number(embedId) });
  28. if (!embed) {
  29. response.sendStatus(404).end();
  30. return;
  31. }
  32. response.locals.embedConfig = embed;
  33. next();
  34. }
  35. async function canRespond(request, response, next) {
  36. try {
  37. const embed = response.locals.embedConfig;
  38. if (!embed) {
  39. response.sendStatus(404).end();
  40. return;
  41. }
  42. // Block if disabled by admin.
  43. if (!embed.enabled) {
  44. response.status(503).json({
  45. id: uuidv4(),
  46. type: "abort",
  47. textResponse: null,
  48. sources: [],
  49. close: true,
  50. error:
  51. "This chat has been disabled by the administrator - try again later.",
  52. });
  53. return;
  54. }
  55. // Check if requester hostname is in the valid allowlist of domains.
  56. const host = request.headers.origin ?? "";
  57. const allowedHosts = EmbedConfig.parseAllowedHosts(embed);
  58. if (allowedHosts !== null && !allowedHosts.includes(host)) {
  59. response.status(401).json({
  60. id: uuidv4(),
  61. type: "abort",
  62. textResponse: null,
  63. sources: [],
  64. close: true,
  65. error: "Invalid request.",
  66. });
  67. return;
  68. }
  69. const { sessionId, message } = reqBody(request);
  70. if (typeof sessionId !== "string" || !validate(String(sessionId))) {
  71. response.status(404).json({
  72. id: uuidv4(),
  73. type: "abort",
  74. textResponse: null,
  75. sources: [],
  76. close: true,
  77. error: "Invalid session ID.",
  78. });
  79. return;
  80. }
  81. if (!message?.length || !VALID_CHAT_MODE.includes(embed.chat_mode)) {
  82. response.status(400).json({
  83. id: uuidv4(),
  84. type: "abort",
  85. textResponse: null,
  86. sources: [],
  87. close: true,
  88. error: !message?.length
  89. ? "Message is empty."
  90. : `${embed.chat_mode} is not a valid mode.`,
  91. });
  92. return;
  93. }
  94. if (
  95. !isNaN(embed.max_chats_per_day) &&
  96. Number(embed.max_chats_per_day) > 0
  97. ) {
  98. const dailyChatCount = await EmbedChats.count({
  99. embed_id: embed.id,
  100. createdAt: {
  101. gte: new Date(new Date() - 24 * 60 * 60 * 1000),
  102. },
  103. });
  104. if (dailyChatCount >= Number(embed.max_chats_per_day)) {
  105. response.status(429).json({
  106. id: uuidv4(),
  107. type: "abort",
  108. textResponse: null,
  109. sources: [],
  110. close: true,
  111. error: "Rate limit exceeded",
  112. errorMsg:
  113. "The quota for this chat has been reached. Try again later or contact the site owner.",
  114. });
  115. return;
  116. }
  117. }
  118. if (
  119. !isNaN(embed.max_chats_per_session) &&
  120. Number(embed.max_chats_per_session) > 0
  121. ) {
  122. const dailySessionCount = await EmbedChats.count({
  123. embed_id: embed.id,
  124. session_id: sessionId,
  125. createdAt: {
  126. gte: new Date(new Date() - 24 * 60 * 60 * 1000),
  127. },
  128. });
  129. if (dailySessionCount >= Number(embed.max_chats_per_session)) {
  130. response.status(429).json({
  131. id: uuidv4(),
  132. type: "abort",
  133. textResponse: null,
  134. sources: [],
  135. close: true,
  136. error:
  137. "Your quota for this chat has been reached. Try again later or contact the site owner.",
  138. });
  139. return;
  140. }
  141. }
  142. next();
  143. } catch (e) {
  144. response.status(500).json({
  145. id: uuidv4(),
  146. type: "abort",
  147. textResponse: null,
  148. sources: [],
  149. close: true,
  150. error: "Invalid request.",
  151. });
  152. return;
  153. }
  154. }
  155. module.exports = {
  156. setConnectionMeta,
  157. validEmbedConfig,
  158. validEmbedConfigId,
  159. canRespond,
  160. };