148 lines
5.8 KiB
JavaScript
148 lines
5.8 KiB
JavaScript
/**
|
|
* Authentication Module for Weight Tracker
|
|
* Provides password protection for the application
|
|
*/
|
|
const Auth = (() => {
|
|
// Session storage key
|
|
const AUTH_KEY = 'weight_tracker_auth';
|
|
|
|
// Default password hash (can be overridden via environment variables)
|
|
// This is a bcrypt hash of "password" - should be replaced in production
|
|
let passwordHash = '$2a$10$EgxHKjDDFcZKtQY9hl/N4.QvEQHCXVnQXw9dzFYlUDVKOcLMGp9eq';
|
|
|
|
// Login state
|
|
let isAuthenticated = false;
|
|
|
|
/**
|
|
* Initialize the authentication module
|
|
*/
|
|
const init = () => {
|
|
// Check for password hash in environment variables (passed via meta tag)
|
|
const envPasswordHash = document.querySelector('meta[name="password-hash"]')?.getAttribute('content');
|
|
if (envPasswordHash) {
|
|
passwordHash = envPasswordHash;
|
|
}
|
|
|
|
// Check if already authenticated
|
|
checkAuthStatus();
|
|
|
|
// If not authenticated, show login screen
|
|
if (!isAuthenticated) {
|
|
showLoginScreen();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Check if the user is authenticated
|
|
*/
|
|
const checkAuthStatus = () => {
|
|
const authData = sessionStorage.getItem(AUTH_KEY);
|
|
isAuthenticated = authData === 'true';
|
|
return isAuthenticated;
|
|
};
|
|
|
|
/**
|
|
* Show the login screen
|
|
*/
|
|
const showLoginScreen = () => {
|
|
// Create login overlay
|
|
const overlay = document.createElement('div');
|
|
overlay.className = 'login-overlay';
|
|
overlay.style.position = 'fixed';
|
|
overlay.style.top = '0';
|
|
overlay.style.left = '0';
|
|
overlay.style.width = '100%';
|
|
overlay.style.height = '100%';
|
|
overlay.style.backgroundColor = 'var(--background-color, #f8f9fa)';
|
|
overlay.style.zIndex = '9999';
|
|
overlay.style.display = 'flex';
|
|
overlay.style.justifyContent = 'center';
|
|
overlay.style.alignItems = 'center';
|
|
|
|
// Create login form
|
|
overlay.innerHTML = `
|
|
<div class="login-container" style="background-color: var(--card-bg, #ffffff); border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); width: 100%; max-width: 400px; padding: 30px;">
|
|
<div style="text-align: center; margin-bottom: 30px;">
|
|
<h1 style="color: var(--primary-color, #4a6fa5); font-size: 2rem; margin-bottom: 5px;">Keep My Weight</h1>
|
|
</div>
|
|
<p style="color: #666; font-size: 1rem; margin-bottom: 30px; text-align: center;">Simple, private weight & meal tracking</p>
|
|
|
|
<form id="login-form">
|
|
<div style="margin-bottom: 20px;">
|
|
<label for="password" style="display: block; margin-bottom: 8px; font-weight: 500;">Password</label>
|
|
<input type="password" id="password" required style="width: 100%; padding: 12px; border-radius: 8px; border: 1px solid #e0e0e0; font-family: inherit; font-size: 1rem;">
|
|
</div>
|
|
|
|
<button type="submit" style="width: 100%; padding: 12px; background-color: var(--primary-color, #4a6fa5); color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 1rem;">Login</button>
|
|
|
|
<div id="error-message" style="color: #d9534f; margin-top: 20px; text-align: center; font-size: 0.9rem;"></div>
|
|
</form>
|
|
|
|
<p style="margin-top: 30px; text-align: center; font-size: 0.8rem; color: #666;">Your data stays private, always. This password protects your personal health information.</p>
|
|
</div>
|
|
`;
|
|
|
|
// Add to DOM
|
|
document.body.appendChild(overlay);
|
|
|
|
// Handle form submission
|
|
const form = document.getElementById('login-form');
|
|
form.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const password = document.getElementById('password').value;
|
|
const errorMessage = document.getElementById('error-message');
|
|
|
|
try {
|
|
// Use bcrypt.js for password verification
|
|
const bcrypt = window.dcodeIO?.bcrypt;
|
|
if (!bcrypt) {
|
|
throw new Error('bcrypt.js not loaded');
|
|
}
|
|
|
|
// Verify password
|
|
const isMatch = await new Promise((resolve) => {
|
|
bcrypt.compare(password, passwordHash, (err, result) => {
|
|
if (err) {
|
|
console.error('Error verifying password:', err);
|
|
resolve(false);
|
|
} else {
|
|
resolve(result);
|
|
}
|
|
});
|
|
});
|
|
|
|
if (isMatch) {
|
|
// Set as authenticated
|
|
sessionStorage.setItem(AUTH_KEY, 'true');
|
|
isAuthenticated = true;
|
|
|
|
// Remove login overlay
|
|
document.body.removeChild(overlay);
|
|
} else {
|
|
errorMessage.textContent = 'Invalid password. Please try again.';
|
|
}
|
|
} catch (error) {
|
|
console.error('Authentication error:', error);
|
|
errorMessage.textContent = 'An error occurred during authentication. Please try again.';
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Log out the user
|
|
*/
|
|
const logout = () => {
|
|
sessionStorage.removeItem(AUTH_KEY);
|
|
isAuthenticated = false;
|
|
showLoginScreen();
|
|
};
|
|
|
|
// Return public API
|
|
return {
|
|
init,
|
|
checkAuthStatus,
|
|
logout
|
|
};
|
|
})();
|