Skip to content
Snippets Groups Projects
Unverified Commit 6b6007f9 authored by Timothy Carambat's avatar Timothy Carambat Committed by GitHub
Browse files

Enable the system owner to be able to update the system wide password and secret (#156)

* Enable the system owner to be able to update the system wide password and secret

* lint and cleanup
parent 5fa61458
No related branches found
No related tags found
No related merge requests found
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>
);
}
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;
......@@ -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>
);
......
......@@ -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",
......
......@@ -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) {
......@@ -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();
......
......@@ -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',
};
......
......@@ -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"
......@@ -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 = {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment