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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ EMAIL_HOST="smtp.gmail.com"
EMAIL_PORT=587
EMAIL_USER="your-email@gmail.com"
EMAIL_PASS="your-app-password"
FRONTEND_URL="http://localhost:3000"
STELLAR_HORIZON_URL="https://horizon-testnet.stellar.org"
STELLAR_NETWORK="testnet"
NODE_ENV=development
Expand Down
52 changes: 52 additions & 0 deletions src/controllers/auth.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const jwt = require('jsonwebtoken');
const User = require('../models/User.model');
const { sendSuccess } = require('../utils/response');
const { sendEmail } = require('../services/email.service');
const passwordResetTemplate = require('../services/templates/passwordReset.template');
const emailVerificationTemplate = require('../services/templates/emailVerification.template');

/**
Expand Down Expand Up @@ -271,10 +272,61 @@ const resetPassword = async (req, res, next) => {
}
};

/**
* Request a password reset email
* POST /api/auth/forgot-password
*/
const forgotPassword = async (req, res, next) => {
try {
const { email } = req.body;
const genericMessage =
'If an account with that email exists, a password reset link has been sent.';

const user = await User.findOne({ email: email.toLowerCase() });

if (!user) {
return sendSuccess(res, {}, 200, genericMessage);
}

const resetToken = crypto.randomBytes(32).toString('hex');
const resetTokenHash = crypto.createHash('sha256').update(resetToken).digest('hex');

user.resetPasswordToken = resetTokenHash;
user.resetPasswordExpires = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
await user.save();

const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3000';
const resetLink = `${frontendUrl}/reset-password?token=${resetToken}`;
const html = passwordResetTemplate(user.fullName, resetLink, '1 hour');

try {
await sendEmail({
to: user.email,
subject: 'Password Reset Request',
html,
});
} catch {
user.resetPasswordToken = null;
user.resetPasswordExpires = null;
await user.save();

const error = new Error('Failed to send password reset email. Please try again later.');
error.statusCode = 500;
error.isOperational = true;
return next(error);
}

return sendSuccess(res, {}, 200, genericMessage);
} catch (error) {
return next(error);
}
};

module.exports = {
register,
login,
logout,
resetPassword,
forgotPassword,
verifyEmail,
};
11 changes: 6 additions & 5 deletions src/routes/auth.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {
login,
logout,
resetPassword,
forgotPassword,
verifyEmail,
} = require('../controllers/auth.controller');
const validate = require('../middlewares/validate');
Expand All @@ -12,6 +13,7 @@ const {
registerSchema,
loginSchema,
resetPasswordSchema,
forgotPasswordSchema,
} = require('../validators/auth.validators');

const router = express.Router();
Expand All @@ -25,12 +27,11 @@ router.post('/login', validate(loginSchema), login);
// POST /api/auth/logout - Logout user (requires authentication)
router.post('/logout', authenticate, logout);

// POST /api/auth/forgot-password - Request a password reset email
router.post('/forgot-password', validate(forgotPasswordSchema), forgotPassword);

// PATCH /api/auth/reset-password/:token - Reset user password with token
router.patch(
'/reset-password/:token',
validate(resetPasswordSchema),
resetPassword
);
router.patch('/reset-password/:token', validate(resetPasswordSchema), resetPassword);

// GET /api/auth/verify-email/:token - Verify email address using token from verification email
router.get('/verify-email/:token', verifyEmail);
Expand Down
4 changes: 4 additions & 0 deletions src/utils/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,22 @@ const getLoggerStream = () => {
};
};


/**
* Logs an informational message
* @param {...any} args - Message and optional metadata
*/

const info = (...args) => {
console.log(...args);
};


/**
* Logs an error message
* @param {...any} args - Message and optional metadata
*/

const error = (...args) => {
console.error(...args);
};
Expand Down
8 changes: 8 additions & 0 deletions src/validators/auth.validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,16 @@ const resetPasswordSchema = Joi.object({
}),
});

const forgotPasswordSchema = Joi.object({
email: Joi.string().email().required().messages({
'string.email': 'Please provide a valid email address',
'any.required': 'Email is required',
}),
});

module.exports = {
registerSchema,
loginSchema,
resetPasswordSchema,
forgotPasswordSchema,
};