commit 3efa2503ea7bf995d67a716b0f72f2df3e6c7bbc Author: fabriceBJHost Date: Tue Jul 1 15:41:16 2025 +0200 server is starting diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..caece3b --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +PORT=3000 +JWT_SECRET=yourSuperSecretKey # Replace with your actual secret key + +# Database configuration +DB_HOST=localhost +DB_USER=root +DB_PASSWORD=yourpassword +DB_NAME=jwt_auth \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a74001d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +node_modules +package-lock.json +.vscode \ No newline at end of file diff --git a/config/databases.js b/config/databases.js new file mode 100644 index 0000000..ab61584 --- /dev/null +++ b/config/databases.js @@ -0,0 +1,11 @@ +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +const pool = mysql.createPool({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, +}); + +module.exports = pool; \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..66a043e --- /dev/null +++ b/index.js @@ -0,0 +1,15 @@ +const express = require('express'); +const authRoutes = require('./routes/authRoute'); +const protectedRoutes = require('./routes/protectedRoute'); +require('dotenv').config(); + +const app = express(); + +app.use(express.json()); + +app.use('/api/auth', authRoutes); +app.use('/api/protected', protectedRoutes); + +app.listen(process.env.PORT, () => { + console.log(`Server running on port ${process.env.PORT}`); +}); diff --git a/middleware/authMiddleware.js b/middleware/authMiddleware.js new file mode 100644 index 0000000..ccd22b7 --- /dev/null +++ b/middleware/authMiddleware.js @@ -0,0 +1,42 @@ +const jwt = require('jsonwebtoken'); +require('dotenv').config(); + +const activeSessions = {}; // store last activity timestamp for tokens + +module.exports = (requiredRole = null) => { + return (req, res, next) => { + const authHeader = req.headers.authorization; + + if (!authHeader) { + return res.status(401).json({ message: 'No token provided' }); + } + + const token = authHeader.split(' ')[1]; + + jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { + if (err) { + return res.status(401).json({ message: 'Invalid token' }); + } + + // Check token last activity + const lastActivity = activeSessions[token]; + const now = Date.now(); + + if (lastActivity && now - lastActivity > 30 * 60 * 1000) { + delete activeSessions[token]; + return res.status(401).json({ message: 'Token expired due to inactivity' }); + } + + // Update last activity + activeSessions[token] = now; + + req.user = decoded; + + if (requiredRole && decoded.role !== requiredRole) { + return res.status(403).json({ message: 'Forbidden. Insufficient role' }); + } + + next(); + }); + }; +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..bdd55aa --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "api-isakafo", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node index.js", + "dev": "nodemon index.js" + }, + "keywords": [], + "author": "C4M-ONG-ADM", + "license": "ISC", + "description": "backend API for Isakafo", + "dependencies": { + "bcryptjs": "^3.0.2", + "dotenv": "^17.0.0", + "express": "^5.1.0", + "jsonwebtoken": "^9.0.2", + "mysql2": "^3.14.1" + }, + "devDependencies": { + "nodemon": "^3.1.10" + } +} diff --git a/routes/authRoute.js b/routes/authRoute.js new file mode 100644 index 0000000..e912e97 --- /dev/null +++ b/routes/authRoute.js @@ -0,0 +1,47 @@ +const express = require('express'); +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); +const pool = require('../config/databases'); +require('dotenv').config(); + +const router = express.Router(); + +router.post('/login', async (req, res) => { + const { username, password } = req.body; + + try { + const [rows] = await pool.query( + 'SELECT * FROM users WHERE username = ?', + [username] + ); + + if (rows.length === 0) { + return res.status(401).json({ message: 'Invalid credentials' }); + } + + const user = rows[0]; + + const isMatch = await bcrypt.compare(password, user.password); + + if (!isMatch) { + return res.status(401).json({ message: 'Invalid credentials' }); + } + + const payload = { + id: user.id, + username: user.username, + role: user.role, + }; + + const token = jwt.sign(payload, process.env.JWT_SECRET, { + expiresIn: '2h', // max lifespan + }); + + res.json({ token }); + } catch (err) { + console.error(err); + res.status(500).json({ message: 'Server error' }); + } +}); + +module.exports = router; diff --git a/routes/protectedRoute.js b/routes/protectedRoute.js new file mode 100644 index 0000000..9572339 --- /dev/null +++ b/routes/protectedRoute.js @@ -0,0 +1,22 @@ +const express = require('express'); +const authMiddleware = require('../middleware/authMiddleware'); + +const router = express.Router(); + +// Open only to logged users +router.get('/profile', authMiddleware(), (req, res) => { + res.json({ + message: 'Welcome to your profile!', + user: req.user, + }); +}); + +// Open only to admins +router.get('/admin', authMiddleware('admin'), (req, res) => { + res.json({ + message: 'Welcome, admin!', + user: req.user, + }); +}); + +module.exports = router;