require('dotenv').config(); const express = require('express'); const session = require('express-session'); const port = process.env.APP_PORT; const app = express(); const cookieParser = require('cookie-parser'); const path = require('path'); const { insertUser, isUserExists, changePassword, db } = require('./backend/db.js'); const { initializeSocket } = require('./backend/socket.js'); const bcrypt = require('bcrypt'); const saltRounds = 10; const { createServer } = require('node:http'); const server = createServer(app); const jwt = require('jsonwebtoken'); const {decode} = require("jsonwebtoken"); const jwtSecret = process.env.JWT_SECRET; app.use(cookieParser()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname, 'public'))); app.use('/static', express.static('frontend')); app.use('/socket.io', express.static('node_modules/socket.io/client-dist')); app.use(session({ secret: process.env.SESSION_SECRET, resave: true, saveUninitialized: true, cookie: { secure: false, maxAge: 30 * 24 * 60 * 60 * 1000 //30 days } })); // auth login API app.post('/auth/login', async (req, res) => { await loginUser(req, res); }); // auth signup API app.post('/auth/signup', async (req, res) => { await signupUser(req, res); }); // logout API app.post('/auth/logout', (req, res) => { // clear JWT token res.clearCookie('token', { path: '/' }); // clear socket.io cookie (no idea what is it for but better remove it) res.clearCookie('io', { path: '/' }); res.status(200).json({ message: 'Successfully logged out', success: true}); }); // get JWT token API app.get('/auth/token', (req, res) => { const token = req.cookies.token; if(!token){ res.send('Not logged in'); } res.send(token); }); app.post('/auth/changepassword', async (req, res) => { const token = req.cookies.token; const { cPassword, nPassword } = req.body; if(!cPassword && nPassword) { return res.json({ message: 'Field is empty' }) } if(nPassword === cPassword) { return res.json({ message: 'Passwords are the same' }) } let username; try { const decoded = jwt.verify(token, jwtSecret); username = username = decoded.username; } catch (err) { return res.status(403).json({ message: 'Unauthorized'}); } try { const result = await db.query('SELECT * FROM accounts WHERE username = $1', [username]); // checks that passwords are matching const match = await bcrypt.compare(cPassword, result.rows[0].password); // if not return information if(!match){ return res.json({ message: 'Current password is invalid', success: false }) } // hash password const salt = await bcrypt.genSalt(saltRounds); const hash = await bcrypt.hash(nPassword, salt); await changePassword(username, hash); //TODO make href to login screen on front after success!!! return res.status(200).json({ message: 'Successfully changed password', success: true }); } catch (err) { return res.status(500).json({ message: 'Failed to change password', success: false}); } }); // get username app.get('/auth/user', (req, res) => { const token = req.cookies.token; // verify token if(token) { jwt.verify(token, jwtSecret, (err, user) => { if(err) { return res.status(403).send('Unauthorized'); } else { const username = user.username; res.json({username}); } }); } }); // serving the login page app.get('/login', (req, res) => { const token = req.cookies.token; // verify token if (token) { res.json({ Error: 'Already logged in' }); } else { res.sendFile(path.join(__dirname, '/frontend/routes/login.html')); } }); // serving the signup page app.get('/signup', (req, res) => { const token = req.cookies.token; if(token){ res.json({ Error: 'Already logged in' }); } else res.sendFile(path.join(__dirname, '/frontend/routes/signup.html')); }); app.get('/settings', (req, res) => { const token = req.cookies.token; if(!token) { res.redirect('/login'); return; } // verify token jwt.verify(token, jwtSecret, (err) => { if (err) { return res.status(403).send('Unauthorized'); } res.sendFile(path.join(__dirname, '/frontend/routes/settings.html')) }); }) app.get('/', (req, res) => { const token = req.cookies.token; if(!token) { return res.redirect('/login'); } else { return res.redirect('/chat'); } }) // serving the chat page if logged in app.get('/chat', (req, res) => { const token = req.cookies.token; if(!token) { res.redirect('/login'); return; } // verify token jwt.verify(token, jwtSecret, (err) => { if (err) { return res.status(403).send('Unauthorized'); } res.sendFile(path.join(__dirname, '/frontend/routes/chat.html')); }); }); initializeSocket(server) // run server server.listen(port, () => { console.log(`Chat app listening on port ${port}`); }); // signup function async function signupUser(req, res) { let username = req.body.username; let password = req.body.password; if(username && password){ try { // Check if user exists const exists = await isUserExists(username); if (exists) { console.log('User already exists'); return res.status(500).json({ message: 'User already exists!' }); } // Hash password const salt = await bcrypt.genSalt(saltRounds); const hash = await bcrypt.hash(password, salt); // Insert user await insertUser(username, hash); return res.status(200).json({ message: "Account successfully created" }); } catch (err) { console.error('Error inserting data:', err); return res.status(500).json({ message: 'Error inserting data' }); } } else { res.status(400).json({ message: 'Form is empty'}) } } // login function async function loginUser(req, res) { let username = req.body.username; let password = req.body.password; if (username && password) { try { username = username.trim(); password = password.trim(); const result = await db.query('SELECT * FROM accounts WHERE username = $1', [username]); // check if user exists if (result.rows.length > 0) { // Compare password const match = await bcrypt.compare(password, result.rows[0].password); if (!match) { res.status(401).json({ message: 'Invalid password'}); return; } const token = jwt.sign({ username }, jwtSecret, { expiresIn: '30d' // token expires in 30 days }); res.cookie('token', token, { httpOnly: true, maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days }); req.session.loggedin = true; req.session.username = username; res.status(200).json({ message: 'Successfully logged in', success: true }); } else { res.send('Incorrect Username or Password!'); } } catch (error) { console.error('Error executing query', error); res.status(500).send('Error executing query'); } } else { res.send('Please enter Username and Password!'); } res.end(); }