diff --git a/public/index.html b/public/index.html
index d26da1e..51e11f3 100644
--- a/public/index.html
+++ b/public/index.html
@@ -62,6 +62,7 @@
+
@@ -186,6 +187,46 @@
+
+
🎨 Application Themes
diff --git a/public/js/admin.js b/public/js/admin.js
index a00247b..e43f9a4 100644
--- a/public/js/admin.js
+++ b/public/js/admin.js
@@ -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 = `
+ 🔐 Password Changed
+ User: ${username}
+ ${timestamp}
+ `;
+
+ // 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 = '';
+
+ 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
diff --git a/server.js b/server.js
index 9c8cdc4..92a6a70 100644
--- a/server.js
+++ b/server.js
@@ -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 {