passwords
This commit is contained in:
1 parent
5ac5626406
commit
6029d6f244
3 files changed
+165
No files matched your search
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user