diff --git a/Dockerfile b/Dockerfile index ea62406..4c04b31 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,7 @@ COPY data-api.js /usr/share/nginx/api/ COPY backup-s3.js /usr/share/nginx/api/ COPY auth-middleware.js /usr/share/nginx/api/ COPY login.html /usr/share/nginx/api/ +COPY inject-password-hash.js /usr/share/nginx/api/ # Copy a custom Nginx configuration that includes the data API proxy COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/index.html b/index.html index 2fdde75..ace1cfb 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,10 @@ Keep My Weight - Personal Weight & Meal Tracker + + + +
@@ -203,6 +207,7 @@ + diff --git a/inject-password-hash.js b/inject-password-hash.js new file mode 100644 index 0000000..ca8ab9a --- /dev/null +++ b/inject-password-hash.js @@ -0,0 +1,45 @@ +/** + * Password Hash Injector for Weight Tracker + * + * This script injects the password hash from environment variables + * into the HTML file before it's served to the client. + */ + +const fs = require('fs'); +const path = require('path'); + +// Default password hash (can be overridden via environment variables) +const DEFAULT_HASH = '$2a$10$EgxHKjDDFcZKtQY9hl/N4.QvEQHCXVnQXw9dzFYlUDVKOcLMGp9eq'; // hash for "password" + +/** + * Inject password hash into HTML file + * @param {string} htmlPath - Path to the HTML file + * @param {string} passwordHash - Bcrypt password hash + */ +function injectPasswordHash(htmlPath, passwordHash) { + try { + // Read the HTML file + let html = fs.readFileSync(htmlPath, 'utf8'); + + // Replace the placeholder with the actual password hash + html = html.replace('$PASSWORD_HASH$', passwordHash); + + // Write the modified HTML back to the file + fs.writeFileSync(htmlPath, html); + + console.log(`Password hash injected into ${htmlPath}`); + } catch (error) { + console.error(`Error injecting password hash: ${error.message}`); + } +} + +// Get password hash from environment variable +const passwordHash = process.env.PASSWORD_HASH || DEFAULT_HASH; + +// Path to the HTML file +const htmlPath = path.join(__dirname, 'public', 'index.html'); + +// Inject password hash +injectPasswordHash(htmlPath, passwordHash); + +console.log('Password hash injection complete'); diff --git a/js/app.js b/js/app.js index a0421bf..810f2fb 100644 --- a/js/app.js +++ b/js/app.js @@ -3,7 +3,10 @@ * Entry point for the application, initializes all components */ document.addEventListener('DOMContentLoaded', () => { - // Initialize all modules + // Initialize authentication first + Auth.init(); + + // Initialize all other modules DataManager.init(); UI.init(); Charts.init(); @@ -12,5 +15,23 @@ document.addEventListener('DOMContentLoaded', () => { UI.renderWeightTable(); UI.renderMealTable(); + // Add logout button to settings tab + const settingsCard = document.querySelector('#settings-content .card:last-child'); + if (settingsCard) { + const logoutSection = document.createElement('div'); + logoutSection.className = 'form-group'; + logoutSection.innerHTML = ` +

Security

+

Log out of your weight tracker application

+ + `; + settingsCard.appendChild(logoutSection); + + // Add logout functionality + document.getElementById('logout-button').addEventListener('click', () => { + Auth.logout(); + }); + } + console.log('Weight Tracker app initialized successfully'); }); diff --git a/js/auth.js b/js/auth.js new file mode 100644 index 0000000..d04d3f9 --- /dev/null +++ b/js/auth.js @@ -0,0 +1,147 @@ +/** + * 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 = ` +
+
+

Keep My Weight

+
+

Simple, private weight & meal tracking

+ +
+
+ + +
+ + + +
+
+ +

Your data stays private, always. This password protects your personal health information.

+
+ `; + + // 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 + }; +})(); diff --git a/supervisord.conf b/supervisord.conf index 47d20d7..b4ec1a8 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -5,6 +5,19 @@ logfile=/dev/stdout logfile_maxbytes=0 pidfile=/var/run/supervisord.pid +[program:inject-password-hash] +command=node /usr/share/nginx/api/inject-password-hash.js +directory=/usr/share/nginx/api +environment=PASSWORD_HASH="%(ENV_PASSWORD_HASH)s" +autostart=true +autorestart=false +startsecs=0 +startretries=1 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + [program:nginx] command=nginx -g 'daemon off;' autostart=true