create-bodhi-node-boilerplate
Version:
Create a Node.js project with basic folder structure and server setup
338 lines (307 loc) • 9.48 kB
JavaScript
const User = require('../models/userModel');
const jwt = require('jsonwebtoken');
const config = require('../config/config');
/**
* @desc Register a new user
* @route POST /api/v1/auth/register
* @access Public
*/
exports.register = async (req, res) => {
try {
const { username, email, password } = req.body;
// Input validation
if (!username || !email || !password) {
return res.status(400).json({
success: false,
message: 'Missing required fields',
error: {
code: 'VALIDATION_ERROR',
details: 'Username, email, and password are required'
}
});
}
// Check if user already exists
const existingUser = await User.findOne({
$or: [
{ email: email.toLowerCase() },
{ username: username.toLowerCase() }
]
});
if (existingUser) {
return res.status(409).json({
success: false,
message: 'User already exists',
error: {
code: 'USER_EXISTS',
details: existingUser.email === email.toLowerCase()
? 'Email already registered'
: 'Username already taken'
}
});
}
// Create user
const user = await User.create({
username: username.toLowerCase(),
email: email.toLowerCase(),
password
});
// Generate tokens
const accessToken = user.getSignedJwtToken();
const refreshToken = user.getRefreshToken();
res.status(201).json({
success: true,
message: 'User registered successfully',
data: {
user: {
id: user._id,
username: user.username,
email: user.email
},
tokens: {
accessToken,
refreshToken
}
}
});
} catch (error) {
res.status(400).json({
success: false,
message: 'Registration failed',
error: {
code: 'REGISTRATION_FAILED',
details: error.message
}
});
}
};
/**
* @desc Login user
* @route POST /api/v1/auth/login
* @access Public
*/
exports.login = async (req, res) => {
try {
const { email, password } = req.body;
// Input validation
if (!email || !password) {
return res.status(400).json({
success: false,
message: 'Missing required fields',
error: {
code: 'VALIDATION_ERROR',
details: 'Email and password are required'
}
});
}
// Check for user and include password for comparison
const user = await User.findOne({ email: email.toLowerCase() }).select('+password');
if (!user) {
return res.status(401).json({
success: false,
message: 'Invalid credentials',
error: {
code: 'AUTH_INVALID_CREDENTIALS',
details: 'Invalid email or password'
}
});
}
// Verify password
const isMatch = await user.matchPassword(password);
if (!isMatch) {
return res.status(401).json({
success: false,
message: 'Invalid credentials',
error: {
code: 'AUTH_INVALID_CREDENTIALS',
details: 'Invalid email or password'
}
});
}
// Generate tokens
const accessToken = user.getSignedJwtToken();
const refreshToken = user.getRefreshToken();
// Save refresh token hash
user.refreshTokenHash = await user.hashToken(refreshToken);
await user.save();
res.status(200).json({
success: true,
message: 'Login successful',
data: {
user: {
id: user._id,
username: user.username,
email: user.email
},
tokens: {
accessToken,
refreshToken
}
}
});
} catch (error) {
res.status(400).json({
success: false,
message: 'Login failed',
error: {
code: 'LOGIN_FAILED',
details: error.message
}
});
}
};
/**
* @desc Get current user profile
* @route GET /api/v1/auth/me
* @access Private
*/
exports.getProfile = async (req, res) => {
try {
const user = await User.findById(req.user.id);
if (!user) {
return res.status(404).json({
success: false,
message: 'User not found',
error: {
code: 'USER_NOT_FOUND',
details: 'User no longer exists'
}
});
}
res.status(200).json({
success: true,
data: {
user: {
id: user._id,
username: user.username,
email: user.email
}
}
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Error retrieving profile',
error: {
code: 'PROFILE_RETRIEVAL_ERROR',
details: error.message
}
});
}
};
/**
* @desc Refresh access token
* @route POST /api/v1/auth/refresh-token
* @access Public
*/
exports.refreshToken = async (req, res) => {
try {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(400).json({
success: false,
message: 'Refresh token is required',
error: {
code: 'REFRESH_TOKEN_REQUIRED',
details: 'Please provide a refresh token'
}
});
}
// Verify refresh token
const decoded = jwt.verify(refreshToken, config.jwt.refreshSecret);
// Get user
const user = await User.findById(decoded.id);
if (!user) {
return res.status(401).json({
success: false,
message: 'Invalid refresh token',
error: {
code: 'INVALID_REFRESH_TOKEN',
details: 'User not found'
}
});
}
// Verify refresh token hash
const isValidToken = await user.verifyRefreshToken(refreshToken);
if (!isValidToken) {
return res.status(401).json({
success: false,
message: 'Invalid refresh token',
error: {
code: 'INVALID_REFRESH_TOKEN',
details: 'Token has been revoked or is invalid'
}
});
}
// Generate new tokens
const accessToken = user.getSignedJwtToken();
const newRefreshToken = user.getRefreshToken();
// Update refresh token hash
user.refreshTokenHash = await user.hashToken(newRefreshToken);
await user.save();
res.status(200).json({
success: true,
message: 'Token refreshed successfully',
data: {
tokens: {
accessToken,
refreshToken: newRefreshToken
}
}
});
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
success: false,
message: 'Refresh token expired',
error: {
code: 'REFRESH_TOKEN_EXPIRED',
details: 'Please login again'
}
});
}
res.status(500).json({
success: false,
message: 'Token refresh failed',
error: {
code: 'TOKEN_REFRESH_ERROR',
details: error.message
}
});
}
};
/**
* @desc Logout user
* @route POST /api/v1/auth/logout
* @access Private
*/
exports.logout = async (req, res) => {
try {
const user = await User.findById(req.user.id);
if (!user) {
return res.status(404).json({
success: false,
message: 'User not found',
error: {
code: 'USER_NOT_FOUND',
details: 'User no longer exists'
}
});
}
// Clear refresh token hash
user.refreshTokenHash = null;
await user.save();
res.status(200).json({
success: true,
message: 'Logged out successfully'
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Logout failed',
error: {
code: 'LOGOUT_ERROR',
details: error.message
}
});
}
};