From 99963f067c4d21c5d983a87fc52188963435056e Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Sun, 10 Aug 2025 03:43:08 -0600 Subject: [PATCH] feat: add login component and user config --- Data/Server/WebUI/src/App.jsx | 34 +++++++++++ Data/Server/WebUI/src/Login.jsx | 103 ++++++++++++++++++++++++++++++++ users.json | 9 +++ 3 files changed, 146 insertions(+) create mode 100644 Data/Server/WebUI/src/Login.jsx create mode 100644 users.json 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..4c25286 --- /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("/users.json") + .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 === user.password) { + setError(""); + onLogin(username); + } else { + setError("Invalid username or password"); + } + }; + + return ( + + + Borealis Logo + + Borealis - Automation Platform + + setUsername(e.target.value)} + margin="normal" + /> + setPassword(e.target.value)} + margin="normal" + /> + {error && ( + + {error} + + )} + + + + ); +} + 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" + } + ] +} +