This commit is contained in:
2025-07-20 20:43:06 +02:00
parent 0abee5b794
commit 29592c7fc8
93 changed files with 23400 additions and 131 deletions

514
frontend/auth-script.js Normal file
View File

@ -0,0 +1,514 @@
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;