From df8bbeda02a6910db8477c3ef29f45a2af24edb2 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Thu, 3 Oct 2024 14:08:19 -0700 Subject: [PATCH 1/4] set message limit per user --- .../UserMenu/AccountModal/index.jsx | 2 +- .../pages/Admin/Users/NewUserModal/index.jsx | 65 +++++++++++++- .../Users/UserRow/EditUserModal/index.jsx | 63 +++++++++++++- server/endpoints/chat.js | 87 +++++++------------ server/models/user.js | 19 +++- .../20241003192954_init/migration.sql | 2 + server/prisma/schema.prisma | 3 +- 7 files changed, 176 insertions(+), 65 deletions(-) create mode 100644 server/prisma/migrations/20241003192954_init/migration.sql diff --git a/frontend/src/components/UserMenu/AccountModal/index.jsx b/frontend/src/components/UserMenu/AccountModal/index.jsx index 9fac4aaebb9..9fe7f60a2e5 100644 --- a/frontend/src/components/UserMenu/AccountModal/index.jsx +++ b/frontend/src/components/UserMenu/AccountModal/index.jsx @@ -135,7 +135,7 @@ export default function AccountModal({ user, hideModal }) { autoComplete="off" />

- Username must be only contain lowercase letters, numbers, + Username must only contain lowercase letters, numbers, underscores, and hyphens with no spaces

diff --git a/frontend/src/pages/Admin/Users/NewUserModal/index.jsx b/frontend/src/pages/Admin/Users/NewUserModal/index.jsx index 3af7ebd4a26..47c31d5fa15 100644 --- a/frontend/src/pages/Admin/Users/NewUserModal/index.jsx +++ b/frontend/src/pages/Admin/Users/NewUserModal/index.jsx @@ -7,6 +7,10 @@ import { RoleHintDisplay } from ".."; export default function NewUserModal({ closeModal }) { const [error, setError] = useState(null); const [role, setRole] = useState("default"); + const [messageLimit, setMessageLimit] = useState({ + enabled: false, + limit: 10, + }); const handleCreate = async (e) => { setError(null); @@ -14,6 +18,12 @@ export default function NewUserModal({ closeModal }) { const data = {}; const form = new FormData(e.target); for (var [key, value] of form.entries()) data[key] = value; + if (messageLimit.enabled) { + data.dailyMessageLimit = messageLimit.limit; + } else { + data.dailyMessageLimit = null; + } + const { user, error } = await Admin.newUser(data); if (!!user) window.location.reload(); setError(error); @@ -58,13 +68,13 @@ export default function NewUserModal({ closeModal }) { pattern="^[a-z0-9_-]+$" onInvalid={(e) => e.target.setCustomValidity( - "Username must be only contain lowercase letters, numbers, underscores, and hyphens with no spaces" + "Username must only contain lowercase letters, numbers, underscores, and hyphens with no spaces" ) } onChange={(e) => e.target.setCustomValidity("")} />

- Username must be only contain lowercase letters, numbers, + Username must only contain lowercase letters, numbers, underscores, and hyphens with no spaces

@@ -110,6 +120,57 @@ export default function NewUserModal({ closeModal }) { +
+
+

+ Limit messages per day +

+

+ Restrict this user to a number of successful queries or + chats within a 24 hour window. +

+
+ +
+
+ {messageLimit.enabled && ( +
+ +
+ e.target.blur()} + onChange={(e) => { + setMessageLimit({ + enabled: true, + limit: Number(e?.target?.value || 0), + }); + }} + value={messageLimit.limit} + min={1} + className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-60 p-2.5" + /> +
+
+ )} +
{error &&

Error: {error}

}

After creating a user they will need to login with their initial diff --git a/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx b/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx index ec234c2f413..3a5e8012bfa 100644 --- a/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx +++ b/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx @@ -6,6 +6,10 @@ import { RoleHintDisplay } from "../.."; export default function EditUserModal({ currentUser, user, closeModal }) { const [role, setRole] = useState(user.role); const [error, setError] = useState(null); + const [messageLimit, setMessageLimit] = useState({ + enabled: user.dailyMessageLimit !== null, + limit: user.dailyMessageLimit || 10, + }); const handleUpdate = async (e) => { setError(null); @@ -16,6 +20,12 @@ export default function EditUserModal({ currentUser, user, closeModal }) { if (!value || value === null) continue; data[key] = value; } + if (messageLimit.enabled) { + data.dailyMessageLimit = messageLimit.limit; + } else { + data.dailyMessageLimit = null; + } + const { success, error } = await Admin.updateUser(user.id, data); if (success) window.location.reload(); setError(error); @@ -58,7 +68,7 @@ export default function EditUserModal({ currentUser, user, closeModal }) { autoComplete="off" />

- Username must be only contain lowercase letters, numbers, + Username must only contain lowercase letters, numbers, underscores, and hyphens with no spaces

@@ -103,6 +113,57 @@ export default function EditUserModal({ currentUser, user, closeModal }) { +
+
+

+ Limit messages per day +

+

+ Restrict this user to a number of successful queries or + chats within a 24 hour window. +

+
+ +
+
+ {messageLimit.enabled && ( +
+ +
+ e.target.blur()} + onChange={(e) => { + setMessageLimit({ + enabled: true, + limit: Number(e?.target?.value || 0), + }); + }} + value={messageLimit.limit} + min={1} + className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-60 p-2.5" + /> +
+
+ )} +
{error &&

Error: {error}

} diff --git a/server/endpoints/chat.js b/server/endpoints/chat.js index 64beefeb6a5..b49071f2b67 100644 --- a/server/endpoints/chat.js +++ b/server/endpoints/chat.js @@ -2,7 +2,6 @@ const { v4: uuidv4 } = require("uuid"); const { reqBody, userFromSession, multiUserMode } = require("../utils/http"); const { validatedRequest } = require("../utils/middleware/validatedRequest"); const { WorkspaceChats } = require("../models/workspaceChats"); -const { SystemSettings } = require("../models/systemSettings"); const { Telemetry } = require("../models/telemetry"); const { streamChatWithWorkspace } = require("../utils/chats/stream"); const { @@ -49,36 +48,24 @@ function chatEndpoints(app) { response.flushHeaders(); if (multiUserMode(response) && user.role !== ROLES.admin) { - const limitMessagesSetting = await SystemSettings.get({ - label: "limit_user_messages", - }); - const limitMessages = limitMessagesSetting?.value === "true"; - - if (limitMessages) { - const messageLimitSetting = await SystemSettings.get({ - label: "message_limit", + if (user.dailyMessageLimit !== null) { + const currentChatCount = await WorkspaceChats.count({ + user_id: user.id, + createdAt: { + gte: new Date(new Date() - 24 * 60 * 60 * 1000), + }, }); - const systemLimit = Number(messageLimitSetting?.value); - if (!!systemLimit) { - const currentChatCount = await WorkspaceChats.count({ - user_id: user.id, - createdAt: { - gte: new Date(new Date() - 24 * 60 * 60 * 1000), - }, + if (currentChatCount >= user.dailyMessageLimit) { + writeResponseChunk(response, { + id: uuidv4(), + type: "abort", + textResponse: null, + sources: [], + close: true, + error: `You have met your maximum 24 hour chat quota of ${user.dailyMessageLimit} chats. Try again later.`, }); - - if (currentChatCount >= systemLimit) { - writeResponseChunk(response, { - id: uuidv4(), - type: "abort", - textResponse: null, - sources: [], - close: true, - error: `You have met your maximum 24 hour chat quota of ${systemLimit} chats set by the instance administrators. Try again later.`, - }); - return; - } + return; } } } @@ -158,38 +145,24 @@ function chatEndpoints(app) { response.flushHeaders(); if (multiUserMode(response) && user.role !== ROLES.admin) { - const limitMessagesSetting = await SystemSettings.get({ - label: "limit_user_messages", - }); - const limitMessages = limitMessagesSetting?.value === "true"; - - if (limitMessages) { - const messageLimitSetting = await SystemSettings.get({ - label: "message_limit", + if (user.dailyMessageLimit !== null) { + const currentChatCount = await WorkspaceChats.count({ + user_id: user.id, + createdAt: { + gte: new Date(new Date() - 24 * 60 * 60 * 1000), + }, }); - const systemLimit = Number(messageLimitSetting?.value); - if (!!systemLimit) { - // Chat qty includes all threads because any user can freely - // create threads and would bypass this rule. - const currentChatCount = await WorkspaceChats.count({ - user_id: user.id, - createdAt: { - gte: new Date(new Date() - 24 * 60 * 60 * 1000), - }, + if (currentChatCount >= user.dailyMessageLimit) { + writeResponseChunk(response, { + id: uuidv4(), + type: "abort", + textResponse: null, + sources: [], + close: true, + error: `You have met your maximum 24 hour chat quota of ${user.dailyMessageLimit} chats. Try again later.`, }); - - if (currentChatCount >= systemLimit) { - writeResponseChunk(response, { - id: uuidv4(), - type: "abort", - textResponse: null, - sources: [], - close: true, - error: `You have met your maximum 24 hour chat quota of ${systemLimit} chats set by the instance administrators. Try again later.`, - }); - return; - } + return; } } } diff --git a/server/models/user.js b/server/models/user.js index a149a45ea6a..ae65d63905b 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -10,6 +10,7 @@ const User = { "pfpFilename", "role", "suspended", + "dailyMessageLimit", ], validations: { username: (newValue = "") => { @@ -32,12 +33,17 @@ const User = { } return String(role); }, + dailyMessageLimit: (dailyMessageLimit = null) => { + return dailyMessageLimit === null ? null : Number(dailyMessageLimit); + }, }, // validations for the above writable fields. castColumnValue: function (key, value) { switch (key) { case "suspended": return Number(Boolean(value)); + case "dailyMessageLimit": + return value === null ? null : Number(value); default: return String(value); } @@ -48,7 +54,12 @@ const User = { return { ...rest }; }, - create: async function ({ username, password, role = "default" }) { + create: async function ({ + username, + password, + role = "default", + dailyMessageLimit = null, + }) { const passwordCheck = this.checkPasswordComplexity(password); if (!passwordCheck.checkedOK) { return { user: null, error: passwordCheck.error }; @@ -58,7 +69,7 @@ const User = { // Do not allow new users to bypass validation if (!this.usernameRegex.test(username)) throw new Error( - "Username must be only contain lowercase letters, numbers, underscores, and hyphens with no spaces" + "Username must only contain lowercase letters, numbers, underscores, and hyphens with no spaces" ); const bcrypt = require("bcrypt"); @@ -68,6 +79,8 @@ const User = { username: this.validations.username(username), password: hashedPassword, role: this.validations.role(role), + dailyMessageLimit: + this.validations.dailyMessageLimit(dailyMessageLimit), }, }); return { user: this.filterFields(user), error: null }; @@ -135,7 +148,7 @@ const User = { return { success: false, error: - "Username must be only contain lowercase letters, numbers, underscores, and hyphens with no spaces", + "Username must only contain lowercase letters, numbers, underscores, and hyphens with no spaces", }; const user = await prisma.users.update({ diff --git a/server/prisma/migrations/20241003192954_init/migration.sql b/server/prisma/migrations/20241003192954_init/migration.sql new file mode 100644 index 00000000000..e3d26d35c44 --- /dev/null +++ b/server/prisma/migrations/20241003192954_init/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "users" ADD COLUMN "dailyMessageLimit" INTEGER; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 276b9eaf9d1..b9613088853 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -67,6 +67,7 @@ model users { seen_recovery_codes Boolean? @default(false) createdAt DateTime @default(now()) lastUpdatedAt DateTime @default(now()) + dailyMessageLimit Int? workspace_chats workspace_chats[] workspace_users workspace_users[] embed_configs embed_configs[] @@ -309,4 +310,4 @@ model browser_extension_api_keys { user users? @relation(fields: [user_id], references: [id], onDelete: Cascade) @@index([user_id]) -} \ No newline at end of file +} From dedac42f78d4375d9b06aaf9654784231a48cea2 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Thu, 3 Oct 2024 14:22:08 -0700 Subject: [PATCH 2/4] remove old limit user messages + unused admin page --- frontend/src/App.jsx | 5 - .../src/components/SettingsButton/index.jsx | 4 +- .../src/components/SettingsSidebar/index.jsx | 5 - frontend/src/pages/Admin/System/index.jsx | 128 ------------------ frontend/src/utils/paths.js | 3 - server/endpoints/admin.js | 15 -- server/endpoints/api/admin/index.js | 53 +------- server/endpoints/system.js | 2 - server/models/systemSettings.js | 4 - server/prisma/seed.js | 2 - server/swagger/openapi.json | 49 +------ 11 files changed, 3 insertions(+), 267 deletions(-) delete mode 100644 frontend/src/pages/Admin/System/index.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c6cac66db7f..cb3bac7f719 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -22,7 +22,6 @@ const WorkspaceChat = lazy(() => import("@/pages/WorkspaceChat")); const AdminUsers = lazy(() => import("@/pages/Admin/Users")); const AdminInvites = lazy(() => import("@/pages/Admin/Invitations")); const AdminWorkspaces = lazy(() => import("@/pages/Admin/Workspaces")); -const AdminSystem = lazy(() => import("@/pages/Admin/System")); const AdminLogs = lazy(() => import("@/pages/Admin/Logging")); const AdminAgents = lazy(() => import("@/pages/Admin/Agents")); const GeneralChats = lazy(() => import("@/pages/GeneralSettings/Chats")); @@ -168,10 +167,6 @@ export default function App() { path="/settings/workspace-chats" element={} /> - } - /> } diff --git a/frontend/src/components/SettingsButton/index.jsx b/frontend/src/components/SettingsButton/index.jsx index 19a4a17aa95..f53e675f1f4 100644 --- a/frontend/src/components/SettingsButton/index.jsx +++ b/frontend/src/components/SettingsButton/index.jsx @@ -29,9 +29,7 @@ export default function SettingsButton() { return ( ( href: paths.settings.invites(), roles: ["admin", "manager"], }, - { - btnText: t("settings.system"), - href: paths.settings.system(), - roles: ["admin", "manager"], - }, ]} />