passwords

This commit is contained in:
nate@necloud.us committed 2025-07-15 11:11:25 -04:00
1 parent 5ac5626406
commit 6029d6f244
3 files changed
+165

No files matched your search

+41
View File
@@ -62,6 +62,7 @@
<button onclick="switchTab('tabReports')">📘 Reports</button>
<button onclick="switchTab('tabSessions')">📅 Sessions</button>
<button onclick="switchTab('tabBadges')">🏅 Badges</button>
<button onclick="switchTab('tabPasswords')">🔐 Passwords</button>
<button onclick="switchTab('tabTheme')">🎨 Themes</button>
</div>
@@ -186,6 +187,46 @@
</div>
<!-- Theme Tab -->
<!-- Passwords Tab -->
<div class="adminTab hidden" id="tabPasswords">
<div class="card">
<div class="card-header">
<h3>🔐 Password Management</h3>
<p style="color: var(--text-secondary);">Change user passwords</p>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
<!-- Change Password Form -->
<div class="card">
<h4>Change User Password</h4>
<form onsubmit="event.preventDefault(); changeUserPassword();" style="display: flex; flex-direction: column; gap: 1rem;">
<label for="passwordUsername">Select User:</label>
<select id="passwordUsername" required style="width: 100%;">
<option value="">-- Select a user --</option>
</select>
<label for="newUserPassword">New Password:</label>
<input id="newUserPassword" placeholder="Enter new password" required type="password"/>
<label for="confirmPassword">Confirm Password:</label>
<input id="confirmPassword" placeholder="Confirm new password" required type="password"/>
<button type="submit" class="btn-warning">🔐 Change Password</button>
</form>
</div>
<!-- Password History -->
<div class="card">
<h4>Password Change Log</h4>
<div id="passwordLog" style="max-height: 300px; overflow-y: auto; background: var(--bg-secondary); padding: 1rem; border-radius: var(--radius);">
<p style="color: var(--text-secondary); font-style: italic;">No password changes recorded yet.</p>
</div>
</div>
</div>
</div>
</div>
<!-- Themes Tab -->
<div class="adminTab hidden" id="tabTheme">
<div class="card">
<h3>🎨 Application Themes</h3>
+99
View File
@@ -47,6 +47,9 @@ window.switchTab = function switchTab(targetId) {
loadStudentNamesForReport();
}
break;
case 'tabPasswords':
populatePasswordUserDropdown();
break;
case 'tabBadges':
if (typeof loadBadgeViewer === 'function') {
loadBadgeViewer();
@@ -575,6 +578,102 @@ function handleFileUpload(event) {
reader.readAsText(file);
}
// 🔐 Password Management Functions
window.changeUserPassword = async function() {
const username = document.getElementById('passwordUsername').value;
const newPassword = document.getElementById('newUserPassword').value;
const confirmPassword = document.getElementById('confirmPassword').value;
if (!username) {
alert('Please select a user');
return;
}
if (!newPassword || newPassword.length < 3) {
alert('Password must be at least 3 characters long');
return;
}
if (newPassword !== confirmPassword) {
alert('Passwords do not match');
return;
}
try {
// Hash the new password (same way as registration)
const encoder = new TextEncoder();
const data = encoder.encode(newPassword);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const newPasswordHash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
// Send password change request
const response = await fetch('/changePassword', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, newPasswordHash })
});
const message = await response.text();
if (response.ok) {
alert(message);
logPasswordChange(username);
// Clear form
document.getElementById('newUserPassword').value = '';
document.getElementById('confirmPassword').value = '';
document.getElementById('passwordUsername').value = '';
} else {
alert('Error: ' + message);
}
} catch (error) {
console.error('Error changing password:', error);
alert('Failed to change password');
}
}
// Log password changes
function logPasswordChange(username) {
const passwordLog = document.getElementById('passwordLog');
const timestamp = new Date().toLocaleString();
const logEntry = document.createElement('div');
logEntry.style.padding = '0.5rem';
logEntry.style.marginBottom = '0.5rem';
logEntry.style.backgroundColor = 'var(--bg-primary)';
logEntry.style.borderRadius = 'var(--radius)';
logEntry.style.borderLeft = '4px solid var(--warning)';
logEntry.innerHTML = `
<strong>🔐 Password Changed</strong><br>
<span style="color: var(--text-secondary);">User: ${username}</span><br>
<span style="color: var(--text-secondary); font-size: 0.9rem;">${timestamp}</span>
`;
// Remove "no changes" message if it exists
if (passwordLog.querySelector('p')) {
passwordLog.innerHTML = '';
}
// Add new entry at the top
passwordLog.insertBefore(logEntry, passwordLog.firstChild);
}
// Populate password username dropdown
function populatePasswordUserDropdown() {
const dropdown = document.getElementById('passwordUsername');
if (!dropdown || !adminUsers) return;
dropdown.innerHTML = '<option value="">-- Select a user --</option>';
adminUsers.forEach(user => {
const option = document.createElement('option');
option.value = user.username;
option.textContent = `${user.username} (${user.role})`;
dropdown.appendChild(option);
});
}
// Add event listeners for admin tabs as backup to onclick
document.addEventListener('DOMContentLoaded', () => {
// Setup tab click listeners
+25
View File
@@ -110,6 +110,31 @@ app.post('/deleteUser', (req, res) => {
res.send(`✅ User "${username}" deleted`);
});
// 🔐 Change user password
app.post('/changePassword', (req, res) => {
const { username, newPasswordHash } = req.body;
if (typeof username !== 'string' || typeof newPasswordHash !== 'string') {
return res.status(400).send("Invalid request data");
}
const usersPath = path.join(DATA_DIR, files.users);
const users = readJsonSafe(usersPath, []);
const userIndex = users.findIndex(u => u.username === username);
if (userIndex === -1) {
return res.status(404).send("User not found");
}
// Update the password hash
users[userIndex].hash = newPasswordHash;
// Save updated users
fs.writeFileSync(usersPath, JSON.stringify(users, null, 2));
console.log(`Password changed for user: ${username}`);
res.send(`✅ Password changed for user "${username}"`);
});
// 📚 Word list
app.get('/getWordList', (req, res) => {
try {