mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2025-04-23 13:08:11 +00:00
Create manager role and limit default role (#351)
* added manager role to options * block default role from editing workspace settings on workspace and text input box * block default user from accessing settings at all * create manager route * let pass through if in single user mode * fix permissions for manager and admin roles in settings * fix settings button for single user and remove unneeded console.logs * rename routes and paths for clarity * admin, manager, default roles complete * remove unneeded comments * consistency changes * manage permissions for mum modes * update sidebar for single-user mode * update comment on middleware Modify permission setting for admins * update render conditional * Add role usage hint to each role --------- Co-authored-by: timothycarambat <rambat1010@gmail.com>
This commit is contained in:
parent
7fcf29d769
commit
fa29003a46
21 changed files with 462 additions and 456 deletions
frontend/src
App.jsx
components
LLMSelection
Modals/MangeWorkspace
PrivateRoute
SettingsSidebar
Sidebar
WorkspaceChat/ChatContainer/PromptInput
pages
Admin/Users
GeneralSettings/Security
utils
server
endpoints
models
utils/middleware
|
@ -1,7 +1,10 @@
|
||||||
import React, { lazy, Suspense } from "react";
|
import React, { lazy, Suspense } from "react";
|
||||||
import { Routes, Route } from "react-router-dom";
|
import { Routes, Route } from "react-router-dom";
|
||||||
import { ContextWrapper } from "./AuthContext";
|
import { ContextWrapper } from "./AuthContext";
|
||||||
import PrivateRoute, { AdminRoute } from "./components/PrivateRoute";
|
import PrivateRoute, {
|
||||||
|
AdminRoute,
|
||||||
|
ManagerRoute,
|
||||||
|
} from "./components/PrivateRoute";
|
||||||
import { ToastContainer } from "react-toastify";
|
import { ToastContainer } from "react-toastify";
|
||||||
import "react-toastify/dist/ReactToastify.css";
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
import Login from "./pages/Login";
|
import Login from "./pages/Login";
|
||||||
|
@ -48,56 +51,55 @@ export default function App() {
|
||||||
/>
|
/>
|
||||||
<Route path="/accept-invite/:code" element={<InvitePage />} />
|
<Route path="/accept-invite/:code" element={<InvitePage />} />
|
||||||
|
|
||||||
{/* General Routes */}
|
{/* Admin */}
|
||||||
<Route
|
<Route
|
||||||
path="/general/llm-preference"
|
path="/settings/llm-preference"
|
||||||
element={<PrivateRoute Component={GeneralLLMPreference} />}
|
element={<AdminRoute Component={GeneralLLMPreference} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/general/embedding-preference"
|
path="/settings/embedding-preference"
|
||||||
element={<PrivateRoute Component={GeneralEmbeddingPreference} />}
|
element={<AdminRoute Component={GeneralEmbeddingPreference} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/general/vector-database"
|
path="/settings/vector-database"
|
||||||
element={<PrivateRoute Component={GeneralVectorDatabase} />}
|
element={<AdminRoute Component={GeneralVectorDatabase} />}
|
||||||
|
/>
|
||||||
|
{/* Manager */}
|
||||||
|
<Route
|
||||||
|
path="/settings/export-import"
|
||||||
|
element={<ManagerRoute Component={GeneralExportImport} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/general/export-import"
|
path="/settings/security"
|
||||||
element={<PrivateRoute Component={GeneralExportImport} />}
|
element={<ManagerRoute Component={GeneralSecurity} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/general/security"
|
path="/settings/appearance"
|
||||||
element={<PrivateRoute Component={GeneralSecurity} />}
|
element={<ManagerRoute Component={GeneralAppearance} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/general/appearance"
|
path="/settings/api-keys"
|
||||||
element={<PrivateRoute Component={GeneralAppearance} />}
|
element={<ManagerRoute Component={GeneralApiKeys} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/general/api-keys"
|
path="/settings/workspace-chats"
|
||||||
element={<PrivateRoute Component={GeneralApiKeys} />}
|
element={<ManagerRoute Component={GeneralChats} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/general/workspace-chats"
|
path="/settings/system-preferences"
|
||||||
element={<PrivateRoute Component={GeneralChats} />}
|
element={<ManagerRoute Component={AdminSystem} />}
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Admin Routes */}
|
|
||||||
<Route
|
|
||||||
path="/admin/system-preferences"
|
|
||||||
element={<AdminRoute Component={AdminSystem} />}
|
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/admin/invites"
|
path="/settings/invites"
|
||||||
element={<AdminRoute Component={AdminInvites} />}
|
element={<ManagerRoute Component={AdminInvites} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/admin/users"
|
path="/settings/users"
|
||||||
element={<AdminRoute Component={AdminUsers} />}
|
element={<ManagerRoute Component={AdminUsers} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/admin/workspaces"
|
path="/settings/workspaces"
|
||||||
element={<AdminRoute Component={AdminWorkspaces} />}
|
element={<ManagerRoute Component={AdminWorkspaces} />}
|
||||||
/>
|
/>
|
||||||
{/* Onboarding Flow */}
|
{/* Onboarding Flow */}
|
||||||
<Route path="/onboarding" element={<OnboardingFlow />} />
|
<Route path="/onboarding" element={<OnboardingFlow />} />
|
||||||
|
|
|
@ -14,7 +14,7 @@ export default function AnthropicAiOptions({ settings, showAlert = false }) {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href={paths.general.embeddingPreference()}
|
href={paths.settings.embeddingPreference()}
|
||||||
className="text-sm md:text-base my-2 underline"
|
className="text-sm md:text-base my-2 underline"
|
||||||
>
|
>
|
||||||
Manage embedding →
|
Manage embedding →
|
||||||
|
|
|
@ -14,7 +14,7 @@ export default function LMStudioOptions({ settings, showAlert = false }) {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href={paths.general.embeddingPreference()}
|
href={paths.settings.embeddingPreference()}
|
||||||
className="text-sm md:text-base my-2 underline"
|
className="text-sm md:text-base my-2 underline"
|
||||||
>
|
>
|
||||||
Manage embedding →
|
Manage embedding →
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { useParams } from "react-router-dom";
|
||||||
import Workspace from "../../../models/workspace";
|
import Workspace from "../../../models/workspace";
|
||||||
import System from "../../../models/system";
|
import System from "../../../models/system";
|
||||||
import { isMobile } from "react-device-detect";
|
import { isMobile } from "react-device-detect";
|
||||||
|
import useUser from "../../../hooks/useUser";
|
||||||
|
|
||||||
const DocumentSettings = lazy(() => import("./Documents"));
|
const DocumentSettings = lazy(() => import("./Documents"));
|
||||||
const WorkspaceSettings = lazy(() => import("./Settings"));
|
const WorkspaceSettings = lazy(() => import("./Settings"));
|
||||||
|
@ -117,9 +118,13 @@ const ManageWorkspace = ({ hideModal = noop, providedSlug = null }) => {
|
||||||
|
|
||||||
export default memo(ManageWorkspace);
|
export default memo(ManageWorkspace);
|
||||||
export function useManageWorkspaceModal() {
|
export function useManageWorkspaceModal() {
|
||||||
|
const { user } = useUser();
|
||||||
const [showing, setShowing] = useState(false);
|
const [showing, setShowing] = useState(false);
|
||||||
|
|
||||||
const showModal = () => {
|
const showModal = () => {
|
||||||
setShowing(true);
|
if (user?.role !== "default") {
|
||||||
|
setShowing(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
|
|
|
@ -14,6 +14,7 @@ function useIsAuthenticated() {
|
||||||
const [isAuthd, setIsAuthed] = useState(null);
|
const [isAuthd, setIsAuthed] = useState(null);
|
||||||
const [shouldRedirectToOnboarding, setShouldRedirectToOnboarding] =
|
const [shouldRedirectToOnboarding, setShouldRedirectToOnboarding] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
const [multiUserMode, setMultiUserMode] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const validateSession = async () => {
|
const validateSession = async () => {
|
||||||
|
@ -25,6 +26,8 @@ function useIsAuthenticated() {
|
||||||
AzureOpenAiKey = false,
|
AzureOpenAiKey = false,
|
||||||
} = await System.keys();
|
} = await System.keys();
|
||||||
|
|
||||||
|
setMultiUserMode(MultiUserMode);
|
||||||
|
|
||||||
// Check for the onboarding redirect condition
|
// Check for the onboarding redirect condition
|
||||||
if (
|
if (
|
||||||
!MultiUserMode &&
|
!MultiUserMode &&
|
||||||
|
@ -77,11 +80,14 @@ function useIsAuthenticated() {
|
||||||
validateSession();
|
validateSession();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return { isAuthd, shouldRedirectToOnboarding };
|
return { isAuthd, shouldRedirectToOnboarding, multiUserMode };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allows only admin to access the route and if in single user mode,
|
||||||
|
// allows all users to access the route
|
||||||
export function AdminRoute({ Component }) {
|
export function AdminRoute({ Component }) {
|
||||||
const { isAuthd, shouldRedirectToOnboarding } = useIsAuthenticated();
|
const { isAuthd, shouldRedirectToOnboarding, multiUserMode } =
|
||||||
|
useIsAuthenticated();
|
||||||
if (isAuthd === null) return <FullScreenLoader />;
|
if (isAuthd === null) return <FullScreenLoader />;
|
||||||
|
|
||||||
if (shouldRedirectToOnboarding) {
|
if (shouldRedirectToOnboarding) {
|
||||||
|
@ -89,7 +95,28 @@ export function AdminRoute({ Component }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = userFromStorage();
|
const user = userFromStorage();
|
||||||
return isAuthd && user?.role === "admin" ? (
|
return isAuthd && (user?.role === "admin" || !multiUserMode) ? (
|
||||||
|
<UserMenu>
|
||||||
|
<Component />
|
||||||
|
</UserMenu>
|
||||||
|
) : (
|
||||||
|
<Navigate to={paths.home()} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows manager and admin to access the route and if in single user mode,
|
||||||
|
// allows all users to access the route
|
||||||
|
export function ManagerRoute({ Component }) {
|
||||||
|
const { isAuthd, shouldRedirectToOnboarding, multiUserMode } =
|
||||||
|
useIsAuthenticated();
|
||||||
|
if (isAuthd === null) return <FullScreenLoader />;
|
||||||
|
|
||||||
|
if (shouldRedirectToOnboarding) {
|
||||||
|
return <Navigate to={paths.onboarding()} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = userFromStorage();
|
||||||
|
return isAuthd && (user?.role !== "default" || !multiUserMode) ? (
|
||||||
<UserMenu>
|
<UserMenu>
|
||||||
<Component />
|
<Component />
|
||||||
</UserMenu>
|
</UserMenu>
|
||||||
|
|
|
@ -65,96 +65,84 @@ export default function SettingsSidebar() {
|
||||||
<div className="h-[100%] flex flex-col w-full justify-between pt-4 overflow-y-hidden">
|
<div className="h-[100%] flex flex-col w-full justify-between pt-4 overflow-y-hidden">
|
||||||
<div className="h-auto sidebar-items">
|
<div className="h-auto sidebar-items">
|
||||||
<div className="flex flex-col gap-y-2 h-[65vh] pb-8 overflow-y-scroll no-scroll">
|
<div className="flex flex-col gap-y-2 h-[65vh] pb-8 overflow-y-scroll no-scroll">
|
||||||
{/* Admin Settings */}
|
{/* Admin/manager Multi-user Settings */}
|
||||||
{user?.role === "admin" && (
|
{!!user && user?.role !== "default" && (
|
||||||
<>
|
<>
|
||||||
<Option
|
<Option
|
||||||
href={paths.admin.system()}
|
href={paths.settings.system()}
|
||||||
btnText="System Preferences"
|
btnText="System Preferences"
|
||||||
icon={<SquaresFour className="h-5 w-5 flex-shrink-0" />}
|
icon={<SquaresFour className="h-5 w-5 flex-shrink-0" />}
|
||||||
/>
|
/>
|
||||||
<Option
|
<Option
|
||||||
href={paths.admin.invites()}
|
href={paths.settings.invites()}
|
||||||
btnText="Invitation"
|
btnText="Invitation"
|
||||||
icon={
|
icon={
|
||||||
<EnvelopeSimple className="h-5 w-5 flex-shrink-0" />
|
<EnvelopeSimple className="h-5 w-5 flex-shrink-0" />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Option
|
<Option
|
||||||
href={paths.admin.users()}
|
href={paths.settings.users()}
|
||||||
btnText="Users"
|
btnText="Users"
|
||||||
icon={<Users className="h-5 w-5 flex-shrink-0" />}
|
icon={<Users className="h-5 w-5 flex-shrink-0" />}
|
||||||
/>
|
/>
|
||||||
<Option
|
<Option
|
||||||
href={paths.admin.workspaces()}
|
href={paths.settings.workspaces()}
|
||||||
btnText="Workspaces"
|
btnText="Workspaces"
|
||||||
icon={<BookOpen className="h-5 w-5 flex-shrink-0" />}
|
icon={<BookOpen className="h-5 w-5 flex-shrink-0" />}
|
||||||
/>
|
/>
|
||||||
<Option
|
|
||||||
href={paths.general.chats()}
|
|
||||||
btnText="Workspace Chat"
|
|
||||||
icon={
|
|
||||||
<ChatCenteredText className="h-5 w-5 flex-shrink-0" />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* General Settings */}
|
|
||||||
<Option
|
<Option
|
||||||
href={paths.general.appearance()}
|
href={paths.settings.chats()}
|
||||||
|
btnText="Workspace Chat"
|
||||||
|
icon={<ChatCenteredText className="h-5 w-5 flex-shrink-0" />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Option
|
||||||
|
href={paths.settings.appearance()}
|
||||||
btnText="Appearance"
|
btnText="Appearance"
|
||||||
icon={<Eye className="h-5 w-5 flex-shrink-0" />}
|
icon={<Eye className="h-5 w-5 flex-shrink-0" />}
|
||||||
/>
|
/>
|
||||||
<Option
|
<Option
|
||||||
href={paths.general.apiKeys()}
|
href={paths.settings.apiKeys()}
|
||||||
btnText="API Keys"
|
btnText="API Keys"
|
||||||
icon={<Key className="h-5 w-5 flex-shrink-0" />}
|
icon={<Key className="h-5 w-5 flex-shrink-0" />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{(!user || user?.role === "admin") && (
|
||||||
|
<>
|
||||||
|
<Option
|
||||||
|
href={paths.settings.llmPreference()}
|
||||||
|
btnText="LLM Preference"
|
||||||
|
icon={<ChatText className="h-5 w-5 flex-shrink-0" />}
|
||||||
|
/>
|
||||||
|
<Option
|
||||||
|
href={paths.settings.embeddingPreference()}
|
||||||
|
btnText="Embedding Preference"
|
||||||
|
icon={<FileCode className="h-5 w-5 flex-shrink-0" />}
|
||||||
|
/>
|
||||||
|
<Option
|
||||||
|
href={paths.settings.vectorDatabase()}
|
||||||
|
btnText="Vector Database"
|
||||||
|
icon={<Database className="h-5 w-5 flex-shrink-0" />}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Option
|
<Option
|
||||||
href={paths.general.llmPreference()}
|
href={paths.settings.exportImport()}
|
||||||
btnText="LLM Preference"
|
|
||||||
icon={<ChatText className="h-5 w-5 flex-shrink-0" />}
|
|
||||||
/>
|
|
||||||
<Option
|
|
||||||
href={paths.general.embeddingPreference()}
|
|
||||||
btnText="Embedding Preference"
|
|
||||||
icon={<FileCode className="h-5 w-5 flex-shrink-0" />}
|
|
||||||
/>
|
|
||||||
<Option
|
|
||||||
href={paths.general.vectorDatabase()}
|
|
||||||
btnText="Vector Database"
|
|
||||||
icon={<Database className="h-5 w-5 flex-shrink-0" />}
|
|
||||||
/>
|
|
||||||
<Option
|
|
||||||
href={paths.general.exportImport()}
|
|
||||||
btnText="Export or Import"
|
btnText="Export or Import"
|
||||||
icon={<DownloadSimple className="h-5 w-5 flex-shrink-0" />}
|
icon={<DownloadSimple className="h-5 w-5 flex-shrink-0" />}
|
||||||
/>
|
/>
|
||||||
{!user && (
|
|
||||||
<Option
|
|
||||||
href={paths.general.chats()}
|
|
||||||
btnText="Chat History"
|
|
||||||
icon={
|
|
||||||
<ChatCenteredText className="h-5 w-5 flex-shrink-0" />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Option
|
<Option
|
||||||
href={paths.general.security()}
|
href={paths.settings.security()}
|
||||||
btnText="Security"
|
btnText="Security"
|
||||||
icon={<Lock className="h-5 w-5 flex-shrink-0" />}
|
icon={<Lock className="h-5 w-5 flex-shrink-0" />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{/* <div className="flex flex-col gap-y-2">
|
|
||||||
<div className="w-full flex items-center justify-between">
|
|
||||||
<LLMStatus />
|
|
||||||
<IndexCount />
|
|
||||||
</div>
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex justify-center mt-2">
|
<div className="flex justify-center mt-2">
|
||||||
<div className="flex space-x-4">
|
<div className="flex space-x-4">
|
||||||
|
@ -277,73 +265,70 @@ export function SidebarMobileHeader() {
|
||||||
style={{ height: "calc(100vw - -3rem)" }}
|
style={{ height: "calc(100vw - -3rem)" }}
|
||||||
className=" flex flex-col gap-y-4 pb-8 overflow-y-scroll no-scroll"
|
className=" flex flex-col gap-y-4 pb-8 overflow-y-scroll no-scroll"
|
||||||
>
|
>
|
||||||
{user?.role === "admin" && (
|
|
||||||
<>
|
|
||||||
<Option
|
|
||||||
href={paths.admin.system()}
|
|
||||||
btnText="System Preferences"
|
|
||||||
icon={<SquaresFour className="h-5 w-5 flex-shrink-0" />}
|
|
||||||
/>
|
|
||||||
<Option
|
|
||||||
href={paths.admin.invites()}
|
|
||||||
btnText="Invitation"
|
|
||||||
icon={
|
|
||||||
<EnvelopeSimple className="h-5 w-5 flex-shrink-0" />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Option
|
|
||||||
href={paths.admin.users()}
|
|
||||||
btnText="Users"
|
|
||||||
icon={<Users className="h-5 w-5 flex-shrink-0" />}
|
|
||||||
/>
|
|
||||||
<Option
|
|
||||||
href={paths.admin.workspaces()}
|
|
||||||
btnText="Workspaces"
|
|
||||||
icon={<BookOpen className="h-5 w-5 flex-shrink-0" />}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* General Settings */}
|
|
||||||
<Option
|
<Option
|
||||||
href={paths.general.chats()}
|
href={paths.settings.system()}
|
||||||
|
btnText="System Preferences"
|
||||||
|
icon={<SquaresFour className="h-5 w-5 flex-shrink-0" />}
|
||||||
|
/>
|
||||||
|
<Option
|
||||||
|
href={paths.settings.invites()}
|
||||||
|
btnText="Invitation"
|
||||||
|
icon={<EnvelopeSimple className="h-5 w-5 flex-shrink-0" />}
|
||||||
|
/>
|
||||||
|
<Option
|
||||||
|
href={paths.settings.users()}
|
||||||
|
btnText="Users"
|
||||||
|
icon={<Users className="h-5 w-5 flex-shrink-0" />}
|
||||||
|
/>
|
||||||
|
<Option
|
||||||
|
href={paths.settings.workspaces()}
|
||||||
|
btnText="Workspaces"
|
||||||
|
icon={<BookOpen className="h-5 w-5 flex-shrink-0" />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Option
|
||||||
|
href={paths.settings.chats()}
|
||||||
btnText="Workspace Chat"
|
btnText="Workspace Chat"
|
||||||
icon={
|
icon={
|
||||||
<ChatCenteredText className="h-5 w-5 flex-shrink-0" />
|
<ChatCenteredText className="h-5 w-5 flex-shrink-0" />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Option
|
<Option
|
||||||
href={paths.general.appearance()}
|
href={paths.settings.appearance()}
|
||||||
btnText="Appearance"
|
btnText="Appearance"
|
||||||
icon={<Eye className="h-5 w-5 flex-shrink-0" />}
|
icon={<Eye className="h-5 w-5 flex-shrink-0" />}
|
||||||
/>
|
/>
|
||||||
<Option
|
<Option
|
||||||
href={paths.general.apiKeys()}
|
href={paths.settings.apiKeys()}
|
||||||
btnText="API Keys"
|
btnText="API Keys"
|
||||||
icon={<Key className="h-5 w-5 flex-shrink-0" />}
|
icon={<Key className="h-5 w-5 flex-shrink-0" />}
|
||||||
/>
|
/>
|
||||||
|
{(!user || user?.role === "admin") && (
|
||||||
|
<>
|
||||||
|
<Option
|
||||||
|
href={paths.settings.llmPreference()}
|
||||||
|
btnText="LLM Preference"
|
||||||
|
icon={<ChatText className="h-5 w-5 flex-shrink-0" />}
|
||||||
|
/>
|
||||||
|
<Option
|
||||||
|
href={paths.settings.embeddingPreference()}
|
||||||
|
btnText="Embedding Preference"
|
||||||
|
icon={<FileCode className="h-5 w-5 flex-shrink-0" />}
|
||||||
|
/>
|
||||||
|
<Option
|
||||||
|
href={paths.settings.vectorDatabase()}
|
||||||
|
btnText="Vector Database"
|
||||||
|
icon={<Database className="h-5 w-5 flex-shrink-0" />}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Option
|
<Option
|
||||||
href={paths.general.llmPreference()}
|
href={paths.settings.exportImport()}
|
||||||
btnText="LLM Preference"
|
|
||||||
icon={<ChatText className="h-5 w-5 flex-shrink-0" />}
|
|
||||||
/>
|
|
||||||
<Option
|
|
||||||
href={paths.general.embeddingPreference()}
|
|
||||||
btnText="Embedding Preference"
|
|
||||||
icon={<FileCode className="h-5 w-5 flex-shrink-0" />}
|
|
||||||
/>
|
|
||||||
<Option
|
|
||||||
href={paths.general.vectorDatabase()}
|
|
||||||
btnText="Vector Database"
|
|
||||||
icon={<Database className="h-5 w-5 flex-shrink-0" />}
|
|
||||||
/>
|
|
||||||
<Option
|
|
||||||
href={paths.general.exportImport()}
|
|
||||||
btnText="Export or Import"
|
btnText="Export or Import"
|
||||||
icon={<DownloadSimple className="h-5 w-5 flex-shrink-0" />}
|
icon={<DownloadSimple className="h-5 w-5 flex-shrink-0" />}
|
||||||
/>
|
/>
|
||||||
<Option
|
<Option
|
||||||
href={paths.general.security()}
|
href={paths.settings.security()}
|
||||||
btnText="Security"
|
btnText="Security"
|
||||||
icon={<Lock className="h-5 w-5 flex-shrink-0" />}
|
icon={<Lock className="h-5 w-5 flex-shrink-0" />}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import paths from "../../../utils/paths";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { GearSix, SquaresFour } from "@phosphor-icons/react";
|
import { GearSix, SquaresFour } from "@phosphor-icons/react";
|
||||||
import truncate from "truncate";
|
import truncate from "truncate";
|
||||||
|
import useUser from "../../../hooks/useUser";
|
||||||
|
|
||||||
export default function ActiveWorkspaces() {
|
export default function ActiveWorkspaces() {
|
||||||
const { slug } = useParams();
|
const { slug } = useParams();
|
||||||
|
@ -17,6 +18,7 @@ export default function ActiveWorkspaces() {
|
||||||
const [workspaces, setWorkspaces] = useState([]);
|
const [workspaces, setWorkspaces] = useState([]);
|
||||||
const [selectedWs, setSelectedWs] = useState(null);
|
const [selectedWs, setSelectedWs] = useState(null);
|
||||||
const { showing, showModal, hideModal } = useManageWorkspaceModal();
|
const { showing, showModal, hideModal } = useManageWorkspaceModal();
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getWorkspaces() {
|
async function getWorkspaces() {
|
||||||
|
@ -90,7 +92,7 @@ export default function ActiveWorkspaces() {
|
||||||
>
|
>
|
||||||
<GearSix
|
<GearSix
|
||||||
weight={settingHover ? "fill" : "regular"}
|
weight={settingHover ? "fill" : "regular"}
|
||||||
hidden={!isActive}
|
hidden={!isActive || user?.role === "default"}
|
||||||
className="h-[20px] w-[20px] transition-all duration-300"
|
className="h-[20px] w-[20px] transition-all duration-300"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -15,8 +15,10 @@ import ActiveWorkspaces from "./ActiveWorkspaces";
|
||||||
import paths from "../../utils/paths";
|
import paths from "../../utils/paths";
|
||||||
import { USER_BACKGROUND_COLOR } from "../../utils/constants";
|
import { USER_BACKGROUND_COLOR } from "../../utils/constants";
|
||||||
import useLogo from "../../hooks/useLogo";
|
import useLogo from "../../hooks/useLogo";
|
||||||
|
import useUser from "../../hooks/useUser";
|
||||||
|
|
||||||
export default function Sidebar() {
|
export default function Sidebar() {
|
||||||
|
const { user } = useUser();
|
||||||
const { logo } = useLogo();
|
const { logo } = useLogo();
|
||||||
const sidebarRef = useRef(null);
|
const sidebarRef = useRef(null);
|
||||||
const {
|
const {
|
||||||
|
@ -43,25 +45,28 @@ export default function Sidebar() {
|
||||||
style={{ objectFit: "contain" }}
|
style={{ objectFit: "contain" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-x-2 items-center text-slate-200">
|
{(!user || user?.role !== "default") && (
|
||||||
{/* <AdminHome /> */}
|
<div className="flex gap-x-2 items-center text-slate-200">
|
||||||
<SettingsButton />
|
<SettingsButton />
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Primary Body */}
|
{/* Primary Body */}
|
||||||
<div className="flex-grow flex flex-col">
|
<div className="flex-grow flex flex-col">
|
||||||
<div className="flex flex-col gap-y-4 pb-8 overflow-y-scroll no-scroll">
|
<div className="flex flex-col gap-y-4 pb-8 overflow-y-scroll no-scroll">
|
||||||
<div className="flex gap-x-2 items-center justify-between">
|
<div className="flex gap-x-2 items-center justify-between">
|
||||||
<button
|
{(!user || user?.role !== "default") && (
|
||||||
onClick={showNewWsModal}
|
<button
|
||||||
className="flex flex-grow w-[75%] h-[44px] gap-x-2 py-[5px] px-4 bg-white rounded-lg text-sidebar justify-center items-center hover:bg-opacity-80 transition-all duration-300"
|
onClick={showNewWsModal}
|
||||||
>
|
className="flex flex-grow w-[75%] h-[44px] gap-x-2 py-[5px] px-4 bg-white rounded-lg text-sidebar justify-center items-center hover:bg-opacity-80 transition-all duration-300"
|
||||||
<Plus className="h-5 w-5" />
|
>
|
||||||
<p className="text-sidebar text-sm font-semibold">
|
<Plus className="h-5 w-5" />
|
||||||
New Workspace
|
<p className="text-sidebar text-sm font-semibold">
|
||||||
</p>
|
New Workspace
|
||||||
</button>
|
</p>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ActiveWorkspaces />
|
<ActiveWorkspaces />
|
||||||
</div>
|
</div>
|
||||||
|
@ -133,6 +138,7 @@ export function SidebarMobileHeader() {
|
||||||
showModal: showNewWsModal,
|
showModal: showNewWsModal,
|
||||||
hideModal: hideNewWsModal,
|
hideModal: hideNewWsModal,
|
||||||
} = useNewWorkspaceModal();
|
} = useNewWorkspaceModal();
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Darkens the rest of the screen
|
// Darkens the rest of the screen
|
||||||
|
@ -197,9 +203,11 @@ export function SidebarMobileHeader() {
|
||||||
style={{ objectFit: "contain" }}
|
style={{ objectFit: "contain" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-x-2 items-center text-slate-500 shink-0">
|
{(!user || user?.role !== "default") && (
|
||||||
<SettingsButton />
|
<div className="flex gap-x-2 items-center text-slate-500 shink-0">
|
||||||
</div>
|
<SettingsButton />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Primary Body */}
|
{/* Primary Body */}
|
||||||
|
@ -210,15 +218,17 @@ export function SidebarMobileHeader() {
|
||||||
className=" flex flex-col gap-y-4 pb-8 overflow-y-scroll no-scroll"
|
className=" flex flex-col gap-y-4 pb-8 overflow-y-scroll no-scroll"
|
||||||
>
|
>
|
||||||
<div className="flex gap-x-2 items-center justify-between">
|
<div className="flex gap-x-2 items-center justify-between">
|
||||||
<button
|
{(!user || user?.role !== "default") && (
|
||||||
onClick={showNewWsModal}
|
<button
|
||||||
className="flex flex-grow w-[75%] h-[44px] gap-x-2 py-[5px] px-4 bg-white rounded-lg text-sidebar justify-center items-center hover:bg-opacity-80 transition-all duration-300"
|
onClick={showNewWsModal}
|
||||||
>
|
className="flex flex-grow w-[75%] h-[44px] gap-x-2 py-[5px] px-4 bg-white rounded-lg text-sidebar justify-center items-center hover:bg-opacity-80 transition-all duration-300"
|
||||||
<Plus className="h-5 w-5" />
|
>
|
||||||
<p className="text-sidebar text-sm font-semibold">
|
<Plus className="h-5 w-5" />
|
||||||
New Workspace
|
<p className="text-sidebar text-sm font-semibold">
|
||||||
</p>
|
New Workspace
|
||||||
</button>
|
</p>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ActiveWorkspaces />
|
<ActiveWorkspaces />
|
||||||
</div>
|
</div>
|
||||||
|
@ -266,7 +276,7 @@ export function SidebarMobileHeader() {
|
||||||
function SettingsButton() {
|
function SettingsButton() {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
href={paths.general.llmPreference()}
|
href={paths.settings.system()}
|
||||||
className="transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
|
className="transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
|
||||||
>
|
>
|
||||||
<Wrench className="h-4 w-4" weight="fill" />
|
<Wrench className="h-4 w-4" weight="fill" />
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { isMobile } from "react-device-detect";
|
||||||
import ManageWorkspace, {
|
import ManageWorkspace, {
|
||||||
useManageWorkspaceModal,
|
useManageWorkspaceModal,
|
||||||
} from "../../../Modals/MangeWorkspace";
|
} from "../../../Modals/MangeWorkspace";
|
||||||
|
import useUser from "../../../../hooks/useUser";
|
||||||
|
|
||||||
export default function PromptInput({
|
export default function PromptInput({
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -22,6 +23,7 @@ export default function PromptInput({
|
||||||
const { showing, showModal, hideModal } = useManageWorkspaceModal();
|
const { showing, showModal, hideModal } = useManageWorkspaceModal();
|
||||||
const formRef = useRef(null);
|
const formRef = useRef(null);
|
||||||
const [_, setFocused] = useState(false);
|
const [_, setFocused] = useState(false);
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
setFocused(false);
|
setFocused(false);
|
||||||
|
@ -86,11 +88,14 @@ export default function PromptInput({
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between py-3.5">
|
<div className="flex justify-between py-3.5">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Gear
|
{user?.role !== "default" && (
|
||||||
onClick={showModal}
|
<Gear
|
||||||
className="w-7 h-7 text-white/60 hover:text-white cursor-pointer"
|
onClick={showModal}
|
||||||
weight="fill"
|
className="w-7 h-7 text-white/60 hover:text-white cursor-pointer"
|
||||||
/>
|
weight="fill"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<ChatModeSelector workspace={workspace} />
|
<ChatModeSelector workspace={workspace} />
|
||||||
{/* <TextT
|
{/* <TextT
|
||||||
className="w-7 h-7 text-white/30 cursor-not-allowed"
|
className="w-7 h-7 text-white/30 cursor-not-allowed"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { X } from "@phosphor-icons/react";
|
import { X } from "@phosphor-icons/react";
|
||||||
import Admin from "../../../../models/admin";
|
import Admin from "../../../../models/admin";
|
||||||
|
import { userFromStorage } from "../../../../utils/request";
|
||||||
|
import { RoleHintDisplay } from "..";
|
||||||
|
|
||||||
const DIALOG_ID = `new-user-modal`;
|
const DIALOG_ID = `new-user-modal`;
|
||||||
|
|
||||||
|
@ -11,6 +13,7 @@ function hideModal() {
|
||||||
export const NewUserModalId = DIALOG_ID;
|
export const NewUserModalId = DIALOG_ID;
|
||||||
export default function NewUserModal() {
|
export default function NewUserModal() {
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
const [role, setRole] = useState("default");
|
||||||
const handleCreate = async (e) => {
|
const handleCreate = async (e) => {
|
||||||
setError(null);
|
setError(null);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -22,6 +25,8 @@ export default function NewUserModal() {
|
||||||
setError(error);
|
setError(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const user = userFromStorage();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog id={DIALOG_ID} className="bg-transparent outline-none">
|
<dialog id={DIALOG_ID} className="bg-transparent outline-none">
|
||||||
<div className="relative w-full max-w-2xl max-h-full">
|
<div className="relative w-full max-w-2xl max-h-full">
|
||||||
|
@ -87,11 +92,16 @@ export default function NewUserModal() {
|
||||||
name="role"
|
name="role"
|
||||||
required={true}
|
required={true}
|
||||||
defaultValue={"default"}
|
defaultValue={"default"}
|
||||||
|
onChange={(e) => setRole(e.target.value)}
|
||||||
className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border border-gray-500 focus:ring-blue-500 focus:border-blue-500"
|
className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border border-gray-500 focus:ring-blue-500 focus:border-blue-500"
|
||||||
>
|
>
|
||||||
<option value="default">Default</option>
|
<option value="default">Default</option>
|
||||||
<option value="admin">Administrator</option>
|
<option value="manager">Manager </option>
|
||||||
|
{user?.role === "admin" && (
|
||||||
|
<option value="admin">Administrator</option>
|
||||||
|
)}
|
||||||
</select>
|
</select>
|
||||||
|
<RoleHintDisplay role={role} />
|
||||||
</div>
|
</div>
|
||||||
{error && (
|
{error && (
|
||||||
<p className="text-red-400 text-sm">Error: {error}</p>
|
<p className="text-red-400 text-sm">Error: {error}</p>
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { X } from "@phosphor-icons/react";
|
import { X } from "@phosphor-icons/react";
|
||||||
import Admin from "../../../../../models/admin";
|
import Admin from "../../../../../models/admin";
|
||||||
|
import { RoleHintDisplay } from "../..";
|
||||||
|
|
||||||
export const EditUserModalId = (user) => `edit-user-${user.id}-modal`;
|
export const EditUserModalId = (user) => `edit-user-${user.id}-modal`;
|
||||||
|
|
||||||
export default function EditUserModal({ user }) {
|
export default function EditUserModal({ currentUser, user }) {
|
||||||
|
const [role, setRole] = useState(user.role);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
|
@ -90,11 +92,16 @@ export default function EditUserModal({ user }) {
|
||||||
name="role"
|
name="role"
|
||||||
required={true}
|
required={true}
|
||||||
defaultValue={user.role}
|
defaultValue={user.role}
|
||||||
|
onChange={(e) => setRole(e.target.value)}
|
||||||
className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border border-gray-500 focus:ring-blue-500 focus:border-blue-500"
|
className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border border-gray-500 focus:ring-blue-500 focus:border-blue-500"
|
||||||
>
|
>
|
||||||
<option value="default">Default</option>
|
<option value="default">Default</option>
|
||||||
<option value="admin">Administrator</option>
|
<option value="manager">Manager</option>
|
||||||
|
{currentUser?.role === "admin" && (
|
||||||
|
<option value="admin">Administrator</option>
|
||||||
|
)}
|
||||||
</select>
|
</select>
|
||||||
|
<RoleHintDisplay role={role} />
|
||||||
</div>
|
</div>
|
||||||
{error && (
|
{error && (
|
||||||
<p className="text-red-400 text-sm">Error: {error}</p>
|
<p className="text-red-400 text-sm">Error: {error}</p>
|
||||||
|
|
|
@ -40,15 +40,17 @@ export default function UserRow({ currUser, user }) {
|
||||||
<td className="px-6 py-4">{titleCase(user.role)}</td>
|
<td className="px-6 py-4">{titleCase(user.role)}</td>
|
||||||
<td className="px-6 py-4">{user.createdAt}</td>
|
<td className="px-6 py-4">{user.createdAt}</td>
|
||||||
<td className="px-6 py-4 flex items-center gap-x-6">
|
<td className="px-6 py-4 flex items-center gap-x-6">
|
||||||
<button
|
{currUser?.role !== "default" && (
|
||||||
onClick={() =>
|
<button
|
||||||
document?.getElementById(EditUserModalId(user))?.showModal()
|
onClick={() =>
|
||||||
}
|
document?.getElementById(EditUserModalId(user))?.showModal()
|
||||||
className="font-medium text-white text-opacity-80 rounded-lg hover:text-white px-2 py-1 hover:text-opacity-60 hover:bg-white hover:bg-opacity-10"
|
}
|
||||||
>
|
className="font-medium text-white text-opacity-80 rounded-lg hover:text-white px-2 py-1 hover:text-opacity-60 hover:bg-white hover:bg-opacity-10"
|
||||||
<DotsThreeOutline weight="fill" className="h-5 w-5" />
|
>
|
||||||
</button>
|
<DotsThreeOutline weight="fill" className="h-5 w-5" />
|
||||||
{currUser.id !== user.id && (
|
</button>
|
||||||
|
)}
|
||||||
|
{currUser?.id !== user.id && currUser?.role !== "default" && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={handleSuspend}
|
onClick={handleSuspend}
|
||||||
|
@ -66,7 +68,7 @@ export default function UserRow({ currUser, user }) {
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<EditUserModal user={user} />
|
<EditUserModal currentUser={currUser} user={user} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,3 +100,35 @@ function UsersContainer() {
|
||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ROLE_HINT = {
|
||||||
|
default: [
|
||||||
|
"Can only send chats with workspaces they are added to by admin or managers.",
|
||||||
|
"Cannot modify any settings at all.",
|
||||||
|
],
|
||||||
|
manager: [
|
||||||
|
"Can view all workspaces and modify all settings.",
|
||||||
|
"Cannot modify LLM, vectorDB, embedding, or other connections.",
|
||||||
|
],
|
||||||
|
admin: [
|
||||||
|
"Highest user level privilege.",
|
||||||
|
"Can see and do everything across the system.",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export function RoleHintDisplay({ role }) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-y-1 py-1 pb-4">
|
||||||
|
<p className="text-white/60 font-semibold text-sm">Permissions</p>
|
||||||
|
<ul className="flex flex-col gap-y-1 list-disc px-4">
|
||||||
|
{ROLE_HINT[role ?? "default"].map((hints, i) => {
|
||||||
|
return (
|
||||||
|
<li key={i} className="text-xs text-white/60">
|
||||||
|
{hints}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ function MultiUserMode() {
|
||||||
window.localStorage.removeItem(AUTH_USER);
|
window.localStorage.removeItem(AUTH_USER);
|
||||||
window.localStorage.removeItem(AUTH_TOKEN);
|
window.localStorage.removeItem(AUTH_TOKEN);
|
||||||
window.localStorage.removeItem(AUTH_TIMESTAMP);
|
window.localStorage.removeItem(AUTH_TIMESTAMP);
|
||||||
window.location = paths.admin.users();
|
window.location = paths.settings.users();
|
||||||
}, 2_000);
|
}, 2_000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,47 +39,42 @@ export default {
|
||||||
apiDocs: () => {
|
apiDocs: () => {
|
||||||
return `${API_BASE}/docs`;
|
return `${API_BASE}/docs`;
|
||||||
},
|
},
|
||||||
general: {
|
settings: {
|
||||||
llmPreference: () => {
|
|
||||||
return "/general/llm-preference";
|
|
||||||
},
|
|
||||||
embeddingPreference: () => {
|
|
||||||
return "/general/embedding-preference";
|
|
||||||
},
|
|
||||||
vectorDatabase: () => {
|
|
||||||
return "/general/vector-database";
|
|
||||||
},
|
|
||||||
exportImport: () => {
|
|
||||||
return "/general/export-import";
|
|
||||||
},
|
|
||||||
security: () => {
|
|
||||||
return "/general/security";
|
|
||||||
},
|
|
||||||
appearance: () => {
|
|
||||||
return "/general/appearance";
|
|
||||||
},
|
|
||||||
apiKeys: () => {
|
|
||||||
return "/general/api-keys";
|
|
||||||
},
|
|
||||||
chats: () => {
|
|
||||||
return "/general/workspace-chats";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
admin: {
|
|
||||||
system: () => {
|
system: () => {
|
||||||
return `/admin/system-preferences`;
|
return `/settings/system-preferences`;
|
||||||
},
|
},
|
||||||
users: () => {
|
users: () => {
|
||||||
return `/admin/users`;
|
return `/settings/users`;
|
||||||
},
|
},
|
||||||
invites: () => {
|
invites: () => {
|
||||||
return `/admin/invites`;
|
return `/settings/invites`;
|
||||||
},
|
},
|
||||||
workspaces: () => {
|
workspaces: () => {
|
||||||
return `/admin/workspaces`;
|
return `/settings/workspaces`;
|
||||||
},
|
},
|
||||||
chats: () => {
|
chats: () => {
|
||||||
return "/admin/workspace-chats";
|
return "/settings/workspace-chats";
|
||||||
|
},
|
||||||
|
llmPreference: () => {
|
||||||
|
return "/settings/llm-preference";
|
||||||
|
},
|
||||||
|
embeddingPreference: () => {
|
||||||
|
return "/settings/embedding-preference";
|
||||||
|
},
|
||||||
|
vectorDatabase: () => {
|
||||||
|
return "/settings/vector-database";
|
||||||
|
},
|
||||||
|
exportImport: () => {
|
||||||
|
return "/settings/export-import";
|
||||||
|
},
|
||||||
|
security: () => {
|
||||||
|
return "/settings/security";
|
||||||
|
},
|
||||||
|
appearance: () => {
|
||||||
|
return "/settings/appearance";
|
||||||
|
},
|
||||||
|
apiKeys: () => {
|
||||||
|
return "/settings/api-keys";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,41 +7,37 @@ const { DocumentVectors } = require("../models/vectors");
|
||||||
const { Workspace } = require("../models/workspace");
|
const { Workspace } = require("../models/workspace");
|
||||||
const { WorkspaceChats } = require("../models/workspaceChats");
|
const { WorkspaceChats } = require("../models/workspaceChats");
|
||||||
const { getVectorDbClass } = require("../utils/helpers");
|
const { getVectorDbClass } = require("../utils/helpers");
|
||||||
const { userFromSession, reqBody } = require("../utils/http");
|
const { reqBody, userFromSession } = require("../utils/http");
|
||||||
|
const {
|
||||||
|
strictMultiUserRoleValid,
|
||||||
|
} = require("../utils/middleware/multiUserProtected");
|
||||||
const { validatedRequest } = require("../utils/middleware/validatedRequest");
|
const { validatedRequest } = require("../utils/middleware/validatedRequest");
|
||||||
|
|
||||||
function adminEndpoints(app) {
|
function adminEndpoints(app) {
|
||||||
if (!app) return;
|
if (!app) return;
|
||||||
|
|
||||||
app.get("/admin/users", [validatedRequest], async (request, response) => {
|
app.get(
|
||||||
try {
|
"/admin/users",
|
||||||
const user = await userFromSession(request, response);
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
if (!user || user?.role !== "admin") {
|
async (_request, response) => {
|
||||||
response.sendStatus(401).end();
|
try {
|
||||||
return;
|
const users = (await User.where()).map((user) => {
|
||||||
|
const { password, ...rest } = user;
|
||||||
|
return rest;
|
||||||
|
});
|
||||||
|
response.status(200).json({ users });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
response.sendStatus(500).end();
|
||||||
}
|
}
|
||||||
const users = (await User.where()).map((user) => {
|
|
||||||
const { password, ...rest } = user;
|
|
||||||
return rest;
|
|
||||||
});
|
|
||||||
response.status(200).json({ users });
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
response.sendStatus(500).end();
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
"/admin/users/new",
|
"/admin/users/new",
|
||||||
[validatedRequest],
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const user = await userFromSession(request, response);
|
|
||||||
if (!user || user?.role !== "admin") {
|
|
||||||
response.sendStatus(401).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newUserParams = reqBody(request);
|
const newUserParams = reqBody(request);
|
||||||
const { user: newUser, error } = await User.create(newUserParams);
|
const { user: newUser, error } = await User.create(newUserParams);
|
||||||
response.status(200).json({ user: newUser, error });
|
response.status(200).json({ user: newUser, error });
|
||||||
|
@ -52,34 +48,27 @@ function adminEndpoints(app) {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
app.post("/admin/user/:id", [validatedRequest], async (request, response) => {
|
app.post(
|
||||||
try {
|
"/admin/user/:id",
|
||||||
const user = await userFromSession(request, response);
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
if (!user || user?.role !== "admin") {
|
async (request, response) => {
|
||||||
response.sendStatus(401).end();
|
try {
|
||||||
return;
|
const { id } = request.params;
|
||||||
|
const updates = reqBody(request);
|
||||||
|
const { success, error } = await User.update(id, updates);
|
||||||
|
response.status(200).json({ success, error });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
response.sendStatus(500).end();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = request.params;
|
|
||||||
const updates = reqBody(request);
|
|
||||||
const { success, error } = await User.update(id, updates);
|
|
||||||
response.status(200).json({ success, error });
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
response.sendStatus(500).end();
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
app.delete(
|
app.delete(
|
||||||
"/admin/user/:id",
|
"/admin/user/:id",
|
||||||
[validatedRequest],
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const user = await userFromSession(request, response);
|
|
||||||
if (!user || user?.role !== "admin") {
|
|
||||||
response.sendStatus(401).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
await User.delete({ id: Number(id) });
|
await User.delete({ id: Number(id) });
|
||||||
response.status(200).json({ success: true, error: null });
|
response.status(200).json({ success: true, error: null });
|
||||||
|
@ -90,33 +79,26 @@ function adminEndpoints(app) {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get("/admin/invites", [validatedRequest], async (request, response) => {
|
app.get(
|
||||||
try {
|
"/admin/invites",
|
||||||
const user = await userFromSession(request, response);
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
if (!user || user?.role !== "admin") {
|
async (_request, response) => {
|
||||||
response.sendStatus(401).end();
|
try {
|
||||||
return;
|
const invites = await Invite.whereWithUsers();
|
||||||
|
response.status(200).json({ invites });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
response.sendStatus(500).end();
|
||||||
}
|
}
|
||||||
|
|
||||||
const invites = await Invite.whereWithUsers();
|
|
||||||
response.status(200).json({ invites });
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
response.sendStatus(500).end();
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
"/admin/invite/new",
|
"/admin/invite/new",
|
||||||
[validatedRequest],
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const user = await userFromSession(request, response);
|
const user = await userFromSession(request, response);
|
||||||
if (!user || user?.role !== "admin") {
|
|
||||||
response.sendStatus(401).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { invite, error } = await Invite.create(user.id);
|
const { invite, error } = await Invite.create(user.id);
|
||||||
response.status(200).json({ invite, error });
|
response.status(200).json({ invite, error });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -128,15 +110,9 @@ function adminEndpoints(app) {
|
||||||
|
|
||||||
app.delete(
|
app.delete(
|
||||||
"/admin/invite/:id",
|
"/admin/invite/:id",
|
||||||
[validatedRequest],
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const user = await userFromSession(request, response);
|
|
||||||
if (!user || user?.role !== "admin") {
|
|
||||||
response.sendStatus(401).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { success, error } = await Invite.deactivate(id);
|
const { success, error } = await Invite.deactivate(id);
|
||||||
response.status(200).json({ success, error });
|
response.status(200).json({ success, error });
|
||||||
|
@ -149,14 +125,9 @@ function adminEndpoints(app) {
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
"/admin/workspaces",
|
"/admin/workspaces",
|
||||||
[validatedRequest],
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
async (request, response) => {
|
async (_request, response) => {
|
||||||
try {
|
try {
|
||||||
const user = await userFromSession(request, response);
|
|
||||||
if (!user || user?.role !== "admin") {
|
|
||||||
response.sendStatus(401).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const workspaces = await Workspace.whereWithUsers();
|
const workspaces = await Workspace.whereWithUsers();
|
||||||
response.status(200).json({ workspaces });
|
response.status(200).json({ workspaces });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -168,14 +139,10 @@ function adminEndpoints(app) {
|
||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
"/admin/workspaces/new",
|
"/admin/workspaces/new",
|
||||||
[validatedRequest],
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const user = await userFromSession(request, response);
|
const user = await userFromSession(request, response);
|
||||||
if (!user || user?.role !== "admin") {
|
|
||||||
response.sendStatus(401).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { name } = reqBody(request);
|
const { name } = reqBody(request);
|
||||||
const { workspace, message: error } = await Workspace.new(
|
const { workspace, message: error } = await Workspace.new(
|
||||||
name,
|
name,
|
||||||
|
@ -191,15 +158,9 @@ function adminEndpoints(app) {
|
||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
"/admin/workspaces/:workspaceId/update-users",
|
"/admin/workspaces/:workspaceId/update-users",
|
||||||
[validatedRequest],
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const user = await userFromSession(request, response);
|
|
||||||
if (!user || user?.role !== "admin") {
|
|
||||||
response.sendStatus(401).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { workspaceId } = request.params;
|
const { workspaceId } = request.params;
|
||||||
const { userIds } = reqBody(request);
|
const { userIds } = reqBody(request);
|
||||||
const { success, error } = await Workspace.updateUsers(
|
const { success, error } = await Workspace.updateUsers(
|
||||||
|
@ -216,15 +177,9 @@ function adminEndpoints(app) {
|
||||||
|
|
||||||
app.delete(
|
app.delete(
|
||||||
"/admin/workspaces/:id",
|
"/admin/workspaces/:id",
|
||||||
[validatedRequest],
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const user = await userFromSession(request, response);
|
|
||||||
if (!user || user?.role !== "admin") {
|
|
||||||
response.sendStatus(401).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const VectorDb = getVectorDbClass();
|
const VectorDb = getVectorDbClass();
|
||||||
const workspace = await Workspace.get({ id: Number(id) });
|
const workspace = await Workspace.get({ id: Number(id) });
|
||||||
|
@ -253,15 +208,9 @@ function adminEndpoints(app) {
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
"/admin/system-preferences",
|
"/admin/system-preferences",
|
||||||
[validatedRequest],
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
async (request, response) => {
|
async (_request, response) => {
|
||||||
try {
|
try {
|
||||||
const user = await userFromSession(request, response);
|
|
||||||
if (!user || user?.role !== "admin") {
|
|
||||||
response.sendStatus(401).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
users_can_delete_workspaces:
|
users_can_delete_workspaces:
|
||||||
(await SystemSettings.get({ label: "users_can_delete_workspaces" }))
|
(await SystemSettings.get({ label: "users_can_delete_workspaces" }))
|
||||||
|
@ -284,15 +233,9 @@ function adminEndpoints(app) {
|
||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
"/admin/system-preferences",
|
"/admin/system-preferences",
|
||||||
[validatedRequest],
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const user = await userFromSession(request, response);
|
|
||||||
if (!user || user?.role !== "admin") {
|
|
||||||
response.sendStatus(401).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updates = reqBody(request);
|
const updates = reqBody(request);
|
||||||
await SystemSettings.updateSettings(updates);
|
await SystemSettings.updateSettings(updates);
|
||||||
response.status(200).json({ success: true, error: null });
|
response.status(200).json({ success: true, error: null });
|
||||||
|
@ -303,39 +246,32 @@ function adminEndpoints(app) {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get("/admin/api-keys", [validatedRequest], async (request, response) => {
|
app.get(
|
||||||
try {
|
"/admin/api-keys",
|
||||||
const user = await userFromSession(request, response);
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
if (!user || user?.role !== "admin") {
|
async (_request, response) => {
|
||||||
response.sendStatus(401).end();
|
try {
|
||||||
return;
|
const apiKeys = await ApiKey.whereWithUser({});
|
||||||
|
return response.status(200).json({
|
||||||
|
apiKeys,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
response.status(500).json({
|
||||||
|
apiKey: null,
|
||||||
|
error: "Could not find an API Keys.",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiKeys = await ApiKey.whereWithUser({});
|
|
||||||
return response.status(200).json({
|
|
||||||
apiKeys,
|
|
||||||
error: null,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
response.status(500).json({
|
|
||||||
apiKey: null,
|
|
||||||
error: "Could not find an API Keys.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
"/admin/generate-api-key",
|
"/admin/generate-api-key",
|
||||||
[validatedRequest],
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const user = await userFromSession(request, response);
|
const user = await userFromSession(request, response);
|
||||||
if (!user || user?.role !== "admin") {
|
|
||||||
response.sendStatus(401).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { apiKey, error } = await ApiKey.create(user.id);
|
const { apiKey, error } = await ApiKey.create(user.id);
|
||||||
return response.status(200).json({
|
return response.status(200).json({
|
||||||
apiKey,
|
apiKey,
|
||||||
|
@ -350,15 +286,10 @@ function adminEndpoints(app) {
|
||||||
|
|
||||||
app.delete(
|
app.delete(
|
||||||
"/admin/delete-api-key/:id",
|
"/admin/delete-api-key/:id",
|
||||||
[validatedRequest],
|
[validatedRequest, strictMultiUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const user = await userFromSession(request, response);
|
|
||||||
if (!user || user?.role !== "admin") {
|
|
||||||
response.sendStatus(401).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await ApiKey.delete({ id: Number(id) });
|
await ApiKey.delete({ id: Number(id) });
|
||||||
return response.status(200).end();
|
return response.status(200).end();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -40,6 +40,7 @@ const { ApiKey } = require("../models/apiKeys");
|
||||||
const { getCustomModels } = require("../utils/helpers/customModels");
|
const { getCustomModels } = require("../utils/helpers/customModels");
|
||||||
const { WorkspaceChats } = require("../models/workspaceChats");
|
const { WorkspaceChats } = require("../models/workspaceChats");
|
||||||
const { Workspace } = require("../models/workspace");
|
const { Workspace } = require("../models/workspace");
|
||||||
|
const { flexUserRoleValid } = require("../utils/middleware/multiUserProtected");
|
||||||
|
|
||||||
function systemEndpoints(app) {
|
function systemEndpoints(app) {
|
||||||
if (!app) return;
|
if (!app) return;
|
||||||
|
@ -244,20 +245,10 @@ function systemEndpoints(app) {
|
||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
"/system/update-env",
|
"/system/update-env",
|
||||||
[validatedRequest],
|
[validatedRequest, flexUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const body = reqBody(request);
|
const body = reqBody(request);
|
||||||
|
|
||||||
// Only admins can update the ENV settings.
|
|
||||||
if (multiUserMode(response)) {
|
|
||||||
const user = await userFromSession(request, response);
|
|
||||||
if (!user || user?.role !== "admin") {
|
|
||||||
response.sendStatus(401).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { newValues, error } = updateENV(body);
|
const { newValues, error } = updateENV(body);
|
||||||
if (process.env.NODE_ENV === "production") await dumpENV();
|
if (process.env.NODE_ENV === "production") await dumpENV();
|
||||||
response.status(200).json({ newValues, error });
|
response.status(200).json({ newValues, error });
|
||||||
|
@ -426,7 +417,7 @@ function systemEndpoints(app) {
|
||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
"/system/upload-logo",
|
"/system/upload-logo",
|
||||||
[validatedRequest],
|
[validatedRequest, flexUserRoleValid],
|
||||||
handleLogoUploads.single("logo"),
|
handleLogoUploads.single("logo"),
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
if (!request.file || !request.file.originalname) {
|
if (!request.file || !request.file.originalname) {
|
||||||
|
@ -440,13 +431,6 @@ function systemEndpoints(app) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (
|
|
||||||
response.locals.multiUserMode &&
|
|
||||||
response.locals.user?.role !== "admin"
|
|
||||||
) {
|
|
||||||
return response.sendStatus(401).end();
|
|
||||||
}
|
|
||||||
|
|
||||||
const newFilename = await renameLogoFile(request.file.originalname);
|
const newFilename = await renameLogoFile(request.file.originalname);
|
||||||
const existingLogoFilename = await SystemSettings.currentLogoFilename();
|
const existingLogoFilename = await SystemSettings.currentLogoFilename();
|
||||||
await removeCustomLogo(existingLogoFilename);
|
await removeCustomLogo(existingLogoFilename);
|
||||||
|
@ -480,16 +464,9 @@ function systemEndpoints(app) {
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
"/system/remove-logo",
|
"/system/remove-logo",
|
||||||
[validatedRequest],
|
[validatedRequest, flexUserRoleValid],
|
||||||
async (request, response) => {
|
async (_request, response) => {
|
||||||
try {
|
try {
|
||||||
if (
|
|
||||||
response.locals.multiUserMode &&
|
|
||||||
response.locals.user?.role !== "admin"
|
|
||||||
) {
|
|
||||||
return response.sendStatus(401).end();
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentLogoFilename = await SystemSettings.currentLogoFilename();
|
const currentLogoFilename = await SystemSettings.currentLogoFilename();
|
||||||
await removeCustomLogo(currentLogoFilename);
|
await removeCustomLogo(currentLogoFilename);
|
||||||
const { success, error } = await SystemSettings.updateSettings({
|
const { success, error } = await SystemSettings.updateSettings({
|
||||||
|
@ -517,7 +494,8 @@ function systemEndpoints(app) {
|
||||||
return response.status(200).json({ canDelete: true });
|
return response.status(200).json({ canDelete: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.locals.user?.role === "admin") {
|
const user = await userFromSession(request, response);
|
||||||
|
if (["admin", "manager"].includes(user?.role)) {
|
||||||
return response.status(200).json({ canDelete: true });
|
return response.status(200).json({ canDelete: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -548,16 +526,9 @@ function systemEndpoints(app) {
|
||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
"/system/set-welcome-messages",
|
"/system/set-welcome-messages",
|
||||||
[validatedRequest],
|
[validatedRequest, flexUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
if (
|
|
||||||
response.locals.multiUserMode &&
|
|
||||||
response.locals.user?.role !== "admin"
|
|
||||||
) {
|
|
||||||
return response.sendStatus(401).end();
|
|
||||||
}
|
|
||||||
|
|
||||||
const { messages = [] } = reqBody(request);
|
const { messages = [] } = reqBody(request);
|
||||||
if (!Array.isArray(messages)) {
|
if (!Array.isArray(messages)) {
|
||||||
return response.status(400).json({
|
return response.status(400).json({
|
||||||
|
@ -659,16 +630,9 @@ function systemEndpoints(app) {
|
||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
"/system/workspace-chats",
|
"/system/workspace-chats",
|
||||||
[validatedRequest],
|
[validatedRequest, flexUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
if (
|
|
||||||
response.locals.multiUserMode &&
|
|
||||||
response.locals.user?.role !== "admin"
|
|
||||||
) {
|
|
||||||
return response.sendStatus(401).end();
|
|
||||||
}
|
|
||||||
|
|
||||||
const { offset = 0, limit = 20 } = reqBody(request);
|
const { offset = 0, limit = 20 } = reqBody(request);
|
||||||
const chats = await WorkspaceChats.whereWithData(
|
const chats = await WorkspaceChats.whereWithData(
|
||||||
{},
|
{},
|
||||||
|
@ -689,16 +653,9 @@ function systemEndpoints(app) {
|
||||||
|
|
||||||
app.delete(
|
app.delete(
|
||||||
"/system/workspace-chats/:id",
|
"/system/workspace-chats/:id",
|
||||||
[validatedRequest],
|
[validatedRequest, flexUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
if (
|
|
||||||
response.locals.multiUserMode &&
|
|
||||||
response.locals.user?.role !== "admin"
|
|
||||||
) {
|
|
||||||
return response.sendStatus(401).end();
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
await WorkspaceChats.delete({ id: Number(id) });
|
await WorkspaceChats.delete({ id: Number(id) });
|
||||||
response.status(200).json({ success, error });
|
response.status(200).json({ success, error });
|
||||||
|
@ -711,16 +668,9 @@ function systemEndpoints(app) {
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
"/system/export-chats",
|
"/system/export-chats",
|
||||||
[validatedRequest],
|
[validatedRequest, flexUserRoleValid],
|
||||||
async (request, response) => {
|
async (_request, response) => {
|
||||||
try {
|
try {
|
||||||
if (
|
|
||||||
response.locals.multiUserMode &&
|
|
||||||
response.locals.user?.role !== "admin"
|
|
||||||
) {
|
|
||||||
return response.sendStatus(401).end();
|
|
||||||
}
|
|
||||||
|
|
||||||
const chats = await WorkspaceChats.whereWithData({}, null, null, {
|
const chats = await WorkspaceChats.whereWithData({}, null, null, {
|
||||||
id: "asc",
|
id: "asc",
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,36 +11,40 @@ const {
|
||||||
processDocument,
|
processDocument,
|
||||||
} = require("../utils/files/documentProcessor");
|
} = require("../utils/files/documentProcessor");
|
||||||
const { validatedRequest } = require("../utils/middleware/validatedRequest");
|
const { validatedRequest } = require("../utils/middleware/validatedRequest");
|
||||||
const { SystemSettings } = require("../models/systemSettings");
|
|
||||||
const { Telemetry } = require("../models/telemetry");
|
const { Telemetry } = require("../models/telemetry");
|
||||||
|
const { flexUserRoleValid } = require("../utils/middleware/multiUserProtected");
|
||||||
const { handleUploads } = setupMulter();
|
const { handleUploads } = setupMulter();
|
||||||
|
|
||||||
function workspaceEndpoints(app) {
|
function workspaceEndpoints(app) {
|
||||||
if (!app) return;
|
if (!app) return;
|
||||||
|
|
||||||
app.post("/workspace/new", [validatedRequest], async (request, response) => {
|
app.post(
|
||||||
try {
|
"/workspace/new",
|
||||||
const user = await userFromSession(request, response);
|
[validatedRequest, flexUserRoleValid],
|
||||||
const { name = null, onboardingComplete = false } = reqBody(request);
|
async (request, response) => {
|
||||||
const { workspace, message } = await Workspace.new(name, user?.id);
|
try {
|
||||||
await Telemetry.sendTelemetry(
|
const user = await userFromSession(request, response);
|
||||||
"workspace_created",
|
const { name = null, onboardingComplete = false } = reqBody(request);
|
||||||
{
|
const { workspace, message } = await Workspace.new(name, user?.id);
|
||||||
multiUserMode: multiUserMode(response),
|
await Telemetry.sendTelemetry(
|
||||||
LLMSelection: process.env.LLM_PROVIDER || "openai",
|
"workspace_created",
|
||||||
VectorDbSelection: process.env.VECTOR_DB || "pinecone",
|
{
|
||||||
},
|
multiUserMode: multiUserMode(response),
|
||||||
user?.id
|
LLMSelection: process.env.LLM_PROVIDER || "openai",
|
||||||
);
|
VectorDbSelection: process.env.VECTOR_DB || "pinecone",
|
||||||
if (onboardingComplete === true)
|
},
|
||||||
await Telemetry.sendTelemetry("onboarding_complete");
|
user?.id
|
||||||
|
);
|
||||||
|
if (onboardingComplete === true)
|
||||||
|
await Telemetry.sendTelemetry("onboarding_complete");
|
||||||
|
|
||||||
response.status(200).json({ workspace, message });
|
response.status(200).json({ workspace, message });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e.message, e);
|
console.log(e.message, e);
|
||||||
response.sendStatus(500).end();
|
response.sendStatus(500).end();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
"/workspace/:slug/update",
|
"/workspace/:slug/update",
|
||||||
|
@ -142,7 +146,7 @@ function workspaceEndpoints(app) {
|
||||||
|
|
||||||
app.delete(
|
app.delete(
|
||||||
"/workspace/:slug",
|
"/workspace/:slug",
|
||||||
[validatedRequest],
|
[validatedRequest, flexUserRoleValid],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const { slug = "" } = request.params;
|
const { slug = "" } = request.params;
|
||||||
|
@ -157,16 +161,6 @@ function workspaceEndpoints(app) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (multiUserMode(response) && user.role !== "admin") {
|
|
||||||
const canDelete =
|
|
||||||
(await SystemSettings.get({ label: "users_can_delete_workspaces" }))
|
|
||||||
?.value === "true";
|
|
||||||
if (!canDelete) {
|
|
||||||
response.sendStatus(500).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await WorkspaceChats.delete({ workspaceId: Number(workspace.id) });
|
await WorkspaceChats.delete({ workspaceId: Number(workspace.id) });
|
||||||
await DocumentVectors.deleteForWorkspace(workspace.id);
|
await DocumentVectors.deleteForWorkspace(workspace.id);
|
||||||
await Document.delete({ workspaceId: Number(workspace.id) });
|
await Document.delete({ workspaceId: Number(workspace.id) });
|
||||||
|
|
|
@ -64,7 +64,7 @@ const Workspace = {
|
||||||
},
|
},
|
||||||
|
|
||||||
getWithUser: async function (user = null, clause = {}) {
|
getWithUser: async function (user = null, clause = {}) {
|
||||||
if (user.role === "admin") return this.get(clause);
|
if (["admin", "manager"].includes(user.role)) return this.get(clause);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const workspace = await prisma.workspaces.findFirst({
|
const workspace = await prisma.workspaces.findFirst({
|
||||||
|
@ -142,7 +142,8 @@ const Workspace = {
|
||||||
limit = null,
|
limit = null,
|
||||||
orderBy = null
|
orderBy = null
|
||||||
) {
|
) {
|
||||||
if (user.role === "admin") return await this.where(clause, limit, orderBy);
|
if (["admin", "manager"].includes(user.role))
|
||||||
|
return await this.where(clause, limit, orderBy);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const workspaces = await prisma.workspaces.findMany({
|
const workspaces = await prisma.workspaces.findMany({
|
||||||
|
|
41
server/utils/middleware/multiUserProtected.js
Normal file
41
server/utils/middleware/multiUserProtected.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
const { SystemSettings } = require("../../models/systemSettings");
|
||||||
|
const { userFromSession } = require("../http");
|
||||||
|
|
||||||
|
const ROLES = ["admin", "manager"];
|
||||||
|
|
||||||
|
// Explicitly check that multi user mode is enabled as well as that the
|
||||||
|
// requesting user has the appropriate role to modify or call the URL.
|
||||||
|
async function strictMultiUserRoleValid(request, response, next) {
|
||||||
|
const multiUserMode =
|
||||||
|
response.locals?.multiUserMode ?? (await SystemSettings.isMultiUserMode());
|
||||||
|
if (!multiUserMode) return response.sendStatus(401).end();
|
||||||
|
|
||||||
|
const user =
|
||||||
|
response.locals?.user ?? (await userFromSession(request, response));
|
||||||
|
if (!ROLES.includes(user?.role)) return response.sendStatus(401).end();
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply role permission checks IF the current system is in multi-user mode.
|
||||||
|
// This is relevant for routes that are shared between MUM and single-user mode.
|
||||||
|
// Checks if the requesting user has the appropriate role to modify or call the URL.
|
||||||
|
async function flexUserRoleValid(request, response, next) {
|
||||||
|
const multiUserMode =
|
||||||
|
response.locals?.multiUserMode ?? (await SystemSettings.isMultiUserMode());
|
||||||
|
if (!multiUserMode) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user =
|
||||||
|
response.locals?.user ?? (await userFromSession(request, response));
|
||||||
|
if (!ROLES.includes(user?.role)) return response.sendStatus(401).end();
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
strictMultiUserRoleValid,
|
||||||
|
flexUserRoleValid,
|
||||||
|
};
|
|
@ -20,7 +20,7 @@ async function validatedRequest(request, response, next) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!process.env.AUTH_TOKEN) {
|
if (!process.env.AUTH_TOKEN) {
|
||||||
response.status(403).json({
|
response.status(401).json({
|
||||||
error: "You need to set an AUTH_TOKEN environment variable.",
|
error: "You need to set an AUTH_TOKEN environment variable.",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -30,7 +30,7 @@ async function validatedRequest(request, response, next) {
|
||||||
const token = auth ? auth.split(" ")[1] : null;
|
const token = auth ? auth.split(" ")[1] : null;
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
response.status(403).json({
|
response.status(401).json({
|
||||||
error: "No auth token found.",
|
error: "No auth token found.",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -38,7 +38,7 @@ async function validatedRequest(request, response, next) {
|
||||||
|
|
||||||
const { p } = decodeJWT(token);
|
const { p } = decodeJWT(token);
|
||||||
if (p !== process.env.AUTH_TOKEN) {
|
if (p !== process.env.AUTH_TOKEN) {
|
||||||
response.status(403).json({
|
response.status(401).json({
|
||||||
error: "Invalid auth token found.",
|
error: "Invalid auth token found.",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -52,7 +52,7 @@ async function validateMultiUserRequest(request, response, next) {
|
||||||
const token = auth ? auth.split(" ")[1] : null;
|
const token = auth ? auth.split(" ")[1] : null;
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
response.status(403).json({
|
response.status(401).json({
|
||||||
error: "No auth token found.",
|
error: "No auth token found.",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -60,7 +60,7 @@ async function validateMultiUserRequest(request, response, next) {
|
||||||
|
|
||||||
const valid = decodeJWT(token);
|
const valid = decodeJWT(token);
|
||||||
if (!valid || !valid.id) {
|
if (!valid || !valid.id) {
|
||||||
response.status(403).json({
|
response.status(401).json({
|
||||||
error: "Invalid auth token.",
|
error: "Invalid auth token.",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -68,12 +68,19 @@ async function validateMultiUserRequest(request, response, next) {
|
||||||
|
|
||||||
const user = await User.get({ id: valid.id });
|
const user = await User.get({ id: valid.id });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
response.status(403).json({
|
response.status(401).json({
|
||||||
error: "Invalid auth for user.",
|
error: "Invalid auth for user.",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user.suspended) {
|
||||||
|
response.status(401).json({
|
||||||
|
error: "User is suspended from system",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
response.locals.user = user;
|
response.locals.user = user;
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue