Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/controllers/auth.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -181,5 +209,6 @@ const resetPassword = async (req, res, next) => {
module.exports = {
register,
login,
logout,
resetPassword,
};
59 changes: 59 additions & 0 deletions src/middlewares/auth.js
Original file line number Diff line number Diff line change
@@ -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;
5 changes: 4 additions & 1 deletion src/routes/auth.routes.js
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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);

Expand Down