514 lines
17 KiB
JavaScript
514 lines
17 KiB
JavaScript
class AuthManager {
|
|
constructor() {
|
|
this.apiBaseUrl = '/api'; // Backend API base URL
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.bindEvents();
|
|
this.initializePasswordValidation();
|
|
this.handleEmailVerification();
|
|
this.handlePasswordReset();
|
|
}
|
|
|
|
bindEvents() {
|
|
// Login form
|
|
const loginForm = document.getElementById('loginForm');
|
|
if (loginForm) {
|
|
loginForm.addEventListener('submit', (e) => this.handleLogin(e));
|
|
}
|
|
|
|
// Registration form
|
|
const registerForm = document.getElementById('registerForm');
|
|
if (registerForm) {
|
|
registerForm.addEventListener('submit', (e) => this.handleRegistration(e));
|
|
}
|
|
|
|
// Forgot password form
|
|
const forgotPasswordForm = document.getElementById('forgotPasswordForm');
|
|
if (forgotPasswordForm) {
|
|
forgotPasswordForm.addEventListener('submit', (e) => this.handleForgotPassword(e));
|
|
}
|
|
|
|
// Reset password form
|
|
const resetPasswordForm = document.getElementById('resetPasswordForm');
|
|
if (resetPasswordForm) {
|
|
resetPasswordForm.addEventListener('submit', (e) => this.handleResetPassword(e));
|
|
}
|
|
|
|
// Resend verification button
|
|
const resendVerificationBtn = document.getElementById('resendVerificationBtn');
|
|
if (resendVerificationBtn) {
|
|
resendVerificationBtn.addEventListener('click', (e) => this.handleResendVerification(e));
|
|
}
|
|
}
|
|
|
|
initializePasswordValidation() {
|
|
const passwordInputs = document.querySelectorAll('input[type="password"]');
|
|
passwordInputs.forEach(input => {
|
|
if (input.id === 'password' || input.id === 'newPassword') {
|
|
input.addEventListener('input', (e) => this.validatePassword(e.target.value));
|
|
}
|
|
});
|
|
|
|
// Confirm password validation
|
|
const confirmPasswordInputs = document.querySelectorAll('#confirmPassword, #confirmNewPassword');
|
|
confirmPasswordInputs.forEach(input => {
|
|
input.addEventListener('input', (e) => this.validatePasswordMatch(e.target));
|
|
});
|
|
}
|
|
|
|
validatePassword(password) {
|
|
const requirements = {
|
|
'req-length': password.length >= 8,
|
|
'req-uppercase': /[A-Z]/.test(password),
|
|
'req-lowercase': /[a-z]/.test(password),
|
|
'req-number': /\d/.test(password),
|
|
'req-special': /[!@#$%^&*(),.?":{}|<>]/.test(password)
|
|
};
|
|
|
|
Object.entries(requirements).forEach(([reqId, isValid]) => {
|
|
const reqElement = document.getElementById(reqId);
|
|
if (reqElement) {
|
|
reqElement.classList.toggle('valid', isValid);
|
|
reqElement.classList.toggle('invalid', !isValid);
|
|
}
|
|
});
|
|
|
|
return Object.values(requirements).every(req => req);
|
|
}
|
|
|
|
validatePasswordMatch(confirmInput) {
|
|
const passwordInput = document.getElementById('password') || document.getElementById('newPassword');
|
|
const isMatch = confirmInput.value === passwordInput.value;
|
|
|
|
confirmInput.setCustomValidity(isMatch ? '' : 'Passwords do not match');
|
|
return isMatch;
|
|
}
|
|
|
|
async handleLogin(e) {
|
|
e.preventDefault();
|
|
|
|
const form = e.target;
|
|
const formData = new FormData(form);
|
|
const loginBtn = document.getElementById('loginBtn');
|
|
|
|
this.setButtonLoading(loginBtn, true);
|
|
this.hideMessages();
|
|
|
|
try {
|
|
const response = await fetch(`${this.apiBaseUrl}/auth/login`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
email: formData.get('email'),
|
|
password: formData.get('password'),
|
|
rememberMe: formData.get('rememberMe') === 'on'
|
|
}),
|
|
credentials: 'include' // Include cookies for session management
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
this.showSuccess('Login successful! Redirecting...');
|
|
|
|
// Store user info if needed
|
|
if (data.user) {
|
|
localStorage.setItem('user', JSON.stringify(data.user));
|
|
}
|
|
|
|
// Redirect to main application
|
|
setTimeout(() => {
|
|
window.location.href = 'index.html';
|
|
}, 1500);
|
|
} else {
|
|
// Check if error is due to unverified email
|
|
if (data.code === 'EMAIL_NOT_VERIFIED') {
|
|
this.showEmailNotVerifiedError(formData.get('email'));
|
|
} else {
|
|
this.showError(data.error || 'Login failed. Please try again.');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Login error:', error);
|
|
this.showError('Network error. Please check your connection and try again.');
|
|
} finally {
|
|
this.setButtonLoading(loginBtn, false);
|
|
}
|
|
}
|
|
|
|
async handleRegistration(e) {
|
|
e.preventDefault();
|
|
|
|
const form = e.target;
|
|
const formData = new FormData(form);
|
|
const registerBtn = document.getElementById('registerBtn');
|
|
|
|
// Validate password requirements
|
|
const password = formData.get('password');
|
|
if (!this.validatePassword(password)) {
|
|
this.showError('Please ensure your password meets all requirements.');
|
|
return;
|
|
}
|
|
|
|
// Validate password confirmation
|
|
const confirmPassword = formData.get('confirmPassword');
|
|
if (password !== confirmPassword) {
|
|
this.showError('Passwords do not match.');
|
|
return;
|
|
}
|
|
|
|
this.setButtonLoading(registerBtn, true);
|
|
this.hideMessages();
|
|
|
|
try {
|
|
const response = await fetch(`${this.apiBaseUrl}/auth/register`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
email: formData.get('email'),
|
|
password: password
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
this.showSuccess('Account created successfully! Please check your email to verify your account.');
|
|
form.reset();
|
|
|
|
// Optionally redirect to login page after a delay
|
|
setTimeout(() => {
|
|
window.location.href = 'login.html';
|
|
}, 3000);
|
|
} else {
|
|
this.showError(data.error || 'Registration failed. Please try again.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Registration error:', error);
|
|
this.showError('Network error. Please check your connection and try again.');
|
|
} finally {
|
|
this.setButtonLoading(registerBtn, false);
|
|
}
|
|
}
|
|
|
|
async handleForgotPassword(e) {
|
|
e.preventDefault();
|
|
|
|
const form = e.target;
|
|
const formData = new FormData(form);
|
|
const resetBtn = document.getElementById('resetBtn');
|
|
|
|
this.setButtonLoading(resetBtn, true);
|
|
this.hideMessages();
|
|
|
|
try {
|
|
const response = await fetch(`${this.apiBaseUrl}/auth/forgot-password`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
email: formData.get('email')
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
this.showSuccess('Password reset link sent! Please check your email.');
|
|
form.reset();
|
|
} else {
|
|
this.showError(data.error || 'Failed to send reset link. Please try again.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Forgot password error:', error);
|
|
this.showError('Network error. Please check your connection and try again.');
|
|
} finally {
|
|
this.setButtonLoading(resetBtn, false);
|
|
}
|
|
}
|
|
|
|
async handleResetPassword(e) {
|
|
e.preventDefault();
|
|
|
|
const form = e.target;
|
|
const formData = new FormData(form);
|
|
const updateBtn = document.getElementById('updatePasswordBtn');
|
|
|
|
// Validate password requirements
|
|
const newPassword = formData.get('newPassword');
|
|
if (!this.validatePassword(newPassword)) {
|
|
this.showError('Please ensure your password meets all requirements.');
|
|
return;
|
|
}
|
|
|
|
// Validate password confirmation
|
|
const confirmPassword = formData.get('confirmNewPassword');
|
|
if (newPassword !== confirmPassword) {
|
|
this.showError('Passwords do not match.');
|
|
return;
|
|
}
|
|
|
|
this.setButtonLoading(updateBtn, true);
|
|
this.hideMessages();
|
|
|
|
try {
|
|
const resetToken = this.getResetTokenFromUrl();
|
|
|
|
const response = await fetch(`${this.apiBaseUrl}/auth/reset-password`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
token: resetToken,
|
|
newPassword: newPassword
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
this.showSuccess('Password updated successfully! Redirecting to login...');
|
|
|
|
setTimeout(() => {
|
|
window.location.href = 'login.html';
|
|
}, 2000);
|
|
} else {
|
|
this.showError(data.error || 'Failed to update password. Please try again.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Reset password error:', error);
|
|
this.showError('Network error. Please check your connection and try again.');
|
|
} finally {
|
|
this.setButtonLoading(updateBtn, false);
|
|
}
|
|
}
|
|
|
|
async handleEmailVerification() {
|
|
// Only run on verify-email.html page
|
|
if (!window.location.pathname.includes('verify-email.html')) {
|
|
return;
|
|
}
|
|
|
|
const token = this.getVerificationTokenFromUrl();
|
|
|
|
if (!token) {
|
|
this.showVerificationError('Invalid verification link.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${this.apiBaseUrl}/auth/verify/${token}`, {
|
|
method: 'GET'
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
this.showVerificationSuccess();
|
|
} else {
|
|
this.showVerificationError(data.error || 'Verification failed.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Email verification error:', error);
|
|
this.showVerificationError('Network error during verification.');
|
|
}
|
|
}
|
|
|
|
async handleResendVerification(e) {
|
|
e.preventDefault();
|
|
|
|
const button = e.target;
|
|
this.setButtonLoading(button, true);
|
|
|
|
try {
|
|
// Get email from URL parameters or prompt user
|
|
const email = this.getEmailFromUrl() || prompt('Please enter your email address:');
|
|
|
|
if (!email) {
|
|
this.showError('Email address is required.');
|
|
return;
|
|
}
|
|
|
|
const response = await fetch(`${this.apiBaseUrl}/auth/resend-verification`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ email })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
this.showResendSuccess();
|
|
} else {
|
|
this.showError(data.error || 'Failed to resend verification email.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Resend verification error:', error);
|
|
this.showError('Network error. Please try again.');
|
|
} finally {
|
|
this.setButtonLoading(button, false);
|
|
}
|
|
}
|
|
|
|
handlePasswordReset() {
|
|
// Only run on reset-password.html page
|
|
if (!window.location.pathname.includes('reset-password.html')) {
|
|
return;
|
|
}
|
|
|
|
const token = this.getResetTokenFromUrl();
|
|
if (token) {
|
|
document.getElementById('resetToken').value = token;
|
|
} else {
|
|
this.showError('Invalid reset link.');
|
|
}
|
|
}
|
|
|
|
// Utility methods
|
|
getVerificationTokenFromUrl() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
return urlParams.get('token');
|
|
}
|
|
|
|
getResetTokenFromUrl() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
return urlParams.get('token');
|
|
}
|
|
|
|
getEmailFromUrl() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
return urlParams.get('email');
|
|
}
|
|
|
|
setButtonLoading(button, isLoading) {
|
|
const btnText = button.querySelector('.btn-text');
|
|
const btnLoading = button.querySelector('.btn-loading');
|
|
|
|
if (isLoading) {
|
|
btnText.style.display = 'none';
|
|
btnLoading.style.display = 'flex';
|
|
button.disabled = true;
|
|
} else {
|
|
btnText.style.display = 'block';
|
|
btnLoading.style.display = 'none';
|
|
button.disabled = false;
|
|
}
|
|
}
|
|
|
|
showError(message) {
|
|
const errorDiv = document.getElementById('authError');
|
|
const errorMessage = document.getElementById('errorMessage');
|
|
|
|
if (errorDiv && errorMessage) {
|
|
errorMessage.textContent = message;
|
|
errorDiv.style.display = 'block';
|
|
|
|
// Auto-hide after 5 seconds
|
|
setTimeout(() => {
|
|
errorDiv.style.display = 'none';
|
|
}, 5000);
|
|
}
|
|
}
|
|
|
|
showSuccess(message) {
|
|
const successDiv = document.getElementById('authSuccess');
|
|
const successMessage = document.getElementById('successMessage');
|
|
|
|
if (successDiv && successMessage) {
|
|
successMessage.textContent = message;
|
|
successDiv.style.display = 'block';
|
|
|
|
// Auto-hide after 5 seconds
|
|
setTimeout(() => {
|
|
successDiv.style.display = 'none';
|
|
}, 5000);
|
|
}
|
|
}
|
|
|
|
hideMessages() {
|
|
const errorDiv = document.getElementById('authError');
|
|
const successDiv = document.getElementById('authSuccess');
|
|
|
|
if (errorDiv) errorDiv.style.display = 'none';
|
|
if (successDiv) successDiv.style.display = 'none';
|
|
}
|
|
|
|
showVerificationSuccess() {
|
|
document.getElementById('verificationLoading').style.display = 'none';
|
|
document.getElementById('verificationError').style.display = 'none';
|
|
document.getElementById('verificationSuccess').style.display = 'block';
|
|
}
|
|
|
|
showVerificationError(message) {
|
|
document.getElementById('verificationLoading').style.display = 'none';
|
|
document.getElementById('verificationSuccess').style.display = 'none';
|
|
document.getElementById('verificationError').style.display = 'block';
|
|
|
|
const errorDescription = document.getElementById('errorDescription');
|
|
if (errorDescription) {
|
|
errorDescription.textContent = message;
|
|
}
|
|
}
|
|
|
|
showResendSuccess() {
|
|
document.getElementById('verificationError').style.display = 'none';
|
|
document.getElementById('resendSuccess').style.display = 'block';
|
|
}
|
|
|
|
// Check if user is authenticated (for protected pages)
|
|
static async checkAuth() {
|
|
try {
|
|
const response = await fetch('/api/auth/me', {
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (response.ok) {
|
|
const user = await response.json();
|
|
return user;
|
|
} else {
|
|
return null;
|
|
}
|
|
} catch (error) {
|
|
console.error('Auth check error:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Logout functionality
|
|
static async logout() {
|
|
try {
|
|
await fetch('/api/auth/logout', {
|
|
method: 'POST',
|
|
credentials: 'include'
|
|
});
|
|
|
|
// Clear local storage
|
|
localStorage.removeItem('user');
|
|
|
|
// Redirect to login
|
|
window.location.href = 'login.html';
|
|
} catch (error) {
|
|
console.error('Logout error:', error);
|
|
// Force redirect even if logout request fails
|
|
window.location.href = 'login.html';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize auth manager when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
new AuthManager();
|
|
});
|
|
|
|
// Export for use in other scripts
|
|
window.AuthManager = AuthManager; |