diff --git a/Data/Server/WebUI/src/App.jsx b/Data/Server/WebUI/src/App.jsx
index e41aa5a..556e4f5 100644
--- a/Data/Server/WebUI/src/App.jsx
+++ b/Data/Server/WebUI/src/App.jsx
@@ -28,6 +28,7 @@ import WorkflowList from "./Workflow_List";
import DeviceList from "./Device_List";
import ScriptList from "./Script_List";
import ScheduledJobsList from "./Scheduled_Jobs_List";
+import Login from "./Login.jsx";
import { io } from "socket.io-client";
@@ -86,6 +87,31 @@ export default function App() {
const [tabMenuAnchor, setTabMenuAnchor] = useState(null);
const [tabMenuTabId, setTabMenuTabId] = useState(null);
const fileInputRef = useRef(null);
+ const [user, setUser] = useState(null);
+
+ useEffect(() => {
+ const session = localStorage.getItem("borealis_session");
+ if (session) {
+ try {
+ const data = JSON.parse(session);
+ if (Date.now() - data.timestamp < 3600 * 1000) {
+ setUser(data.username);
+ } else {
+ localStorage.removeItem("borealis_session");
+ }
+ } catch {
+ localStorage.removeItem("borealis_session");
+ }
+ }
+ }, []);
+
+ const handleLoginSuccess = (username) => {
+ setUser(username);
+ localStorage.setItem(
+ "borealis_session",
+ JSON.stringify({ username, timestamp: Date.now() })
+ );
+ };
useEffect(() => {
const saved = localStorage.getItem(LOCAL_STORAGE_KEY);
@@ -386,6 +412,14 @@ export default function App() {
);
}
};
+ if (!user) {
+ return (
+
+
+
+
+ );
+ }
return (
diff --git a/Data/Server/WebUI/src/Login.jsx b/Data/Server/WebUI/src/Login.jsx
new file mode 100644
index 0000000..3be8ab3
--- /dev/null
+++ b/Data/Server/WebUI/src/Login.jsx
@@ -0,0 +1,103 @@
+import React, { useState, useEffect } from "react";
+import { Box, TextField, Button, Typography } from "@mui/material";
+
+export default function Login({ onLogin }) {
+ const [users, setUsers] = useState([]);
+ const [username, setUsername] = useState("admin");
+ const [password, setPassword] = useState("");
+ const [error, setError] = useState("");
+
+ useEffect(() => {
+ fetch("/api/users")
+ .then((res) => res.json())
+ .then((data) => setUsers(data.users || []))
+ .catch(() => setUsers([]));
+ }, []);
+
+ const sha512 = async (text) => {
+ const encoder = new TextEncoder();
+ const data = encoder.encode(text);
+ const hashBuffer = await crypto.subtle.digest("SHA-512", data);
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ const user = users.find((u) => u.username === username);
+ if (!user) {
+ setError("Invalid username or password");
+ return;
+ }
+ const hash = await sha512(password);
+ if (hash.toLowerCase() === (user.password || "").toLowerCase()) {
+ setError("");
+ onLogin(username);
+ } else {
+ setError("Invalid username or password");
+ }
+ };
+
+ return (
+
+
+
+
+ Borealis - Automation Platform
+
+ setUsername(e.target.value)}
+ margin="normal"
+ />
+ setPassword(e.target.value)}
+ margin="normal"
+ />
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+ );
+}
+
diff --git a/Data/Server/server.py b/Data/Server/server.py
index 56ec5da..b953f95 100644
--- a/Data/Server/server.py
+++ b/Data/Server/server.py
@@ -61,6 +61,20 @@ def serve_dist(path):
def health():
return jsonify({"status": "ok"})
+# ---------------------------------------------
+# User Authentication Endpoint
+# ---------------------------------------------
+@app.route("/api/users", methods=["GET"])
+def get_users():
+ users_path = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), "..", "..", "users.json")
+ )
+ try:
+ with open(users_path, "r", encoding="utf-8") as fh:
+ return jsonify(json.load(fh))
+ except Exception:
+ return jsonify({"users": []})
+
# ---------------------------------------------
# Borealis Python API Endpoints
# ---------------------------------------------
diff --git a/users.json b/users.json
new file mode 100644
index 0000000..4b3fa05
--- /dev/null
+++ b/users.json
@@ -0,0 +1,9 @@
+{
+ "users": [
+ {
+ "username": "admin",
+ "password": "18ec3210467c363a6cf96901261be4431bb4a75285ccd362b8cf8eae504ce6250ea4d64264a75486bf446051e3d4a6ac004410914ae706d29a01e96ba53f49a3"
+ }
+ ]
+}
+