nbd-app
Version:
🚀 CLI tool to scaffold full-stack authentication starter projects with React, Node.js, and multiple auth providers (Email, Google, and more)
174 lines (158 loc) • 4.26 kB
JavaScript
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const config = require('../config');
const crypto = require("crypto");
const getFrontendUrl = require('../utils/getFrontendUrl');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
/**
* Generate a JWT token for the user
* @param {Object} user - User object with id
* @returns {String} JWT token
*/
const generateToken = (user) => {
if (!process.env.JWT_SECRET) {
throw new Error('JWT_SECRET is not set in environment variables');
}
return jwt.sign(
{ id: user._id },
process.env.JWT_SECRET,
{ expiresIn: config.jwt.expiresIn }
);
};
/**
* Set JWT token in HTTP-only cookie
* @param {Object} res - Express response object
* @param {String} token - JWT token
*/
const sendTokenCookie = (res, token) => {
res.cookie(
config.jwt.cookieName,
token,
config.jwt.cookieOptions
);
};
// Google Strategy Setup
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL,
},
async (accessToken, refreshToken, profile, done) => {
let user = await User.findOne({ email: profile.emails[0].value });
if (!user) {
user = await User.create({
username: profile.displayName,
email: profile.emails[0].value,
isVerified: true,
password: Math.random().toString(36), // random password
});
}
return done(null, user);
}
));
passport.serializeUser((user, done) => done(null, user.id));
passport.deserializeUser(async (id, done) => {
const user = await User.findById(id);
done(null, user);
});
/**
* @Description Logout user
* @Route GET /api/auth/logout
* @Access Private
*/
exports.logout = async (req, res) => {
try {
// Clear the auth cookie with same options as set
res.clearCookie(config.jwt.cookieName, config.jwt.cookieOptions);
res.status(200).json({
success: true,
message: 'Logged out successfully',
});
} catch (error) {
console.error('Logout error:', error);
res.status(500).json({
success: false,
message: 'Logout failed', // Generic message
});
}
};
/**
* @Description Get current logged in user profile
* @Route GET /api/auth/me
* @Access Private
*/
exports.getMe = async (req, res) => {
try {
// User is already attached to req by the protect middleware
// Remove sensitive fields if any
const { password, ...safeUser } = req.user._doc || req.user;
res.status(200).json({
success: true,
user: safeUser,
});
} catch (error) {
console.error('Get user error:', error);
res.status(500).json({
success: false,
message: 'Failed to get user data', // Generic message
});
}
};
/**
* @Description Update user profile
* @Route PUT /api/auth/update
* @Access Private
*/
exports.updateProfile = async (req, res) => {
try {
const { username, email, avatar, bio } = req.body;
const userId = req.user._id;
// Check if username or email already exists for another user
const existingUser = await User.findOne({
$and: [
{ _id: { $ne: userId } },
{
$or: [
{ username: new RegExp(`^${username}$`, 'i') },
{ email: new RegExp(`^${email}$`, 'i') }
]
}
]
});
if (existingUser) {
return res.status(400).json({
success: false,
message: 'Username or email already taken'
});
}
// Update user profile
const updatedUser = await User.findByIdAndUpdate(
userId,
{
username,
email,
avatar,
bio
},
{ new: true, select: '-password' }
);
if (!updatedUser) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}
res.json({
success: true,
message: 'Profile updated successfully',
user: updatedUser
});
} catch (error) {
console.error('Update profile error:', error);
res.status(500).json({
success: false,
message: 'Failed to update profile'
});
}
};