Password updator for users / admin
This commit is contained in:
parent
06ecffaf4d
commit
4fb9847812
@ -188,6 +188,77 @@ class AuthController {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async changePassword(req, res) {
|
||||
try {
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
|
||||
// Validate input
|
||||
if (!currentPassword || !newPassword) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Current password and new password are required'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate new password strength
|
||||
if (newPassword.length < 8) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'New password must be at least 8 characters long'
|
||||
});
|
||||
}
|
||||
|
||||
// Get user from session
|
||||
const userId = req.session.userId;
|
||||
const userEmail = req.session.userEmail;
|
||||
|
||||
if (!userId || !userEmail) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Session expired. Please login again.'
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch user from NocoDB to verify current password
|
||||
const user = await nocodbService.getUserByEmail(userEmail);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify current password
|
||||
const storedPassword = user.Password || user.password;
|
||||
if (storedPassword !== currentPassword) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Current password is incorrect'
|
||||
});
|
||||
}
|
||||
|
||||
// Update password in NocoDB
|
||||
await nocodbService.updateUser(userId, {
|
||||
Password: newPassword
|
||||
});
|
||||
|
||||
console.log('Password changed successfully for user:', userEmail);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Password changed successfully'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Change password error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to change password. Please try again later.'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AuthController();
|
||||
@ -254,9 +254,13 @@
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
border-left: 4px solid #27ae60;
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.message-error {
|
||||
@ -268,6 +272,114 @@
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.password-success-animation {
|
||||
animation: successPulse 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes successPulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 0 rgba(39, 174, 96, 0.7);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 0 0 10px rgba(39, 174, 96, 0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 0 rgba(39, 174, 96, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.success-checkmark {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: #27ae60;
|
||||
color: white;
|
||||
text-align: center;
|
||||
line-height: 24px;
|
||||
margin-right: 0.5rem;
|
||||
font-weight: bold;
|
||||
animation: checkmarkPop 0.4s ease-out;
|
||||
}
|
||||
|
||||
@keyframes checkmarkPop {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Toast notification styles */
|
||||
.toast-notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #27ae60;
|
||||
color: white;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-weight: 500;
|
||||
animation: toastSlideIn 0.4s ease-out;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.toast-notification.error {
|
||||
background: #e74c3c;
|
||||
}
|
||||
|
||||
.toast-notification .toast-icon {
|
||||
font-size: 1.5rem;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: rgba(255,255,255,0.2);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@keyframes toastSlideIn {
|
||||
0% {
|
||||
transform: translateX(400px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes toastSlideOut {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(400px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.toast-notification.toast-exit {
|
||||
animation: toastSlideOut 0.4s ease-in forwards;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
@ -964,6 +1076,7 @@ Sincerely,
|
||||
<h2>Account Settings</h2>
|
||||
|
||||
<div class="account-info">
|
||||
<h3 style="color: #2c3e50; margin-bottom: 1.5rem;">Account Information</h3>
|
||||
<form id="account-form">
|
||||
<div class="form-group">
|
||||
<label for="account-name">Full Name</label>
|
||||
@ -991,6 +1104,40 @@ Sincerely,
|
||||
To update your account information, please contact an administrator.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Password Change Section -->
|
||||
<div class="account-info" style="margin-top: 2rem;">
|
||||
<h3 style="color: #2c3e50; margin-bottom: 1rem;">Change Password</h3>
|
||||
<p style="color: #666; font-size: 0.9rem; margin-bottom: 1.5rem;">
|
||||
For security reasons, please use a strong password with at least 8 characters.
|
||||
</p>
|
||||
|
||||
<div id="password-message" class="hidden"></div>
|
||||
|
||||
<form id="change-password-form">
|
||||
<div class="form-group">
|
||||
<label for="current-password">Current Password *</label>
|
||||
<input type="password" id="current-password" name="currentPassword" required
|
||||
placeholder="Enter your current password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new-password">New Password *</label>
|
||||
<input type="password" id="new-password" name="newPassword" required
|
||||
placeholder="Enter your new password (min 8 characters)"
|
||||
minlength="8">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirm-password">Confirm New Password *</label>
|
||||
<input type="password" id="confirm-password" name="confirmPassword" required
|
||||
placeholder="Re-enter your new password"
|
||||
minlength="8">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<button type="submit" class="btn btn-primary">Change Password</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancel-password-change">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -193,6 +193,34 @@ class AuthManager {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Change user password
|
||||
async changePassword(currentPassword, newPassword) {
|
||||
try {
|
||||
const response = await apiClient.post('/auth/change-password', {
|
||||
currentPassword,
|
||||
newPassword
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
return {
|
||||
success: true,
|
||||
message: response.message || 'Password changed successfully'
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: response.error || 'Failed to change password'
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Change password failed:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || 'Failed to change password. Please try again.'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create global auth manager instance
|
||||
|
||||
@ -124,6 +124,22 @@ class UserDashboard {
|
||||
});
|
||||
}
|
||||
|
||||
// Password change form
|
||||
const passwordForm = document.getElementById('change-password-form');
|
||||
if (passwordForm) {
|
||||
passwordForm.addEventListener('submit', (e) => {
|
||||
this.handlePasswordChange(e);
|
||||
});
|
||||
}
|
||||
|
||||
// Cancel password change button
|
||||
const cancelPasswordBtn = document.getElementById('cancel-password-change');
|
||||
if (cancelPasswordBtn) {
|
||||
cancelPasswordBtn.addEventListener('click', () => {
|
||||
this.resetPasswordForm();
|
||||
});
|
||||
}
|
||||
|
||||
// Response filter changes
|
||||
const responseCampaignFilter = document.getElementById('responses-campaign-filter');
|
||||
const responseStatusFilter = document.getElementById('responses-status-filter');
|
||||
@ -1356,6 +1372,176 @@ class UserDashboard {
|
||||
this.showMessage('Failed to create campaign: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Password Change Methods
|
||||
async handlePasswordChange(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const currentPassword = document.getElementById('current-password').value;
|
||||
const newPassword = document.getElementById('new-password').value;
|
||||
const confirmPassword = document.getElementById('confirm-password').value;
|
||||
|
||||
// Client-side validation
|
||||
if (newPassword !== confirmPassword) {
|
||||
this.showPasswordMessage('New passwords do not match', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword.length < 8) {
|
||||
this.showPasswordMessage('Password must be at least 8 characters long', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPassword === newPassword) {
|
||||
this.showPasswordMessage('New password must be different from current password', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Disable submit button
|
||||
const submitBtn = e.target.querySelector('button[type="submit"]');
|
||||
const originalText = submitBtn.textContent;
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Changing Password...';
|
||||
|
||||
const result = await this.authManager.changePassword(currentPassword, newPassword);
|
||||
|
||||
// Re-enable button
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = originalText;
|
||||
|
||||
if (result.success) {
|
||||
this.showPasswordMessage(result.message || 'Password changed successfully!', 'success');
|
||||
|
||||
// Show toast notification
|
||||
this.showToast('🎉 Password changed successfully! Your account is now more secure.', 'success');
|
||||
|
||||
// Visual feedback: briefly change button to success state
|
||||
submitBtn.style.transition = 'all 0.3s ease';
|
||||
submitBtn.style.backgroundColor = '#27ae60';
|
||||
submitBtn.textContent = '✓ Password Changed!';
|
||||
|
||||
// Reset button after 2 seconds, then clear form
|
||||
setTimeout(() => {
|
||||
submitBtn.style.backgroundColor = '';
|
||||
submitBtn.textContent = originalText;
|
||||
this.resetPasswordForm();
|
||||
}, 2000);
|
||||
} else {
|
||||
this.showPasswordMessage(result.error || 'Failed to change password', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Password change error:', error);
|
||||
this.showPasswordMessage('Failed to change password: ' + error.message, 'error');
|
||||
|
||||
// Re-enable button on error
|
||||
const submitBtn = e.target.querySelector('button[type="submit"]');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Change Password';
|
||||
}
|
||||
}
|
||||
|
||||
resetPasswordForm() {
|
||||
const form = document.getElementById('change-password-form');
|
||||
if (form) {
|
||||
form.reset();
|
||||
}
|
||||
|
||||
// Clear any messages
|
||||
const messageContainer = document.getElementById('password-message');
|
||||
if (messageContainer) {
|
||||
messageContainer.textContent = '';
|
||||
messageContainer.className = 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
showPasswordMessage(message, type) {
|
||||
const messageContainer = document.getElementById('password-message');
|
||||
if (messageContainer) {
|
||||
// Clear previous content
|
||||
messageContainer.innerHTML = '';
|
||||
|
||||
if (type === 'success') {
|
||||
// Add checkmark icon for success
|
||||
const checkmark = document.createElement('span');
|
||||
checkmark.className = 'success-checkmark';
|
||||
checkmark.textContent = '✓';
|
||||
messageContainer.appendChild(checkmark);
|
||||
|
||||
// Add message text
|
||||
const messageText = document.createElement('span');
|
||||
messageText.textContent = message;
|
||||
messageContainer.appendChild(messageText);
|
||||
|
||||
messageContainer.className = 'message-success password-success-animation';
|
||||
|
||||
// Add animation to the password form section
|
||||
const passwordSection = document.getElementById('change-password-form').parentElement;
|
||||
if (passwordSection) {
|
||||
passwordSection.style.transition = 'all 0.3s ease';
|
||||
passwordSection.style.borderColor = '#27ae60';
|
||||
passwordSection.style.boxShadow = '0 0 20px rgba(39, 174, 96, 0.3)';
|
||||
|
||||
// Reset the border after animation
|
||||
setTimeout(() => {
|
||||
passwordSection.style.borderColor = '';
|
||||
passwordSection.style.boxShadow = '';
|
||||
}, 2000);
|
||||
}
|
||||
} else {
|
||||
// Error message without checkmark
|
||||
messageContainer.textContent = message;
|
||||
messageContainer.className = 'message-error';
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-hide success messages after 5 seconds
|
||||
if (type === 'success') {
|
||||
setTimeout(() => {
|
||||
if (messageContainer) {
|
||||
messageContainer.style.transition = 'opacity 0.5s ease';
|
||||
messageContainer.style.opacity = '0';
|
||||
|
||||
setTimeout(() => {
|
||||
messageContainer.className = 'hidden';
|
||||
messageContainer.style.opacity = '1';
|
||||
}, 500);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
showToast(message, type = 'success') {
|
||||
// Create toast element
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast-notification ${type === 'error' ? 'error' : ''}`;
|
||||
|
||||
// Create icon
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'toast-icon';
|
||||
icon.textContent = type === 'success' ? '✓' : '✕';
|
||||
|
||||
// Create message text
|
||||
const messageText = document.createElement('span');
|
||||
messageText.textContent = message;
|
||||
|
||||
// Assemble toast
|
||||
toast.appendChild(icon);
|
||||
toast.appendChild(messageText);
|
||||
|
||||
// Add to page
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// Auto-remove after 4 seconds with animation
|
||||
setTimeout(() => {
|
||||
toast.classList.add('toast-exit');
|
||||
setTimeout(() => {
|
||||
if (toast.parentNode) {
|
||||
toast.parentNode.removeChild(toast);
|
||||
}
|
||||
}, 400); // Match animation duration
|
||||
}, 4000);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize dashboard when DOM is loaded
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
const express = require('express');
|
||||
const authController = require('../controllers/authController');
|
||||
const { requireAuth } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -12,4 +13,7 @@ router.post('/logout', authController.logout);
|
||||
// GET /api/auth/session
|
||||
router.get('/session', authController.checkSession);
|
||||
|
||||
// POST /api/auth/change-password (requires authentication)
|
||||
router.post('/change-password', requireAuth, authController.changePassword);
|
||||
|
||||
module.exports = router;
|
||||
Loading…
x
Reference in New Issue
Block a user