diff --git a/Backend/app.js b/Backend/app.js index 995685f..274d922 100644 --- a/Backend/app.js +++ b/Backend/app.js @@ -4,18 +4,23 @@ dotenv.config(); const express = require('express'); const cors = require('cors'); const app = express(); +const cookieParser = require('cookie-parser'); const connectToDb = require('./db/db'); const userRoutes = require('./routes/user.routes'); +const captainRoutes = require('./routes/captain.routes'); connectToDb(); app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); +app.use(cookieParser()); app.get('/',(req,res) =>{ res.send('hello world'); }); app.use('/users', userRoutes); + +app.use('/captains', captainRoutes); module.exports = app; \ No newline at end of file diff --git a/Backend/controllers/captain.controller.js b/Backend/controllers/captain.controller.js new file mode 100644 index 0000000..a71fa57 --- /dev/null +++ b/Backend/controllers/captain.controller.js @@ -0,0 +1 @@ +const captainModel = require('../models/captain.model'); diff --git a/Backend/controllers/user.controller.js b/Backend/controllers/user.controller.js index 274c5b0..3dc5068 100644 --- a/Backend/controllers/user.controller.js +++ b/Backend/controllers/user.controller.js @@ -3,9 +3,10 @@ const userModel = require('../models/user.model'); const userService = require('../services/user.service'); const { validationResult } = require('express-validator'); +const blackListTokenModel = require('../models/blacklistToken.model'); -module.exports.registerUSer = async (req, res,next) => { +module.exports.registerUser = async (req, res,next) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); @@ -16,7 +17,7 @@ module.exports.registerUSer = async (req, res,next) => { const hashedPassword = await userModel.hashPassword(password); - const user = await userService.createUSer({ + const user = await userService.createUser({ firstname : fullname.firstname, lastname : fullname.lastname, email, @@ -44,8 +45,29 @@ module.exports.loginUser = async (req, res, next) => { if(!isMatch){ return res.status(401).json({message : 'Ivalid email or password'}); } - + const token = user.generateAuthToken(); + + res.cookie('token', token); + res.status(200).json({token, user}); +} + +module.exports.getUserProfile = async (req, res, next) => { + res.status(200).json(req.user); +} + +module.exports.logoutUser = async (req, res, next) => { + + res.clearCookie('token'); + + const token = req.cookies.token || req.headers.authorization?.split(' ')[1]; + + await blackListTokenModel.create({ token }); + + + res.status(200).json({message: 'Logged out successfully'}); + + } \ No newline at end of file diff --git a/Backend/middlewares/auth.middleware.js b/Backend/middlewares/auth.middleware.js new file mode 100644 index 0000000..ac583c9 --- /dev/null +++ b/Backend/middlewares/auth.middleware.js @@ -0,0 +1,23 @@ +const userModel = require('../models/user.model'); +const becrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); + +module.exports.authUser = async (req, res, next) => { + const token = req.cookies.token || (req.headers.authorization && req.headers.authorization.split(' ')[1]); + + if(!token) { + return res.status(401).json({message: 'Unauthorized'}); + } + + const isBlacklisted = await userModel.findOne({ token: token }); + + try{ + const decoded = jwt.verify(token,process.env.JWT_SECRET); + const user = await userModel.findById(decoded._id); + + req.user = user; + return next(); + }catch(err) { + return res.status(401).json({message: 'Unauthorized'}); + } +} \ No newline at end of file diff --git a/Backend/models/blacklistToken.model.js b/Backend/models/blacklistToken.model.js new file mode 100644 index 0000000..e689864 --- /dev/null +++ b/Backend/models/blacklistToken.model.js @@ -0,0 +1,8 @@ +const mongoose = require('mongoose'); + +const blacklistTokenSchema = new mongoose.Schema({ + token: { type: String, required: true, unique: true }, + createdAt: { type: Date, default: Date.now, expires: 86400 } // 24 hours TTL +}); + +module.exports = mongoose.model('BlacklistToken', blacklistTokenSchema); \ No newline at end of file diff --git a/Backend/models/captain.model.js b/Backend/models/captain.model.js new file mode 100644 index 0000000..899f647 --- /dev/null +++ b/Backend/models/captain.model.js @@ -0,0 +1,98 @@ +const mongoose = require('mongoose'); +const bcrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); + + +const captainSchema = new mongoose.Schema({ + fullname: { + firstname: { + type: String, + required: true, + minLength: [3, 'First name should contain at least three characters'], + }, + lastname: { + type: String, + + minLength: [3, 'Last name should contain at least three characters'], + } + }, + email: { + type: String, + required: true, + unique: true, + lowercase: true, + match: [/.+\@.+\..+/, 'Please fill a valid email address'], + }, + password: { + type: String, + required: true, + select: false, + }, + socketId: { + type: String, + }, + + status:{ + type: String, + enum: ['active', 'inactive'], + default: 'inactive', + }, + vehicle : { + color: { + type: String, + required: true, + minLength: [3, 'Color should contain at least three characters'], + }, + plate: { + type: String, + required: true, + minLength: [3, 'Plate should contain at least three characters'], + }, + capacity : { + type: Number, + required: true, + min: [1, 'Capacity should be at least 1'], + + }, + vehicleType: { + type: String, + required: true, + enum: ['car', 'motorcycle','auto'], + + + } + + + }, + location: { + lat:{ + type : Number, + }, + lng : { + type : Number, + } + + } + +}) + + +captainSchema.methods.generateAuthToken = function() { + const token = jwt.sign( + { _id: this._id }, + process.env.JWT_SECRET, + { expiresIn: '24h' } + ); + return token; +} + +captainSchema.methods.comparePassword = async function(password) { + return await bcrypt.compare(password, this.password); +} +captainSchema.statics.hashPassword = async function(password) { + return await bcrypt.hash(password, 10); +} + +const captainModel = mongoose.model('Captain', captainSchema); + +module.exports = captainModel; \ No newline at end of file diff --git a/Backend/models/user.model.js b/Backend/models/user.model.js index ddc8baf..dacf57e 100644 --- a/Backend/models/user.model.js +++ b/Backend/models/user.model.js @@ -34,7 +34,11 @@ const userSchema = new mongoose.Schema({ }) userSchema.methods.generateAuthToken = function(){ - const token = jwt.sign({_id: this._id},process.env.JWT_SECRET); + const token = jwt.sign( + { _id: this._id }, + process.env.JWT_SECRET, + { expiresIn: '24h' } + ); return token; } diff --git a/Backend/package-lock.json b/Backend/package-lock.json index f55bc27..039fb04 100644 --- a/Backend/package-lock.json +++ b/Backend/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "bcrypt": "^6.0.0", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^17.0.0", "express": "^5.1.0", @@ -172,6 +173,25 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, "node_modules/cookie-signature": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", diff --git a/Backend/package.json b/Backend/package.json index de61e8c..607060e 100644 --- a/Backend/package.json +++ b/Backend/package.json @@ -11,6 +11,7 @@ "description": "", "dependencies": { "bcrypt": "^6.0.0", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^17.0.0", "express": "^5.1.0", diff --git a/Backend/routes/captain.routes.js b/Backend/routes/captain.routes.js new file mode 100644 index 0000000..1ea44ad --- /dev/null +++ b/Backend/routes/captain.routes.js @@ -0,0 +1,19 @@ +const express = require('express'); +const router = express.Router(); +const {body} = require('express-validator'); + +router.post('/register',[ + body('email').isEmail().withMessage('Please enter a valid email'), + body('password').isLength({min: 6}).withMessage('Password must be at least 6 characters long'), + body('fullname.firstname').isLength({min: 3}).withMessage('First name must be at least 3 characters long'), + body('vehicle.color').isLength({min: 3}).withMessage('Color must be at least 3 characters long'), + body('vehicle.plate').isLength({min: 3}).withMessage('Plate must be at least 3 characters long'), + body('vehicle.capacity').isInt({min: 1}).withMessage('Capacity must be at least 1'), + body('vehicle.vehicleType').isIn(['car', 'motorcycle', 'auto']).withMessage('Vehicle type must be one of car, motorcycle, or auto') + +], +userController.registerUser); + + + +module.exports = router; \ No newline at end of file diff --git a/Backend/routes/user.routes.js b/Backend/routes/user.routes.js index abdbe32..8d1e365 100644 --- a/Backend/routes/user.routes.js +++ b/Backend/routes/user.routes.js @@ -2,6 +2,7 @@ const express = require('express'); const router = express.Router(); const {body} = require("express-validator"); const userController = require('../controllers/user.controller'); +const authMiddleware = require('../middlewares/auth.middleware'); router.post('/register', [ body('fullname.firstname').isLength({min: 3}).withMessage('First name should contain at least three characters'), @@ -10,7 +11,7 @@ router.post('/register', [ ], -userController.registerUSer); +userController.registerUser); router.post('/login', [ body('email').isEmail().withMessage('Invalid Email'), @@ -19,6 +20,8 @@ router.post('/login', [ userController.loginUser ) +router.get('/profile',authMiddleware.authUser, userController.getUserProfile); +router.get('/logout', authMiddleware.authUser, userController.logoutUser); module.exports = router; \ No newline at end of file diff --git a/Backend/services/captain.service.js b/Backend/services/captain.service.js new file mode 100644 index 0000000..1646029 --- /dev/null +++ b/Backend/services/captain.service.js @@ -0,0 +1,33 @@ +const captainModel = require('../models/captain.model'); + +module.exports.createCaptain = async ({ + firstname, + lastname, + email, + password, + color, + plate, + capacity, + vehicleType, +}) => { + if (!firstname || !lastname || !email || !password || !color || !plate || !capacity || !vehicleType) { + throw new Error('All fields are required'); + } + + const captain = captainModel.create({ + fullname: { + firstname, + lastname, + }, + email, + password, + vehicle: { + color, + plate, + capacity, + vehicleType, + }, + }); + + return captain; +};