From 171fd243de8f80f66ac91acb7cc73116de34a81e Mon Sep 17 00:00:00 2001 From: tornikegerantia <57709793-tornikegerantia@users.noreply.replit.com> Date: Wed, 15 Apr 2026 00:19:58 +0000 Subject: [PATCH] Implement a fully functional user login and registration system Refactor the authentication system to separate login and registration forms, implement password hashing, manage JWT tokens, and enable user data persistence in `backend/data/users.json`. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 375ec6d3-d5af-4f82-ab81-5c60fd4a86a3 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 8d427c3d-aa60-488b-82c4-6cef148ba5d7 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/147e665c-8c0d-48ec-b0ad-fdc89cd4460f/375ec6d3-d5af-4f82-ab81-5c60fd4a86a3/e238nM8 Replit-Helium-Checkpoint-Created: true --- backend/controllers/authController.js | 17 ++-- backend/middleware/authMiddleware.js | 1 + backend/middleware/validationMiddleware.js | 4 +- backend/models/userModel.js | 15 ++-- js/api.js | 89 +++++++++++++++++++-- login.html | 91 ++++++++++++++-------- replit.md | 3 +- 7 files changed, 164 insertions(+), 56 deletions(-) diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js index d5d78f3..20a345d 100644 --- a/backend/controllers/authController.js +++ b/backend/controllers/authController.js @@ -15,19 +15,20 @@ const generateToken = (id) => { exports.register = async (req, res) => { try { const { firstName, lastName, email, password, passwordConfirm } = req.body; + const normalizedEmail = email ? email.toLowerCase().trim() : ''; // Validation if (!firstName || !lastName || !email || !password || !passwordConfirm) { return res.status(400).json({ message: 'All fields are required' }); } - if (!validateEmail(email)) { + if (!validateEmail(normalizedEmail)) { return res.status(400).json({ message: 'Invalid email format' }); } if (!validatePassword(password)) { return res.status(400).json({ - message: 'Password must be at least 8 characters with uppercase, lowercase, and number', + message: 'Password must be at least 6 characters', }); } @@ -36,7 +37,7 @@ exports.register = async (req, res) => { } // Check if user already exists - let user = await User.findOne({ email }); + let user = await User.findOne({ email: normalizedEmail }); if (user) { return res.status(400).json({ message: 'Email already in use' }); } @@ -45,7 +46,7 @@ exports.register = async (req, res) => { user = await User.create({ firstName, lastName, - email, + email: normalizedEmail, password, }); @@ -69,18 +70,18 @@ exports.register = async (req, res) => { exports.login = async (req, res) => { try { const { email, password } = req.body; + const normalizedEmail = email ? email.toLowerCase().trim() : ''; // Validation if (!email || !password) { return res.status(400).json({ message: 'Email and password are required' }); } - if (!validateEmail(email)) { + if (!validateEmail(normalizedEmail)) { return res.status(400).json({ message: 'Invalid email format' }); } - // Check if user exists and get password field - const user = await User.findOne({ email }); + const user = await User.findOne({ email: normalizedEmail }); if (!user) { return res.status(401).json({ message: 'Invalid email or password' }); } @@ -173,7 +174,7 @@ exports.refreshToken = async (req, res) => { // @access Private exports.getMe = async (req, res) => { try { - const user = await User.findById(req.user.id); + const user = await User.findById(req.user._id); res.status(200).json({ success: true, diff --git a/backend/middleware/authMiddleware.js b/backend/middleware/authMiddleware.js index 1a17194..7a5655e 100644 --- a/backend/middleware/authMiddleware.js +++ b/backend/middleware/authMiddleware.js @@ -29,6 +29,7 @@ exports.protect = async (req, res, next) => { return res.status(404).json({ message: 'User not found' }); } + user.id = user._id; req.user = user; next(); } catch (error) { diff --git a/backend/middleware/validationMiddleware.js b/backend/middleware/validationMiddleware.js index 3283c8d..4a5ed95 100644 --- a/backend/middleware/validationMiddleware.js +++ b/backend/middleware/validationMiddleware.js @@ -7,9 +7,7 @@ const validateEmail = (email) => { }; const validatePassword = (password) => { - // At least 8 characters, 1 uppercase, 1 lowercase, 1 number - const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/; - return regex.test(password); + return typeof password === 'string' && password.length >= 6; }; const validatePhone = (phone) => { diff --git a/backend/models/userModel.js b/backend/models/userModel.js index bcb25a5..6f83c4e 100644 --- a/backend/models/userModel.js +++ b/backend/models/userModel.js @@ -1,6 +1,7 @@ const fs = require('fs'); const path = require('path'); const bcrypt = require('bcryptjs'); +const crypto = require('crypto'); // Simple file-based storage for demo purposes const USERS_FILE = path.join(__dirname, '../data/users.json'); @@ -19,10 +20,11 @@ if (!fs.existsSync(USERS_FILE)) { // User Schema (simplified for file storage) class User { constructor(data) { - this._id = data._id || Date.now().toString(); + this._id = data._id || crypto.randomUUID(); + this.id = this._id; this.firstName = data.firstName; this.lastName = data.lastName; - this.email = data.email; + this.email = data.email ? data.email.toLowerCase().trim() : data.email; this.password = data.password; this.phone = data.phone || null; this.address = data.address || {}; @@ -108,7 +110,8 @@ User.findOne = async (query) => { try { const users = await User.find(); if (query.email) { - return users.find(u => u.email === query.email) || null; + const email = query.email.toLowerCase().trim(); + return users.find(u => u.email === email) || null; } return null; } catch (error) { @@ -120,13 +123,13 @@ User.findOne = async (query) => { User.create = async (data) => { try { const users = await User.find(); + const email = data.email.toLowerCase().trim(); - // Check for duplicate email - if (users.some(u => u.email === data.email)) { + if (users.some(u => u.email === email)) { throw new Error('Email already in use'); } - const newUser = new User(data); + const newUser = new User({ ...data, email }); await newUser.save(); return newUser; diff --git a/js/api.js b/js/api.js index fc52184..4b2d44b 100644 --- a/js/api.js +++ b/js/api.js @@ -6,6 +6,26 @@ const API_BASE_URL = (function() { return 'http://localhost:5000/api'; })(); const TOKEN_KEY = 'authToken'; +const USER_KEY = 'authUser'; + +function saveAuth(data) { + if (data && data.token) { + localStorage.setItem(TOKEN_KEY, data.token); + } + if (data && data.user) { + localStorage.setItem(USER_KEY, JSON.stringify(data.user)); + } +} + +function getStoredUser() { + try { + const user = localStorage.getItem(USER_KEY); + return user ? JSON.parse(user) : null; + } catch (error) { + localStorage.removeItem(USER_KEY); + return null; + } +} // Ensure auth methods are available globally for inline scripts window.login = async function(email, password) { @@ -18,8 +38,8 @@ window.login = async function(email, password) { const data = await response.json(); if (data.success) { - localStorage.setItem(TOKEN_KEY, data.token); - console.log('Login successful'); + saveAuth(data); + updateAuthLinks(); return data; } @@ -42,8 +62,8 @@ window.register = async function(firstName, lastName, email, password, passwordC const data = await response.json(); if (data.success) { - localStorage.setItem(TOKEN_KEY, data.token); - console.log('Registration successful'); + saveAuth(data); + updateAuthLinks(); return data; } @@ -62,9 +82,12 @@ window.register = async function(firstName, lastName, email, password, passwordC function logout() { localStorage.removeItem(TOKEN_KEY); - console.log('Logged out'); + localStorage.removeItem(USER_KEY); + updateAuthLinks(); } +window.logout = logout; + function isLoggedIn() { return localStorage.getItem(TOKEN_KEY) !== null; } @@ -73,6 +96,62 @@ function getToken() { return localStorage.getItem(TOKEN_KEY); } +async function getCurrentUser() { + const token = getToken(); + if (!token) return null; + + try { + const response = await fetch(`${API_BASE_URL}/auth/me`, { + headers: { Authorization: `Bearer ${token}` }, + }); + const data = await response.json(); + if (data.success) { + localStorage.setItem(USER_KEY, JSON.stringify(data.user)); + updateAuthLinks(); + return data.user; + } + logout(); + return null; + } catch (error) { + return getStoredUser(); + } +} + +window.getCurrentUser = getCurrentUser; + +function updateAuthLinks() { + if (typeof document === 'undefined') return; + + const user = getStoredUser(); + document.querySelectorAll('.auth-link').forEach(link => { + if (isLoggedIn()) { + const name = user && user.firstName ? user.firstName : 'Account'; + link.textContent = `Logout (${name})`; + link.href = '#'; + link.onclick = event => { + event.preventDefault(); + logout(); + window.location.href = 'login.html'; + }; + } else { + link.textContent = 'Login / Register'; + link.href = 'login.html'; + link.onclick = null; + } + }); +} + +window.updateAuthLinks = updateAuthLinks; + +if (typeof document !== 'undefined') { + document.addEventListener('DOMContentLoaded', () => { + updateAuthLinks(); + if (isLoggedIn()) { + getCurrentUser(); + } + }); +} + // ============================================ // PRODUCT FUNCTIONS // ============================================ diff --git a/login.html b/login.html index c06fda2..da896d6 100644 --- a/login.html +++ b/login.html @@ -17,45 +17,48 @@ -