myex-cli
Version:
Opinionated Express.js framework with CLI tools
262 lines (233 loc) • 6.88 kB
JavaScript
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
import crypto from 'crypto';
import { User } from '../models/user.model.js';
import { Token } from '../models/token.model.js';
import { logger } from '../utils/logger.js';
export const authService = {
/**
* Find a user by email
* @param {string} email - User email
* @returns {Promise<Object|null>} User object or null
*/
findUserByEmail: async (email) => {
try {
return await User.findOne({ email });
} catch (error) {
logger.error(`Error finding user by email: ${error.message}`);
throw error;
}
},
/**
* Create a new user
* @param {Object} userData - User data
* @returns {Promise<Object>} Created user
*/
createUser: async (userData) => {
try {
// Hash password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(userData.password, salt);
// Create new user
const user = new User({
email: userData.email,
password: hashedPassword,
name: userData.name,
role: userData.role || 'user',
});
// Save user to database
await user.save();
return user;
} catch (error) {
logger.error(`Error creating user: ${error.message}`);
throw error;
}
},
/**
* Generate access token for a user
* @param {Object} user - User object
* @returns {string} JWT access token
*/
generateAccessToken: (user) => {
return jwt.sign(
{
id: user._id,
email: user.email,
role: user.role,
},
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN || '1d' }
);
},
/**
* Generate refresh token for a user
* @param {Object} user - User object
* @returns {string} JWT refresh token
*/
generateRefreshToken: (user) => {
return jwt.sign(
{
id: user._id,
},
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
},
/**
* Save refresh token to database
* @param {string} userId - User ID
* @param {string} refreshToken - Refresh token
* @returns {Promise<void>}
*/
saveRefreshToken: async (userId, refreshToken) => {
try {
// Remove existing refresh tokens for the user
await Token.deleteMany({ userId, type: 'refresh' });
// Create new refresh token record
const token = new Token({
userId,
token: refreshToken,
type: 'refresh',
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
});
await token.save();
} catch (error) {
logger.error(`Error saving refresh token: ${error.message}`);
throw error;
}
},
/**
* Verify a refresh token and return the associated user
* @param {string} refreshToken - Refresh token
* @returns {Promise<Object|null>} User object or null
*/
verifyRefreshToken: async (refreshToken) => {
try {
// Verify JWT token
const decoded = jwt.verify(refreshToken, process.env.JWT_SECRET);
// Check if token exists in database
const tokenDoc = await Token.findOne({
userId: decoded.id,
token: refreshToken,
type: 'refresh',
expiresAt: { $gt: new Date() },
});
if (!tokenDoc) {
return null;
}
// Get user
const user = await User.findById(decoded.id);
return user;
} catch (error) {
logger.error(`Error verifying refresh token: ${error.message}`);
return null;
}
},
/**
* Remove a refresh token from the database
* @param {string} refreshToken - Refresh token
* @returns {Promise<void>}
*/
removeRefreshToken: async (refreshToken) => {
try {
await Token.deleteOne({ token: refreshToken, type: 'refresh' });
} catch (error) {
logger.error(`Error removing refresh token: ${error.message}`);
throw error;
}
},
/**
* Generate a password reset token for a user
* @param {string} userId - User ID
* @returns {Promise<string>} Reset token
*/
generatePasswordResetToken: async (userId) => {
try {
// Generate random token
const resetToken = crypto.randomBytes(32).toString('hex');
// Hash token
const hashedToken = crypto
.createHash('sha256')
.update(resetToken)
.digest('hex');
// Remove existing reset tokens for the user
await Token.deleteMany({ userId, type: 'reset' });
// Create new reset token record
const token = new Token({
userId,
token: hashedToken,
type: 'reset',
expiresAt: new Date(Date.now() + 1 * 60 * 60 * 1000), // 1 hour
});
await token.save();
return resetToken;
} catch (error) {
logger.error(`Error generating password reset token: ${error.message}`);
throw error;
}
},
/**
* Verify a password reset token
* @param {string} resetToken - Reset token
* @returns {Promise<string|null>} User ID or null
*/
verifyPasswordResetToken: async (resetToken) => {
try {
// Hash token
const hashedToken = crypto
.createHash('sha256')
.update(resetToken)
.digest('hex');
// Find token in database
const tokenDoc = await Token.findOne({
token: hashedToken,
type: 'reset',
expiresAt: { $gt: new Date() },
});
if (!tokenDoc) {
return null;
}
return tokenDoc.userId;
} catch (error) {
logger.error(`Error verifying password reset token: ${error.message}`);
return null;
}
},
/**
* Update a user's password
* @param {string} userId - User ID
* @param {string} newPassword - New password
* @returns {Promise<void>}
*/
updatePassword: async (userId, newPassword) => {
try {
// Hash new password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(newPassword, salt);
// Update user password
await User.findByIdAndUpdate(userId, { password: hashedPassword });
} catch (error) {
logger.error(`Error updating password: ${error.message}`);
throw error;
}
},
/**
* Invalidate a password reset token
* @param {string} resetToken - Reset token
* @returns {Promise<void>}
*/
invalidatePasswordResetToken: async (resetToken) => {
try {
// Hash token
const hashedToken = crypto
.createHash('sha256')
.update(resetToken)
.digest('hex');
// Delete token from database
await Token.deleteOne({ token: hashedToken, type: 'reset' });
} catch (error) {
logger.error(`Error invalidating password reset token: ${error.message}`);
throw error;
}
},
};