From d0c62d05a5bd1015c5aa61eb7ca973f1b3694c36 Mon Sep 17 00:00:00 2001 From: Ravindranbit Date: Tue, 28 Apr 2026 16:44:51 +0530 Subject: [PATCH] feat: implement backend authentication with JWT and bcrypt - Installed jsonwebtoken and bcryptjs dependencies - Created /api/auth/login endpoint in server.js for credential validation and token generation - Added setup-db.js script to dynamically initialize admin_users table with default account --- .env.example | 8 ++++ package-lock.json | 118 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 12 +++-- server.js | 44 +++++++++++++++++ setup-db.js | 51 ++++++++++++++++++++ 5 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 .env.example create mode 100644 setup-db.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7fda342 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +PORT=3000 +DB_HOST=127.0.0.1 +DB_USER=root +DB_PASSWORD=your_mysql_password +DB_NAME=tamil_culture_waterloo + +# Optional: set this if your MySQL server runs on a different port or remote host. +# DB_PORT=3306 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ad2c1f5..dbc6356 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,11 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "bcryptjs": "^3.0.3", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", + "jsonwebtoken": "^9.0.3", "multer": "^1.4.5-lts.1", "mysql2": "^3.6.0" }, @@ -68,6 +70,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -125,6 +136,12 @@ "node": ">=8" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -326,6 +343,15 @@ "node": ">= 0.4" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -685,6 +711,97 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -1110,7 +1227,6 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, "bin": { "semver": "bin/semver.js" }, diff --git a/package.json b/package.json index aa1b371..62ad9c3 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,15 @@ "author": "", "license": "MIT", "dependencies": { - "express": "^4.18.2", - "mysql2": "^3.6.0", - "multer": "^1.4.5-lts.1", + "bcryptjs": "^3.0.3", + "cors": "^2.8.5", "dotenv": "^16.3.1", - "cors": "^2.8.5" + "express": "^4.18.2", + "jsonwebtoken": "^9.0.3", + "multer": "^1.4.5-lts.1", + "mysql2": "^3.6.0" }, "devDependencies": { "nodemon": "^3.0.1" } -} \ No newline at end of file +} diff --git a/server.js b/server.js index 5190362..94e8118 100644 --- a/server.js +++ b/server.js @@ -5,6 +5,10 @@ const path = require('path'); const fs = require('fs'); require('dotenv').config(); +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); +const JWT_SECRET = process.env.JWT_SECRET || 'supersecret_default_key_change_me_in_production'; + const app = express(); const PORT = process.env.PORT || 3000; @@ -111,6 +115,46 @@ const upload = multer({ } }); +// =============================== +// =============================== +// AUTHENTICATION ROUTES +// =============================== + +// POST login +app.post('/api/auth/login', async (req, res) => { + try { + const { email, password } = req.body; + if (!email || !password) { + return res.status(400).json({ success: false, message: 'Email and password are required' }); + } + + const [rows] = await pool.execute('SELECT * FROM admin_users WHERE email = ?', [email]); + if (rows.length === 0) { + return res.status(401).json({ success: false, message: 'Invalid credentials' }); + } + + const user = rows[0]; + const isMatch = await bcrypt.compare(password, user.password_hash); + if (!isMatch) { + return res.status(401).json({ success: false, message: 'Invalid credentials' }); + } + + const token = jwt.sign({ id: user.id, email: user.email, role: user.role }, JWT_SECRET, { expiresIn: '1d' }); + + res.json({ + success: true, + message: 'Login successful', + data: { + token, + user: { id: user.id, email: user.email, role: user.role } + } + }); + } catch (error) { + console.error('Login error:', error); + res.status(500).json({ success: false, message: 'Server error during login' }); + } +}); + // =============================== // HEALTH CHECK & STATUS ROUTES // =============================== diff --git a/setup-db.js b/setup-db.js new file mode 100644 index 0000000..328be9d --- /dev/null +++ b/setup-db.js @@ -0,0 +1,51 @@ +const mysql = require('mysql2/promise'); +const bcrypt = require('bcryptjs'); +require('dotenv').config(); + +async function setup() { + try { + const pool = mysql.createPool({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + }); + + console.log('Connected to MySQL...'); + + // Create table + await pool.execute(` + CREATE TABLE IF NOT EXISTS admin_users ( + id INT AUTO_INCREMENT PRIMARY KEY, + email VARCHAR(255) NOT NULL UNIQUE, + password_hash VARCHAR(255) NOT NULL, + role VARCHAR(50) DEFAULT 'admin', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `); + console.log('Verified admin_users table exists.'); + + // Check if admin user exists + const [rows] = await pool.execute('SELECT * FROM admin_users WHERE email = ?', ['admin@example.com']); + + if (rows.length === 0) { + const passwordStr = 'adminpassword'; + const hash = await bcrypt.hash(passwordStr, 10); + + await pool.execute('INSERT INTO admin_users (email, password_hash, role) VALUES (?, ?, ?)', [ + 'admin@example.com', hash, 'admin' + ]); + console.log('Default admin user created: admin@example.com / adminpassword'); + } else { + console.log('Default admin user already exists.'); + } + + console.log('Database setup complete.'); + process.exit(0); + } catch (err) { + console.error('Setup failed:', err); + process.exit(1); + } +} + +setup();