mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2025-03-16 07:02:22 +00:00
12 auth implementation (#13)
* Add Auth protection for cloud-based or private instances * skip check on local dev
This commit is contained in:
parent
fdacf4bb2e
commit
62e3f62e82
19 changed files with 429 additions and 26 deletions
frontend/src
components
Modals
Sidebar
models
pages
utils
server
|
@ -1,10 +1,7 @@
|
|||
import React from "react";
|
||||
import { titleCase } from "text-case";
|
||||
|
||||
export default function CannotRemoveModal({
|
||||
hideModal,
|
||||
vectordb,
|
||||
}) {
|
||||
export default function CannotRemoveModal({ hideModal, vectordb }) {
|
||||
return (
|
||||
<dialog
|
||||
open={true}
|
||||
|
@ -19,7 +16,11 @@ export default function CannotRemoveModal({
|
|||
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<p className="text-base mt-4">
|
||||
{titleCase(vectordb)} does not support atomic removal of documents.<br />Unfortunately, you will have to delete the entire workspace to remove this document from being referenced.
|
||||
{titleCase(vectordb)} does not support atomic removal of
|
||||
documents.
|
||||
<br />
|
||||
Unfortunately, you will have to delete the entire workspace to
|
||||
remove this document from being referenced.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex w-full justify-center items-center mt-4">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { X } from 'react-feather';
|
||||
import { X } from "react-feather";
|
||||
import System from "../../../models/system";
|
||||
import Workspace from "../../../models/workspace";
|
||||
import paths from "../../../utils/paths";
|
||||
|
@ -18,7 +18,7 @@ export default function ManageWorkspace({ hideModal = noop, workspace }) {
|
|||
const [originalDocuments, setOriginalDocuments] = useState([]);
|
||||
const [selectedFiles, setSelectFiles] = useState([]);
|
||||
const [vectordb, setVectorDB] = useState(null);
|
||||
const [showingNoRemovalModal, setShowingNoRemovalModal] = useState(false)
|
||||
const [showingNoRemovalModal, setShowingNoRemovalModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchKeys() {
|
||||
|
@ -29,7 +29,7 @@ export default function ManageWorkspace({ hideModal = noop, workspace }) {
|
|||
setDirectories(localFiles);
|
||||
setOriginalDocuments([...originalDocs]);
|
||||
setSelectFiles([...originalDocs]);
|
||||
setVectorDB(settings?.VectorDB)
|
||||
setVectorDB(settings?.VectorDB);
|
||||
setLoading(false);
|
||||
}
|
||||
fetchKeys();
|
||||
|
@ -99,7 +99,7 @@ export default function ManageWorkspace({ hideModal = noop, workspace }) {
|
|||
return isFolder
|
||||
? originalDocuments.some((doc) => doc.includes(filepath.split("/")[0]))
|
||||
: originalDocuments.some((doc) => doc.includes(filepath));
|
||||
}
|
||||
};
|
||||
|
||||
const toggleSelection = (filepath) => {
|
||||
const isFolder = !filepath.includes("/");
|
||||
|
@ -108,7 +108,7 @@ export default function ManageWorkspace({ hideModal = noop, workspace }) {
|
|||
if (isSelected(filepath)) {
|
||||
// Certain vector DBs do not contain the ability to delete vectors
|
||||
// so we cannot remove from these. The user will have to clear the entire workspace.
|
||||
if (['lancedb'].includes(vectordb) && isOriginalDoc(filepath)) {
|
||||
if (["lancedb"].includes(vectordb) && isOriginalDoc(filepath)) {
|
||||
setShowingNoRemovalModal(true);
|
||||
return false;
|
||||
}
|
||||
|
|
119
frontend/src/components/Modals/Password.jsx
Normal file
119
frontend/src/components/Modals/Password.jsx
Normal file
|
@ -0,0 +1,119 @@
|
|||
import React, { useState, useEffect, useRef } from "react";
|
||||
import System from "../../models/system";
|
||||
|
||||
export default function PasswordModal() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const formEl = useRef(null);
|
||||
const [error, setError] = useState(null);
|
||||
const handleLogin = async (e) => {
|
||||
setError(null);
|
||||
setLoading(true);
|
||||
e.preventDefault();
|
||||
const data = {};
|
||||
|
||||
const form = new FormData(formEl.current);
|
||||
for (var [key, value] of form.entries()) data[key] = value;
|
||||
const { valid, token, message } = await System.requestToken(data);
|
||||
if (valid && !!token) {
|
||||
window.localStorage.setItem("anythingllm_authtoken", token);
|
||||
window.location.reload();
|
||||
} else {
|
||||
setError(message);
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="fixed top-0 left-0 right-0 z-50 w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] h-full bg-black bg-opacity-50 flex items-center justify-center">
|
||||
<div className="flex fixed top-0 left-0 right-0 w-full h-full" />
|
||||
<div class="relative w-full max-w-2xl max-h-full">
|
||||
<form ref={formEl} onSubmit={handleLogin}>
|
||||
<div class="relative bg-white rounded-lg shadow dark:bg-stone-700">
|
||||
<div class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600">
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
This workspace is password protected.
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-6 space-y-6 flex h-full w-full">
|
||||
<div className="w-full flex flex-col gap-y-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="password"
|
||||
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
|
||||
>
|
||||
Workspace Password
|
||||
</label>
|
||||
<input
|
||||
name="password"
|
||||
type="password"
|
||||
id="password"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-stone-600 dark:border-stone-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
{error && (
|
||||
<p className="text-red-600 dark:text-red-400 text-sm">
|
||||
Error: {error}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-gray-800 dark:text-slate-200 text-sm">
|
||||
You will only have to enter this password once. After
|
||||
successful login it will be stored in your browser.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
|
||||
<button
|
||||
disabled={loading}
|
||||
type="submit"
|
||||
class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
|
||||
>
|
||||
{loading ? "Validating..." : "Submit"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function usePasswordModal() {
|
||||
const [requiresAuth, setRequiresAuth] = useState(null);
|
||||
useEffect(() => {
|
||||
async function checkAuthReq() {
|
||||
if (!window) return;
|
||||
if (import.meta.env.DEV) {
|
||||
setRequiresAuth(false);
|
||||
} else {
|
||||
const currentToken = window.localStorage.getItem("anythingllm_authtoken");
|
||||
const settings = await System.keys();
|
||||
const requiresAuth = settings?.RequiresAuth || false;
|
||||
|
||||
// If Auth is disabled - skip check
|
||||
if (!requiresAuth) {
|
||||
setRequiresAuth(requiresAuth);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!!currentToken) {
|
||||
const valid = await System.checkAuth(currentToken);
|
||||
if (!valid) {
|
||||
setRequiresAuth(true);
|
||||
window.localStorage.removeItem("anythingllm_authtoken");
|
||||
return;
|
||||
} else {
|
||||
setRequiresAuth(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
setRequiresAuth(true);
|
||||
}
|
||||
}
|
||||
checkAuthReq();
|
||||
}, []);
|
||||
|
||||
return { requiresAuth };
|
||||
}
|
|
@ -51,10 +51,11 @@ export default function ActiveWorkspaces() {
|
|||
>
|
||||
<a
|
||||
href={isActive ? null : paths.workspace.chat(workspace.slug)}
|
||||
className={`flex flex-grow w-[75%] h-[36px] gap-x-2 py-[5px] px-4 border border-slate-400 rounded-lg text-slate-800 dark:text-slate-200 justify-start items-center ${isActive
|
||||
className={`flex flex-grow w-[75%] h-[36px] gap-x-2 py-[5px] px-4 border border-slate-400 rounded-lg text-slate-800 dark:text-slate-200 justify-start items-center ${
|
||||
isActive
|
||||
? "bg-gray-100 dark:bg-stone-600"
|
||||
: "hover:bg-slate-100 dark:hover:bg-stone-900 "
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
<Book className="h-4 w-4" />
|
||||
<p className="text-slate-800 dark:text-slate-200 text-xs leading-loose font-semibold">
|
||||
|
|
134
frontend/src/components/Sidebar/Placeholder/index.jsx
Normal file
134
frontend/src/components/Sidebar/Placeholder/index.jsx
Normal file
|
@ -0,0 +1,134 @@
|
|||
import React, { useRef } from "react";
|
||||
import {
|
||||
BookOpen,
|
||||
Briefcase,
|
||||
Cpu,
|
||||
GitHub,
|
||||
Key,
|
||||
Plus,
|
||||
AlertCircle,
|
||||
} from "react-feather";
|
||||
import * as Skeleton from "react-loading-skeleton";
|
||||
import "react-loading-skeleton/dist/skeleton.css";
|
||||
import paths from "../../../utils/paths";
|
||||
|
||||
export default function Sidebar() {
|
||||
const sidebarRef = useRef(null);
|
||||
|
||||
// const handleWidthToggle = () => {
|
||||
// if (!sidebarRef.current) return false;
|
||||
// sidebarRef.current.classList.add('translate-x-[-100%]')
|
||||
// }
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={sidebarRef}
|
||||
style={{ height: "calc(100% - 32px)" }}
|
||||
className="transition-all duration-500 relative m-[16px] rounded-[26px] bg-white dark:bg-black-900 min-w-[15.5%] p-[18px] "
|
||||
>
|
||||
{/* <button onClick={handleWidthToggle} className='absolute -right-[13px] top-[35%] bg-white w-auto h-auto bg-transparent flex items-center'>
|
||||
<svg width="16" height="96" viewBox="0 0 16 96" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#141414"><path d="M2.5 0H3C3 20 15 12 15 32V64C15 84 3 76 3 96H2.5V0Z" fill="black" fill-opacity="0.12" stroke="transparent" stroke-width="0px"></path><path d="M0 0H2.5C2.5 20 14.5 12 14.5 32V64C14.5 84 2.5 76 2.5 96H0V0Z" fill="#141414"></path></svg>
|
||||
<ChevronLeft className='absolute h-4 w-4 text-white mr-1' />
|
||||
</button> */}
|
||||
|
||||
<div className="w-full h-full flex flex-col overflow-x-hidden items-between">
|
||||
{/* Header Information */}
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<p className="text-xl font-base text-slate-600 dark:text-slate-200">
|
||||
AnythingLLM
|
||||
</p>
|
||||
<div className="flex gap-x-2 items-center text-slate-500">
|
||||
<button className="transition-all duration-300 p-2 rounded-full bg-slate-200 text-slate-400 dark:bg-stone-800 hover:bg-slate-800 hover:text-slate-200 dark:hover:text-slate-200">
|
||||
<Key className="h-4 w-4 " />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Primary Body */}
|
||||
<div className="h-[100%] flex flex-col w-full justify-between pt-4 overflow-y-hidden">
|
||||
<div className="h-auto sidebar-items dark:sidebar-items">
|
||||
<div className="flex flex-col gap-y-4 h-[65vh] pb-8 overflow-y-scroll no-scroll">
|
||||
<div className="flex gap-x-2 items-center justify-between">
|
||||
<button className="flex flex-grow w-[75%] h-[36px] gap-x-2 py-[5px] px-4 border border-slate-400 rounded-lg text-slate-800 dark:text-slate-200 justify-start items-center hover:bg-slate-100 dark:hover:bg-stone-900">
|
||||
<Plus className="h-4 w-4" />
|
||||
<p className="text-slate-800 dark:text-slate-200 text-xs leading-loose font-semibold">
|
||||
New workspace
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
<Skeleton.default
|
||||
height={36}
|
||||
width="100%"
|
||||
count={3}
|
||||
baseColor="#292524"
|
||||
highlightColor="#4c4948"
|
||||
enableAnimation={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<div className="w-full flex items-center justify-start">
|
||||
<div className="flex w-fit items-center justify-end gap-x-2">
|
||||
<p className="text-slate-400 leading-loose text-sm">LLM</p>
|
||||
<div className="flex items-center gap-x-1 border border-red-400 px-2 bg-red-200 rounded-full">
|
||||
<p className="text-red-700 leading-tight text-sm">
|
||||
offline
|
||||
</p>
|
||||
<AlertCircle className="h-3 w-3 stroke-red-100 fill-red-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href={paths.hosting()}
|
||||
target="_blank"
|
||||
className="flex flex-grow w-[100%] h-[36px] gap-x-2 py-[5px] px-4 border border-slate-400 dark:border-transparent rounded-lg text-slate-800 dark:text-slate-200 justify-center items-center hover:bg-slate-100 dark:bg-stone-800 dark:hover:bg-stone-900"
|
||||
>
|
||||
<Cpu className="h-4 w-4" />
|
||||
<p className="text-slate-800 dark:text-slate-200 text-xs leading-loose font-semibold">
|
||||
Managed cloud hosting
|
||||
</p>
|
||||
</a>
|
||||
<a
|
||||
href={paths.hosting()}
|
||||
target="_blank"
|
||||
className="flex flex-grow w-[100%] h-[36px] gap-x-2 py-[5px] px-4 border border-slate-400 dark:border-transparent rounded-lg text-slate-800 dark:text-slate-200 justify-center items-center hover:bg-slate-100 dark:bg-stone-800 dark:hover:bg-stone-900"
|
||||
>
|
||||
<Briefcase className="h-4 w-4" />
|
||||
<p className="text-slate-800 dark:text-slate-200 text-xs leading-loose font-semibold">
|
||||
Enterprise Installation
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex items-end justify-between mt-2">
|
||||
<div className="flex gap-x-1 items-center">
|
||||
<a
|
||||
href={paths.github()}
|
||||
className="transition-all duration-300 p-2 rounded-full bg-slate-200 text-slate-400 dark:bg-slate-800 hover:bg-slate-800 hover:text-slate-200 dark:hover:text-slate-200"
|
||||
>
|
||||
<GitHub className="h-4 w-4 " />
|
||||
</a>
|
||||
<a
|
||||
href={paths.docs()}
|
||||
className="transition-all duration-300 p-2 rounded-full bg-slate-200 text-slate-400 dark:bg-slate-800 hover:bg-slate-800 hover:text-slate-200 dark:hover:text-slate-200"
|
||||
>
|
||||
<BookOpen className="h-4 w-4 " />
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
href={paths.mailToMintplex()}
|
||||
className="transition-all duration-300 text-xs text-slate-200 dark:text-slate-600 hover:text-blue-600 dark:hover:text-blue-400"
|
||||
>
|
||||
@MintplexLabs
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import { API_BASE } from "../utils/constants";
|
||||
import { baseHeaders } from "../utils/request";
|
||||
|
||||
const System = {
|
||||
ping: async function () {
|
||||
|
@ -7,7 +8,9 @@ const System = {
|
|||
.catch(() => false);
|
||||
},
|
||||
totalIndexes: async function () {
|
||||
return await fetch(`${API_BASE}/system-vectors`)
|
||||
return await fetch(`${API_BASE}/system/system-vectors`, {
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error("Could not find indexes.");
|
||||
return res.json();
|
||||
|
@ -25,7 +28,9 @@ const System = {
|
|||
.catch(() => null);
|
||||
},
|
||||
localFiles: async function () {
|
||||
return await fetch(`${API_BASE}/local-files`)
|
||||
return await fetch(`${API_BASE}/system/local-files`, {
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error("Could not find setup information.");
|
||||
return res.json();
|
||||
|
@ -33,6 +38,27 @@ const System = {
|
|||
.then((res) => res.localFiles)
|
||||
.catch(() => null);
|
||||
},
|
||||
checkAuth: async function (currentToken = null) {
|
||||
return await fetch(`${API_BASE}/system/check-token`, {
|
||||
headers: baseHeaders(currentToken),
|
||||
})
|
||||
.then((res) => res.ok)
|
||||
.catch(() => false);
|
||||
},
|
||||
requestToken: async function (body) {
|
||||
return await fetch(`${API_BASE}/request-token`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ ...body }),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error("Could not validate login.");
|
||||
return res.json();
|
||||
})
|
||||
.then((res) => res)
|
||||
.catch((e) => {
|
||||
return { valid: false, message: e.message };
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default System;
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { API_BASE } from "../utils/constants";
|
||||
import { baseHeaders } from "../utils/request";
|
||||
|
||||
const Workspace = {
|
||||
new: async function (data = {}) {
|
||||
const { workspace, message } = await fetch(`${API_BASE}/workspace/new`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.catch((e) => {
|
||||
|
@ -19,6 +21,7 @@ const Workspace = {
|
|||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(changes), // contains 'adds' and 'removes' keys that are arrays of filepaths
|
||||
headers: baseHeaders(),
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
|
@ -29,7 +32,9 @@ const Workspace = {
|
|||
return { workspace, message };
|
||||
},
|
||||
chatHistory: async function (slug) {
|
||||
const history = await fetch(`${API_BASE}/workspace/${slug}/chats`)
|
||||
const history = await fetch(`${API_BASE}/workspace/${slug}/chats`, {
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => res.history || [])
|
||||
.catch(() => []);
|
||||
|
@ -39,6 +44,7 @@ const Workspace = {
|
|||
const chatResult = await fetch(`${API_BASE}/workspace/${slug}/chat`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ message, mode }),
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.catch((e) => {
|
||||
|
@ -57,7 +63,9 @@ const Workspace = {
|
|||
return workspaces;
|
||||
},
|
||||
bySlug: async function (slug = "") {
|
||||
const workspace = await fetch(`${API_BASE}/workspace/${slug}`)
|
||||
const workspace = await fetch(`${API_BASE}/workspace/${slug}`, {
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => res.workspace)
|
||||
.catch(() => null);
|
||||
|
@ -66,6 +74,7 @@ const Workspace = {
|
|||
delete: async function (slug) {
|
||||
const result = await fetch(`${API_BASE}/workspace/${slug}`, {
|
||||
method: "DELETE",
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => res.ok)
|
||||
.catch(() => false);
|
||||
|
|
|
@ -1,8 +1,26 @@
|
|||
import React from "react";
|
||||
import DefaultChatContainer from "../../components/DefaultChat";
|
||||
import Sidebar from "../../components/Sidebar";
|
||||
import SidebarPlaceholder from "../../components/Sidebar/Placeholder";
|
||||
import ChatPlaceholder from "../../components/WorkspaceChat/LoadingChat";
|
||||
import PasswordModal, {
|
||||
usePasswordModal,
|
||||
} from "../../components/Modals/Password";
|
||||
|
||||
export default function Main() {
|
||||
const { requiresAuth } = usePasswordModal();
|
||||
if (requiresAuth === null || requiresAuth) {
|
||||
return (
|
||||
<>
|
||||
{requiresAuth && <PasswordModal />}
|
||||
<div className="w-screen h-screen overflow-hidden bg-orange-100 dark:bg-stone-700 flex">
|
||||
<SidebarPlaceholder />
|
||||
<ChatPlaceholder />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden bg-orange-100 dark:bg-stone-700 flex">
|
||||
<Sidebar />
|
||||
|
|
|
@ -3,8 +3,30 @@ import { default as WorkspaceChatContainer } from "../../components/WorkspaceCha
|
|||
import Sidebar from "../../components/Sidebar";
|
||||
import { useParams } from "react-router-dom";
|
||||
import Workspace from "../../models/workspace";
|
||||
import SidebarPlaceholder from "../../components/Sidebar/Placeholder";
|
||||
import ChatPlaceholder from "../../components/WorkspaceChat/LoadingChat";
|
||||
import PasswordModal, {
|
||||
usePasswordModal,
|
||||
} from "../../components/Modals/Password";
|
||||
|
||||
export default function WorkspaceChat() {
|
||||
const { requiresAuth } = usePasswordModal();
|
||||
if (requiresAuth === null || requiresAuth) {
|
||||
return (
|
||||
<>
|
||||
{requiresAuth && <PasswordModal />}
|
||||
<div className="w-screen h-screen overflow-hidden bg-orange-100 dark:bg-stone-700 flex">
|
||||
<SidebarPlaceholder />
|
||||
<ChatPlaceholder />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return <ShowWorkspaceChat />;
|
||||
}
|
||||
|
||||
function ShowWorkspaceChat() {
|
||||
const { slug } = useParams();
|
||||
const [workspace, setWorkspace] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
|
9
frontend/src/utils/request.js
Normal file
9
frontend/src/utils/request.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Sets up the base headers for all authenticated requests so that we are able to prevent
|
||||
// basic spoofing since a valid token is required and that cannot be spoofed
|
||||
export function baseHeaders(providedToken = null) {
|
||||
const token =
|
||||
providedToken || window.localStorage.getItem("anythingllm_authtoken");
|
||||
return {
|
||||
Authorization: token ? `Bearer ${token}` : null,
|
||||
};
|
||||
}
|
|
@ -18,4 +18,5 @@ PINECONE_INDEX=
|
|||
|
||||
# CLOUD DEPLOYMENT VARIRABLES ONLY
|
||||
# AUTH_TOKEN="hunter2" # This is the password to your application if remote hosting.
|
||||
# JWT_SECRET="my-random-string-for-seeding" # Only needed if AUTH_TOKEN is set. Please generate random string at least 12 chars long.
|
||||
# STORAGE_DIR= # absolute filesystem path with no trailing slash
|
|
@ -3,6 +3,7 @@ process.env.NODE_ENV === "development"
|
|||
: require("dotenv").config();
|
||||
const { viewLocalFiles } = require("../utils/files");
|
||||
const { getVectorDbClass } = require("../utils/helpers");
|
||||
const { reqBody, makeJWT } = require("../utils/http");
|
||||
|
||||
function systemEndpoints(app) {
|
||||
if (!app) return;
|
||||
|
@ -15,6 +16,7 @@ function systemEndpoints(app) {
|
|||
try {
|
||||
const vectorDB = process.env.VECTOR_DB || "pinecone";
|
||||
const results = {
|
||||
RequiresAuth: !!process.env.AUTH_TOKEN,
|
||||
VectorDB: vectorDB,
|
||||
OpenAiKey: !!process.env.OPEN_AI_KEY,
|
||||
OpenAiModelPref: process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo",
|
||||
|
@ -38,7 +40,37 @@ function systemEndpoints(app) {
|
|||
}
|
||||
});
|
||||
|
||||
app.get("/system-vectors", async (_, response) => {
|
||||
app.get("/system/check-token", (_, response) => {
|
||||
response.sendStatus(200).end();
|
||||
});
|
||||
|
||||
app.post("/request-token", (request, response) => {
|
||||
try {
|
||||
const { password } = reqBody(request);
|
||||
if (password !== process.env.AUTH_TOKEN) {
|
||||
response
|
||||
.status(402)
|
||||
.json({
|
||||
valid: false,
|
||||
token: null,
|
||||
message: "Invalid password provided",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
response.status(200).json({
|
||||
valid: true,
|
||||
token: makeJWT({ p: password }, "30d"),
|
||||
message: null,
|
||||
});
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log(e.message, e);
|
||||
response.sendStatus(500).end();
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/system/system-vectors", async (_, response) => {
|
||||
try {
|
||||
const VectorDb = getVectorDbClass();
|
||||
const vectorCount = await VectorDb.totalIndicies();
|
||||
|
@ -49,7 +81,7 @@ function systemEndpoints(app) {
|
|||
}
|
||||
});
|
||||
|
||||
app.get("/local-files", async (_, response) => {
|
||||
app.get("/system/local-files", async (_, response) => {
|
||||
try {
|
||||
const localFiles = await viewLocalFiles();
|
||||
response.status(200).json({ localFiles });
|
||||
|
|
|
@ -14,7 +14,6 @@ const { getVectorDbClass } = require("./utils/helpers");
|
|||
const app = express();
|
||||
|
||||
app.use(cors({ origin: true }));
|
||||
app.use(validatedRequest);
|
||||
app.use(bodyParser.text());
|
||||
app.use(bodyParser.json());
|
||||
app.use(
|
||||
|
@ -23,6 +22,8 @@ app.use(
|
|||
})
|
||||
);
|
||||
|
||||
app.use("/system/*", validatedRequest);
|
||||
app.use("/workspace/*", validatedRequest);
|
||||
systemEndpoints(app);
|
||||
workspaceEndpoints(app);
|
||||
chatEndpoints(app);
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"sqlite": "^4.2.1",
|
||||
"sqlite3": "^5.1.6",
|
||||
"uuid": "^9.0.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"vectordb": "0.1.5-beta"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
process.env.NODE_ENV === "development"
|
||||
? require("dotenv").config({ path: `.env.${process.env.NODE_ENV}` })
|
||||
: require("dotenv").config();
|
||||
const JWT = require("jsonwebtoken");
|
||||
const SECRET = process.env.JWT_SECRET;
|
||||
|
||||
function reqBody(request) {
|
||||
return typeof request.body === "string"
|
||||
? JSON.parse(request.body)
|
||||
|
@ -8,7 +14,21 @@ function queryParams(request) {
|
|||
return request.query;
|
||||
}
|
||||
|
||||
function makeJWT(info = {}, expiry = "30d") {
|
||||
if (!SECRET) throw new Error("Cannot create JWT as JWT_SECRET is unset.");
|
||||
return JWT.sign(info, SECRET, { expiresIn: expiry });
|
||||
}
|
||||
|
||||
function decodeJWT(jwtToken) {
|
||||
try {
|
||||
return JWT.verify(jwtToken, SECRET);
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
reqBody,
|
||||
queryParams,
|
||||
makeJWT,
|
||||
decodeJWT,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
const { decodeJWT } = require("../http");
|
||||
|
||||
function validatedRequest(request, response, next) {
|
||||
// When in development passthrough auth token for ease of development.
|
||||
if (process.env.NODE_ENV === "development" || !process.env.AUTH_TOKEN) {
|
||||
// Or if the user simply did not set an Auth token or JWT Secret
|
||||
if (
|
||||
process.env.NODE_ENV === "development" ||
|
||||
!process.env.AUTH_TOKEN ||
|
||||
!process.env.JWT_SECRET
|
||||
) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
@ -22,7 +29,8 @@ function validatedRequest(request, response, next) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (token !== process.env.AUTH_TOKEN) {
|
||||
const { p } = decodeJWT(token);
|
||||
if (p !== process.env.AUTH_TOKEN) {
|
||||
response.status(403).json({
|
||||
error: "Invalid auth token found.",
|
||||
});
|
||||
|
|
|
@ -26,8 +26,9 @@ function curateLanceSources(sources = []) {
|
|||
}
|
||||
|
||||
const LanceDb = {
|
||||
uri: `${!!process.env.STORAGE_DIR ? `${process.env.STORAGE_DIR}/` : "./"
|
||||
}lancedb`,
|
||||
uri: `${
|
||||
!!process.env.STORAGE_DIR ? `${process.env.STORAGE_DIR}/` : "./"
|
||||
}lancedb`,
|
||||
name: "LanceDb",
|
||||
connect: async function () {
|
||||
if (process.env.VECTOR_DB !== "lancedb")
|
||||
|
@ -282,4 +283,4 @@ const LanceDb = {
|
|||
},
|
||||
};
|
||||
|
||||
module.exports.LanceDb = LanceDb
|
||||
module.exports.LanceDb = LanceDb;
|
||||
|
|
Loading…
Add table
Reference in a new issue