WIP
This commit is contained in:
514
frontend/auth-script.js
Normal file
514
frontend/auth-script.js
Normal 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;
|
||||
Reference in New Issue
Block a user