diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index b08b64e..8352933 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -4,6 +4,34 @@ const jwt = require('jsonwebtoken'); const User = require('../models/User.model'); const { sendSuccess } = require('../utils/response'); +/** + * Logout user by invalidating refresh token + * POST /api/auth/logout + */ +const logout = async (req, res, next) => { + try { + const userId = req.userId; + + // Find the user and clear their refresh token + const user = await User.findById(userId); + if (!user) { + const error = new Error('User not found'); + error.statusCode = 404; + error.isOperational = true; + return next(error); + } + + // Clear the stored refresh token + user.refreshTokenHash = null; + user.refreshTokenExpiresAt = null; + await user.save(); + + return sendSuccess(res, {}, 200, 'Logout successful'); + } catch (error) { + return next(error); + } +}; + /** * Register a new user * POST /api/auth/register @@ -181,5 +209,6 @@ const resetPassword = async (req, res, next) => { module.exports = { register, login, + logout, resetPassword, }; diff --git a/src/middlewares/auth.js b/src/middlewares/auth.js new file mode 100644 index 0000000..aedf172 --- /dev/null +++ b/src/middlewares/auth.js @@ -0,0 +1,59 @@ +const jwt = require('jsonwebtoken'); +const User = require('../models/User.model'); + +/** + * Authentication middleware - verifies JWT token and attaches user to request + */ +const authenticate = async (req, res, next) => { + try { + // Get token from Authorization header + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + const error = new Error('Authentication required'); + error.statusCode = 401; + error.isOperational = true; + return next(error); + } + + const token = authHeader.substring(7); // Remove 'Bearer ' prefix + + // Verify the token + const decoded = jwt.verify(token, process.env.JWT_SECRET); + + // Check if token is an access token + if (decoded.type !== 'access') { + const error = new Error('Invalid token type'); + error.statusCode = 401; + error.isOperational = true; + return next(error); + } + + // Find the user + const user = await User.findById(decoded.sub); + if (!user) { + const error = new Error('User not found'); + error.statusCode = 401; + error.isOperational = true; + return next(error); + } + + // Attach user to request object + req.user = user; + req.userId = user._id.toString(); + + next(); + } catch (error) { + if (error.name === 'JsonWebTokenError') { + error.message = 'Invalid token'; + error.statusCode = 401; + error.isOperational = true; + } else if (error.name === 'TokenExpiredError') { + error.message = 'Token expired'; + error.statusCode = 401; + error.isOperational = true; + } + next(error); + } +}; + +module.exports = authenticate; diff --git a/src/routes/auth.routes.js b/src/routes/auth.routes.js index 074b73c..2fa51f7 100644 --- a/src/routes/auth.routes.js +++ b/src/routes/auth.routes.js @@ -1,6 +1,7 @@ const express = require('express'); -const { register, login, resetPassword } = require('../controllers/auth.controller'); +const { register, login, logout, resetPassword } = require('../controllers/auth.controller'); const validate = require('../middlewares/validate'); +const authenticate = require('../middlewares/auth'); const { registerSchema, loginSchema, resetPasswordSchema } = require('../validators/auth.validators'); const router = express.Router(); @@ -11,6 +12,8 @@ router.post('/register', validate(registerSchema), register); // POST /api/auth/login - Login an existing user router.post('/login', validate(loginSchema), login); +// POST /api/auth/logout - Logout user (requires authentication) +router.post('/logout', authenticate, logout); // PATCH /api/auth/reset-password/:token - Reset user password with token router.patch('/reset-password/:token', validate(resetPasswordSchema), resetPassword);