这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions frontend/src/components/Modals/Settings/PasswordProtection/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, { useState, useEffect } from "react";
import System from "../../../../models/system";

const noop = () => false;
export default function PasswordProtection({ hideModal = noop }) {
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState(null);
const [usePassword, setUsePassword] = useState(false);

const handleSubmit = async (e) => {
e.preventDefault();
setSaving(true);
setSuccess(false);
setError(null);

const form = new FormData(e.target);
const data = {
usePassword,
newPassword: form.get("password"),
};

const { success, error } = await System.updateSystemPassword(data);
if (success) {
setSuccess(true);
setSaving(false);
setTimeout(() => {
window.localStorage.removeItem("anythingllm_authToken");
window.location.reload();
}, 2_000);
return;
}

setError(error);
setSaving(false);
};

useEffect(() => {
async function fetchKeys() {
const settings = await System.keys();
setUsePassword(settings?.RequiresAuth);
setLoading(false);
}
fetchKeys();
}, []);

return (
<div className="relative w-full max-w-2xl max-h-full">
<div className="relative bg-white rounded-lg shadow dark:bg-stone-700">
<div className="flex items-start justify-between px-6 py-4">
<p className="text-gray-800 dark:text-stone-200 text-base ">
Protect your AnythingLLM instance with a password. If you forget
this there is no recovery method so ensure you save this password.
</p>
</div>
{(error || success) && (
<div className="w-full flex px-6">
{error && (
<div className="w-full bg-red-300 text-red-800 font-semibold px-4 py-2 rounded-lg">
{error}
</div>
)}
{success && (
<div className="w-full bg-green-300 text-green-800 font-semibold px-4 py-2 rounded-lg">
Your page will refresh in a few seconds.
</div>
)}
</div>
)}
<div className="p-6 space-y-6 flex h-full w-full">
{loading ? (
<div className="w-full h-full flex items-center justify-center">
<p className="text-gray-800 dark:text-gray-200 text-base">
loading system settings
</p>
</div>
) : (
<div className="w-full flex flex-col gap-y-4">
<form onSubmit={handleSubmit}>
<div className="">
<label className="mb-2.5 block font-medium text-black dark:text-white">
Password Protect Instance
</label>

<label className="relative inline-flex cursor-pointer items-center">
<input
type="checkbox"
name="use_password"
onClick={() => setUsePassword(!usePassword)}
checked={usePassword}
className="peer sr-only pointer-events-none"
/>
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-gray-200 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-green-600 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:border-gray-600 dark:bg-stone-400 dark:peer-focus:ring-blue-800"></div>
</label>
</div>
<div className="w-full flex flex-col gap-y-2 my-2">
{usePassword && (
<div>
<label
htmlFor="password"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
New Password
</label>
<input
name="password"
type="text"
className="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"
placeholder="Your Instance Password"
minLength={8}
required={true}
autoComplete="off"
/>
</div>
)}
<button
disabled={saving}
type="submit"
className="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"
>
{saving ? "Saving..." : "Save Changes"}
</button>
</div>
</form>
</div>
)}
</div>
<div className="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<button
onClick={hideModal}
type="button"
className="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"
>
Close
</button>
</div>
</div>
</div>
);
}
11 changes: 10 additions & 1 deletion frontend/src/components/Modals/Settings/index.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { useState } from "react";
import { Archive, Cloud, Key, X } from "react-feather";
import { Archive, Lock, Key, X } from "react-feather";
import SystemKeys from "./Keys";
import ExportOrImportData from "./ExportImport";
import PasswordProtection from "./PasswordProtection";

const TABS = {
keys: SystemKeys,
exportimport: ExportOrImportData,
password: PasswordProtection,
};

const noop = () => false;
Expand Down Expand Up @@ -62,6 +64,13 @@ function SettingTabs({ selectedTab, changeTab }) {
icon={<Archive className="h-4 w-4 flex-shrink-0" />}
onClick={changeTab}
/>
<SettingTab
active={selectedTab === "password"}
displayName="Password Protection"
tabName="password"
icon={<Lock className="h-4 w-4 flex-shrink-0" />}
onClick={changeTab}
/>
</ul>
</div>
);
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/models/system.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ const System = {
return { newValues: null, error: e.message };
});
},
updateSystemPassword: async (data) => {
return await fetch(`${API_BASE}/system/update-password`, {
method: "POST",
headers: baseHeaders(),
body: JSON.stringify(data),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { success: false, error: e.message };
});
},
deleteDocument: async (name, meta) => {
return await fetch(`${API_BASE}/system/remove-document`, {
method: "DELETE",
Expand Down
15 changes: 15 additions & 0 deletions server/endpoints/system.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const { getVectorDbClass } = require("../utils/helpers");
const { updateENV } = require("../utils/helpers/updateENV");
const { reqBody, makeJWT } = require("../utils/http");
const { setupDataImports } = require("../utils/files/multer");
const { v4 } = require("uuid");
const { handleImports } = setupDataImports();

function systemEndpoints(app) {
Expand Down Expand Up @@ -155,6 +156,20 @@ function systemEndpoints(app) {
}
});

app.post("/system/update-password", async (request, response) => {
try {
const { usePassword, newPassword } = reqBody(request);
const { error } = updateENV({
AuthToken: usePassword ? newPassword : "",
JWTSecret: usePassword ? v4() : "",
});
response.status(200).json({ success: !error, error });
} catch (e) {
console.log(e.message, e);
response.sendStatus(500).end();
}
});

app.get("/system/data-export", async (_, response) => {
try {
const { filename, error } = await exportData();
Expand Down
10 changes: 8 additions & 2 deletions server/utils/helpers/updateENV.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@ const KEY_MAPPING = {
envKey: "PINECONE_INDEX",
checks: [],
},
AuthToken: {
envKey: "AUTH_TOKEN",
checks: [],
},
JWTSecret: {
envKey: "JWT_SECRET",
checks: [],
},
// Not supported yet.
// 'AuthToken': 'AUTH_TOKEN',
// 'JWTSecret': 'JWT_SECRET',
// 'StorageDir': 'STORAGE_DIR',
};

Expand Down
10 changes: 5 additions & 5 deletions server/utils/http/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ 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"
Expand All @@ -15,15 +14,16 @@ function queryParams(request) {
}

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 });
if (!process.env.JWT_SECRET)
throw new Error("Cannot create JWT as JWT_SECRET is unset.");
return JWT.sign(info, process.env.JWT_SECRET, { expiresIn: expiry });
}

function decodeJWT(jwtToken) {
try {
return JWT.verify(jwtToken, SECRET);
return JWT.verify(jwtToken, process.env.JWT_SECRET);
} catch {}
return null;
return { p: null };
}

module.exports = {
Expand Down