Merge pull request #25 from bunny-lab-io/codex/add-material-ui-login-screen-to-borealis

Add login screen with SHA-512 authentication
This commit is contained in:
2025-08-10 04:19:27 -06:00
committed by GitHub
4 changed files with 160 additions and 0 deletions

View File

@@ -28,6 +28,7 @@ import WorkflowList from "./Workflow_List";
import DeviceList from "./Device_List"; import DeviceList from "./Device_List";
import ScriptList from "./Script_List"; import ScriptList from "./Script_List";
import ScheduledJobsList from "./Scheduled_Jobs_List"; import ScheduledJobsList from "./Scheduled_Jobs_List";
import Login from "./Login.jsx";
import { io } from "socket.io-client"; import { io } from "socket.io-client";
@@ -86,6 +87,31 @@ export default function App() {
const [tabMenuAnchor, setTabMenuAnchor] = useState(null); const [tabMenuAnchor, setTabMenuAnchor] = useState(null);
const [tabMenuTabId, setTabMenuTabId] = useState(null); const [tabMenuTabId, setTabMenuTabId] = useState(null);
const fileInputRef = useRef(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(() => { useEffect(() => {
const saved = localStorage.getItem(LOCAL_STORAGE_KEY); const saved = localStorage.getItem(LOCAL_STORAGE_KEY);
@@ -386,6 +412,14 @@ export default function App() {
); );
} }
}; };
if (!user) {
return (
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<Login onLogin={handleLoginSuccess} />
</ThemeProvider>
);
}
return ( return (
<ThemeProvider theme={darkTheme}> <ThemeProvider theme={darkTheme}>

View File

@@ -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 (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100vh",
backgroundColor: "#2b2b2b",
}}
>
<Box
component="form"
onSubmit={handleSubmit}
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
width: 300,
}}
>
<img
src="/Borealis_Logo.png"
alt="Borealis Logo"
style={{ width: "120px", marginBottom: "16px" }}
/>
<Typography variant="h6" sx={{ mb: 3 }}>
Borealis - Automation Platform
</Typography>
<TextField
label="Username"
variant="outlined"
fullWidth
value={username}
onChange={(e) => setUsername(e.target.value)}
margin="normal"
/>
<TextField
label="Password"
type="password"
variant="outlined"
fullWidth
value={password}
onChange={(e) => setPassword(e.target.value)}
margin="normal"
/>
{error && (
<Typography color="error" sx={{ mt: 1 }}>
{error}
</Typography>
)}
<Button
type="submit"
variant="contained"
fullWidth
sx={{ mt: 2, bgcolor: "#58a6ff", "&:hover": { bgcolor: "#1d82d3" } }}
>
Login
</Button>
</Box>
</Box>
);
}

View File

@@ -61,6 +61,20 @@ def serve_dist(path):
def health(): def health():
return jsonify({"status": "ok"}) 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 # Borealis Python API Endpoints
# --------------------------------------------- # ---------------------------------------------

9
users.json Normal file
View File

@@ -0,0 +1,9 @@
{
"users": [
{
"username": "admin",
"password": "18ec3210467c363a6cf96901261be4431bb4a75285ccd362b8cf8eae504ce6250ea4d64264a75486bf446051e3d4a6ac004410914ae706d29a01e96ba53f49a3"
}
]
}