auth: add authentication
All checks were successful
Build ESP32 BMC Firmware / build (push) Successful in 1m7s
All checks were successful
Build ESP32 BMC Firmware / build (push) Successful in 1m7s
This commit is contained in:
@@ -524,10 +524,109 @@
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Login Modal Styles */
|
||||
.login-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.login-overlay.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.login-modal {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-modal h2 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.login-modal p {
|
||||
color: #aaa;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.login-field {
|
||||
margin-bottom: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.login-field label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #ccc;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.login-field input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: #fff;
|
||||
font-size: 1em;
|
||||
outline: none;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.login-field input:focus {
|
||||
border-color: #00d4ff;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.login-error {
|
||||
color: #ff4444;
|
||||
margin-top: 15px;
|
||||
font-size: 0.9em;
|
||||
min-height: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Login Modal -->
|
||||
<div class="login-overlay" id="login-overlay">
|
||||
<div class="login-modal">
|
||||
<h2>🔐 BMC Login</h2>
|
||||
<p>Enter your credentials to access the BMC dashboard</p>
|
||||
<form id="login-form" onsubmit="return handleLogin(event)">
|
||||
<div class="login-field">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" id="username" name="username" required autocomplete="username">
|
||||
</div>
|
||||
<div class="login-field">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" required autocomplete="current-password">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary login-btn">Login</button>
|
||||
<div class="login-error" id="login-error"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container" id="main-container" style="display: none;">
|
||||
<header>
|
||||
<h1>🖥️ VisionFive2 BMC Dashboard</h1>
|
||||
<div class="status-bar">
|
||||
@@ -717,6 +816,122 @@
|
||||
// API base URL
|
||||
const API_BASE = '';
|
||||
|
||||
// Authentication
|
||||
let authCredentials = null;
|
||||
|
||||
// Get stored credentials from sessionStorage
|
||||
function getStoredCredentials() {
|
||||
const stored = sessionStorage.getItem('bmc_auth');
|
||||
if (stored) {
|
||||
return JSON.parse(stored);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Store credentials in sessionStorage
|
||||
function storeCredentials(username, password) {
|
||||
sessionStorage.setItem('bmc_auth', JSON.stringify({ username, password }));
|
||||
}
|
||||
|
||||
// Clear stored credentials
|
||||
function clearCredentials() {
|
||||
sessionStorage.removeItem('bmc_auth');
|
||||
authCredentials = null;
|
||||
}
|
||||
|
||||
// Create Basic Auth header value
|
||||
function getAuthHeader() {
|
||||
if (!authCredentials) {
|
||||
const stored = getStoredCredentials();
|
||||
if (stored) {
|
||||
authCredentials = stored;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return 'Basic ' + btoa(authCredentials.username + ':' + authCredentials.password);
|
||||
}
|
||||
|
||||
// Handle login form submission
|
||||
async function handleLogin(event) {
|
||||
event.preventDefault();
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const errorDiv = document.getElementById('login-error');
|
||||
|
||||
// Store credentials temporarily
|
||||
authCredentials = { username, password };
|
||||
|
||||
// Test credentials by making an API call
|
||||
try {
|
||||
const authHeader = 'Basic ' + btoa(username + ':' + password);
|
||||
const response = await fetch(`${API_BASE}/api/system/info`, {
|
||||
headers: {
|
||||
'Authorization': authHeader
|
||||
}
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
errorDiv.textContent = 'Invalid username or password';
|
||||
authCredentials = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
errorDiv.textContent = 'Connection error. Please try again.';
|
||||
authCredentials = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Success - store credentials and show main content
|
||||
storeCredentials(username, password);
|
||||
document.getElementById('login-overlay').classList.add('hidden');
|
||||
document.getElementById('main-container').style.display = 'block';
|
||||
|
||||
// Initialize the dashboard
|
||||
initialize();
|
||||
|
||||
} catch (error) {
|
||||
errorDiv.textContent = 'Connection error. Please try again.';
|
||||
authCredentials = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if already logged in
|
||||
function checkAuth() {
|
||||
const stored = getStoredCredentials();
|
||||
if (stored) {
|
||||
authCredentials = stored;
|
||||
// Verify credentials are still valid
|
||||
const authHeader = getAuthHeader();
|
||||
fetch(`${API_BASE}/api/system/info`, {
|
||||
headers: { 'Authorization': authHeader }
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === 401) {
|
||||
clearCredentials();
|
||||
document.getElementById('login-overlay').classList.remove('hidden');
|
||||
document.getElementById('main-container').style.display = 'none';
|
||||
} else {
|
||||
document.getElementById('login-overlay').classList.add('hidden');
|
||||
document.getElementById('main-container').style.display = 'block';
|
||||
initialize();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// On error, still show login
|
||||
document.getElementById('login-overlay').classList.remove('hidden');
|
||||
document.getElementById('main-container').style.display = 'none';
|
||||
});
|
||||
} else {
|
||||
document.getElementById('login-overlay').classList.remove('hidden');
|
||||
document.getElementById('main-container').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Toast notification
|
||||
function showToast(message, type = 'success') {
|
||||
const container = document.getElementById('toast-container');
|
||||
@@ -730,8 +945,9 @@
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// API helper
|
||||
// API helper with authentication
|
||||
async function apiCall(endpoint, method = 'GET', data = null) {
|
||||
const authHeader = getAuthHeader();
|
||||
const options = {
|
||||
method: method,
|
||||
headers: {
|
||||
@@ -739,12 +955,25 @@
|
||||
}
|
||||
};
|
||||
|
||||
if (authHeader) {
|
||||
options.headers['Authorization'] = authHeader;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}${endpoint}`, options);
|
||||
|
||||
// Handle 401 - session expired
|
||||
if (response.status === 401) {
|
||||
clearCredentials();
|
||||
document.getElementById('login-overlay').classList.remove('hidden');
|
||||
document.getElementById('main-container').style.display = 'none';
|
||||
throw new Error('Session expired. Please login again.');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
@@ -1168,6 +1397,11 @@
|
||||
showToast('Firmware uploaded successfully! Device will restart...', 'success');
|
||||
document.getElementById('ota-progress-bar').style.width = '100%';
|
||||
document.getElementById('ota-progress-text').textContent = '100%';
|
||||
} else if (xhr.status === 401) {
|
||||
showToast('Session expired. Please login again.', 'error');
|
||||
clearCredentials();
|
||||
document.getElementById('login-overlay').classList.remove('hidden');
|
||||
document.getElementById('main-container').style.display = 'none';
|
||||
} else {
|
||||
let errorMsg = 'Upload failed';
|
||||
try {
|
||||
@@ -1187,6 +1421,13 @@
|
||||
};
|
||||
|
||||
xhr.open('POST', `${API_BASE}/api/ota/upload`);
|
||||
|
||||
// Add authentication header
|
||||
const authHeader = getAuthHeader();
|
||||
if (authHeader) {
|
||||
xhr.setRequestHeader('Authorization', authHeader);
|
||||
}
|
||||
|
||||
xhr.send(formData);
|
||||
|
||||
} catch (error) {
|
||||
@@ -1197,8 +1438,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialize function called after successful login
|
||||
function initialize() {
|
||||
updateSystemInfo();
|
||||
updateGPIOStates();
|
||||
updatePowerStatus();
|
||||
@@ -1243,6 +1484,11 @@
|
||||
setInterval(updateGPIOStates, 2000);
|
||||
setInterval(updatePowerStatus, 2000);
|
||||
setInterval(updateOTAStatus, 5000);
|
||||
}
|
||||
|
||||
// Check authentication on page load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
checkAuth();
|
||||
});
|
||||
|
||||
// Log viewer functions
|
||||
@@ -1250,7 +1496,18 @@
|
||||
|
||||
async function refreshLogs() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/logs`);
|
||||
const authHeader = getAuthHeader();
|
||||
const response = await fetch(`${API_BASE}/api/logs`, {
|
||||
headers: authHeader ? { 'Authorization': authHeader } : {}
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
clearCredentials();
|
||||
document.getElementById('login-overlay').classList.remove('hidden');
|
||||
document.getElementById('main-container').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const logs = await response.text();
|
||||
const logsOutput = document.getElementById('logs-output');
|
||||
logsOutput.innerHTML = `<pre>${escapeHtml(logs)}</pre>`;
|
||||
@@ -1264,7 +1521,19 @@
|
||||
|
||||
async function clearLogs() {
|
||||
try {
|
||||
await fetch(`${API_BASE}/api/logs/clear`, { method: 'POST' });
|
||||
const authHeader = getAuthHeader();
|
||||
const response = await fetch(`${API_BASE}/api/logs/clear`, {
|
||||
method: 'POST',
|
||||
headers: authHeader ? { 'Authorization': authHeader } : {}
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
clearCredentials();
|
||||
document.getElementById('login-overlay').classList.remove('hidden');
|
||||
document.getElementById('main-container').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
refreshLogs();
|
||||
showToast('Logs cleared', 'success');
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user