From 29cd016be328ca3b2932f3cc39147068bb74d0e8 Mon Sep 17 00:00:00 2001 From: Rurangwa Leo <88591087+wayneleon1@users.noreply.github.com> Date: Sat, 29 Jun 2024 08:39:45 +0200 Subject: [PATCH] resolve continue with google (#127) resolve continue with fb redirect controller --- src/controller/userController.ts | 157 ++++++++++++++++++------------ src/middlewares/passport-setup.ts | 90 +++++++++-------- src/routes/auth-routes.ts | 60 +++++++++++- src/routes/userRoutes.ts | 6 +- 4 files changed, 201 insertions(+), 112 deletions(-) diff --git a/src/controller/userController.ts b/src/controller/userController.ts index e2042726..d3760645 100644 --- a/src/controller/userController.ts +++ b/src/controller/userController.ts @@ -9,7 +9,6 @@ import { sendCode } from '../emails/mailer'; import jwt from 'jsonwebtoken'; import errorHandler from '../middlewares/errorHandler'; - // Assuming dbConnection.getRepository(UserModel) returns a repository instance const userRepository = dbConnection.getRepository(UserModel); const roleRepository = dbConnection.getRepository(Role); @@ -26,10 +25,9 @@ interface UpdateRrofileRequestBody { firstName: string; lastName: string; email: string; - password:string; + password: string; } - // Define validation and sanitization rules const registerUserRules = [ check('firstName').isLength({ min: 1 }).withMessage('First name is required'), @@ -80,7 +78,11 @@ export const registerUser = [ ); const confirmLink = `${process.env.APP_URL}/api/v1/confirm?token=${token}`; - process.env.NODE_ENV !== 'test' && await sendEmail('confirm', email, { name: firstName, link: confirmLink }); + process.env.NODE_ENV !== 'test' && + (await sendEmail('confirm', email, { + name: firstName, + link: confirmLink, + })); res.status(201).json({ message: 'User successfully registered', @@ -143,7 +145,7 @@ export const Login = errorHandler(async (req: Request, res: Response) => { ); const confirmLink = `${process.env.APP_URL}/api/v1/confirm?token=${token}`; - if (process.env.NODE_ENV !== 'test'){ + if (process.env.NODE_ENV !== 'test') { await sendEmail('confirm', user.email, { name: user.firstName, link: confirmLink, @@ -166,12 +168,13 @@ export const Login = errorHandler(async (req: Request, res: Response) => { }); } - res - .status(200) - .json({ message: 'Please provide the 2FA code sent to your email.' }); + return res.status(200).json({ + message: 'Please provide the 2FA code sent to your email.', + user: { id: user.id, email: user.email }, + }); } else if (user.userType.name === 'Admin') { const token = jwt.sign({ user }, process.env.JWT_SECRET as jwt.Secret, { - expiresIn: '1h', + expiresIn: '7d', }); return res @@ -179,7 +182,7 @@ export const Login = errorHandler(async (req: Request, res: Response) => { .json({ token, message: 'Admin Logged in successfully' }); } else { const token = jwt.sign({ user }, process.env.JWT_SECRET as jwt.Secret, { - expiresIn: '1h', + expiresIn: '7d', }); return res @@ -205,12 +208,11 @@ export const verify2FA = errorHandler(async (req: Request, res: Response) => { } const token = jwt.sign({ user }, process.env.JWT_SECRET as jwt.Secret, { - expiresIn: '1d', + expiresIn: '7d', }); return res.status(200).json({ token }); }); - // Delete All Users export const deleteAllUsers = async (req: Request, res: Response) => { try { @@ -227,80 +229,107 @@ export const deleteAllUsers = async (req: Request, res: Response) => { // Get All Users export const getAllUsers = errorHandler(async (req: Request, res: Response) => { const users = await userRepository.find(); - return res.status(200).json({message: 'Users fetched successfully', users}); + return res.status(200).json({ message: 'Users fetched successfully', users }); }); -export const recoverPassword = errorHandler(async (req: Request, res: Response) => { - const { email } = req.body as { email: string }; +export const recoverPassword = errorHandler( + async (req: Request, res: Response) => { + const { email } = req.body as { email: string }; - const user = await userRepository.findOne({ where: { email } }); + const user = await userRepository.findOne({ where: { email } }); - if (!user) { - return res.status(404).json({ message: 'User not found' }); - } + if (!user) { + return res.status(404).json({ message: 'User not found' }); + } - // Generate a JWT token with the user's email as the payload - const recoverToken = jwt.sign({ email : user.email }, process.env.JWT_SECRET as jwt.Secret, { expiresIn: '1h' }); - - const confirmLink = `${process.env.APP_URL}/api/v1/user/recover/confirm?recoverToken=${recoverToken}`; - - return res.status(200).json({ message: 'Password reset token generated successfully', confirmLink }); + // Generate a JWT token with the user's email as the payload + const recoverToken = jwt.sign( + { email: user.email }, + process.env.JWT_SECRET as jwt.Secret, + { expiresIn: '1h' } + ); -}); + const confirmLink = `${process.env.APP_URL}/api/v1/user/recover/confirm?recoverToken=${recoverToken}`; + + return res.status(200).json({ + message: 'Password reset token generated successfully', + confirmLink, + }); + } +); //password Recover Confirmation -export const updateNewPassword = errorHandler(async (req: Request, res: Response) => { - const recoverToken = req.query.recoverToken as string; -const { password } = req.body as { password: string }; +export const updateNewPassword = errorHandler( + async (req: Request, res: Response) => { + const recoverToken = req.query.recoverToken as string; + const { password } = req.body as { password: string }; -if (!recoverToken) { - return res.status(404).json({ message: 'Invalid or expired token' }); -} + if (!recoverToken) { + return res.status(404).json({ message: 'Invalid or expired token' }); + } -const decoded = jwt.verify(recoverToken, process.env.JWT_SECRET as jwt.Secret) as { - email : string; -}; -const user = await userRepository.findOne({ - where: { email: decoded.email }, -}); + const decoded = jwt.verify( + recoverToken, + process.env.JWT_SECRET as jwt.Secret + ) as { + email: string; + }; + const user = await userRepository.findOne({ + where: { email: decoded.email }, + }); -if (!user) { - return res.status(404).json({ message: 'User not found' }); -} - -const hashedPassword : string = await bcrypt.hash(password, 10); -user.password = hashedPassword; + if (!user) { + return res.status(404).json({ message: 'User not found' }); + } -await userRepository.save(user); + const hashedPassword: string = await bcrypt.hash(password, 10); + user.password = hashedPassword; -return res.status(200).json({ message: 'Password updated successfully' }); + await userRepository.save(user); -}); -export const updateProfile = errorHandler(async (req: Request, res: Response) => { - const userId: number = parseInt(req.params.id); - const { firstName, lastName, email } = req.body as UpdateRrofileRequestBody; + return res.status(200).json({ message: 'Password updated successfully' }); + } +); +export const updateProfile = errorHandler( + async (req: Request, res: Response) => { + const userId: number = parseInt(req.params.id); + const { firstName, lastName, email } = req.body as UpdateRrofileRequestBody; - const user = await userRepository.findOne({ where: { id: userId } }); + const user = await userRepository.findOne({ where: { id: userId } }); - if (!user) { - return res.status(404).json({ error: 'User not found' }); - } + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + user.firstName = firstName || user.firstName; + user.lastName = lastName || user.lastName; - user.firstName = firstName || user.firstName; - user.lastName = lastName || user.lastName; - - const emailExists = await userRepository.findOne({ where: { email } }); - + if (emailExists) { return res.status(400).json({ error: 'Email is already taken' }); } - + user.email = email; - - - await userRepository.save(user); + await userRepository.save(user); + + return res.status(201).json({ message: 'User updated successfully' }); + } +); + +export const deleteUser = errorHandler(async (req: Request, res: Response) => { + const userId: number = parseInt(req.params.userId); + + const user = await userRepository.findOne({ + where: { id: userId }, + }); + + if (!user) { + return res.status(404).json({ message: 'user Not Found' }); + } + + await userRepository.delete(userId); - return res.status(201).json({ message: 'User updated successfully' }); + return res.status(200).json({ message: 'User deleted successfully' }); }); diff --git a/src/middlewares/passport-setup.ts b/src/middlewares/passport-setup.ts index 23e86496..8f36cf92 100644 --- a/src/middlewares/passport-setup.ts +++ b/src/middlewares/passport-setup.ts @@ -3,10 +3,12 @@ import GooglePassport from 'passport-google-oauth20'; import FacebookPassport from 'passport-facebook'; import dbConnection from '../database'; import UserModel from '../database/models/userModel'; +import { Role } from '../database/models/roleEntity'; import dotenv from 'dotenv'; dotenv.config(); const userRepository = dbConnection.getRepository(UserModel); +const roleRepository = dbConnection.getRepository(Role); passport.serializeUser((user: any, done) => { done(null, user.id); @@ -30,16 +32,24 @@ passport.use( async (accessToken: any, refreshToken: any, profile: any, done: any) => { try { // check if user is already exit in db - const existUser = await userRepository.findOneBy({ - googleId: profile.id, + const existUser = await userRepository.find({ + where: [{ googleId: profile.id }, { email: profile.emails[0].value }], + select: { + userType: { + name: true, + }, + }, + relations: ['userType'], }); - if (existUser) { + if (existUser.length > 0) { // already have the user - console.log('user already exist in db:', existUser); - done(null, existUser); + // console.log('user already exist in db:', existUser[0]); + done(null, existUser[0]); } else { // if not , create a new user in db + const userRole = await roleRepository.findOneBy({ name: 'Buyer' }); + const newUser = new UserModel({ firstName: profile.name.givenName, lastName: profile.name.familyName, @@ -48,9 +58,10 @@ passport.use( picture: profile.photos[0].value, provider: profile.provider, isVerified: true, + userType: userRole!, }); + const user = await userRepository.save(newUser); - console.log('User successfully registered'); done(null, user); } } catch (error) { @@ -72,50 +83,45 @@ passport.use( }, async (accessToken: any, refreshToken: any, profile: any, done: any) => { // check if user is already exit in db - const existUser = await userRepository.findOneBy({ - facebookId: profile.id, + let email = profile.email || profile.emails[0].value; + + const existUser = await userRepository.find({ + where: [{ facebookId: profile.id }, { email: email }], + select: { + userType: { + name: true, + }, + }, + relations: ['userType'], }); - if (existUser) { + if (existUser.length > 0) { // already have the user console.log('user already exist in db:', existUser); - done(null, existUser); + done(null, existUser[0]); } else { try { - // check if user is already exit in db - const existUser = await userRepository.findOneBy({ - googleId: profile.id, - }); - - if (existUser) { - // already have the user - console.log('user already exist in db:', existUser); - done(null, existUser); - } else { - // if not , create a new user in db - - // Here we have to check user email because there is people that register with facebook that never verify their email account and there is people that sign up with a phone number, in both those cases their emails will always be undefined. - let email = profile.email || profile.emails[0].value; - - if (!email) { - console.log('this user has no email in his facebook account'); - let err = { message: 'this user is missing an email' }; - return done(err); - } - const newUser = new UserModel({ - firstName: profile.name.givenName, - lastName: profile.name.familyName, - email: email, - facebookId: profile.id, - picture: profile.photos[0].value, - provider: profile.provider, - }); - const user = await userRepository.save(newUser); - - console.log('User successfully registered'); + // if not , create a new user in db + const userRole = await roleRepository.findOneBy({ name: 'Buyer' }); - done(null, user); + // Here we have to check user email because there is people that register with facebook that never verify their email account and there is people that sign up with a phone number, in both those cases their emails will always be undefined. + if (!email) { + console.log('this user has no email in his facebook account'); + let err = { message: 'this user is missing an email' }; + return done(err); } + const newUser = new UserModel({ + firstName: profile.name.givenName, + lastName: profile.name.familyName, + email: email, + facebookId: profile.id, + picture: profile.photos[0].value, + provider: profile.provider, + isVerified: true, + userType: userRole!, + }); + const user = await userRepository.save(newUser); + done(null, user); } catch (error) { console.log('An error occurred while registering the user:', error); } diff --git a/src/routes/auth-routes.ts b/src/routes/auth-routes.ts index 2c87fbbe..6e60a863 100644 --- a/src/routes/auth-routes.ts +++ b/src/routes/auth-routes.ts @@ -1,6 +1,12 @@ import { Router, Request, Response } from 'express'; const authRoutes = Router(); import passport = require('passport'); +import jwt from 'jsonwebtoken'; +import { sendCode } from '../emails/mailer'; +import UserModel from '../database/models/userModel'; +import dbConnection from '../database'; + +const userRepository = dbConnection.getRepository(UserModel); // auth with Google authRoutes.get( @@ -12,8 +18,31 @@ authRoutes.get( authRoutes.get( '/google/redirect', passport.authenticate('google'), - (req: Request, res: Response) => { - return res.send(req.user); + async (req: Request, res: Response) => { + const user = req.user!; + // checking if a user is a Vendor + if (user.userType.name === 'Vendor') { + const twoFactorCode = Math.floor(100000 + Math.random() * 900000); + await userRepository.update(user.id, { twoFactorCode }); + if (process.env.NODE_ENV !== 'test') { + await sendCode(user.email, 'Your 2FA Code', './templates/2fa.html', { + name: user.firstName, + twoFactorCode: twoFactorCode.toString(), + }); + } + return res.status(200).json({ + message: 'Please provide the 2FA code sent to your email.', + user: { id: user.id, email: user.email }, + }); + } + + const token = jwt.sign({ user }, process.env.JWT_SECRET as jwt.Secret, { + expiresIn: '7d', + }); + + return res.json({ + token: token, + }); } ); @@ -27,8 +56,31 @@ authRoutes.get( authRoutes.get( '/facebook/redirect', passport.authenticate('facebook'), - (req: Request, res: Response) => { - res.send(req.user); + async (req: Request, res: Response) => { + const user = req.user!; + // checking if a user is a Vendor + if (user.userType.name === 'Vendor') { + const twoFactorCode = Math.floor(100000 + Math.random() * 900000); + await userRepository.update(user.id, { twoFactorCode }); + if (process.env.NODE_ENV !== 'test') { + await sendCode(user.email, 'Your 2FA Code', './templates/2fa.html', { + name: user.firstName, + twoFactorCode: twoFactorCode.toString(), + }); + } + return res.status(200).json({ + message: 'Please provide the 2FA code sent to your email.', + user: { id: user.id, email: user.email }, + }); + } + + const token = jwt.sign({ user }, process.env.JWT_SECRET as jwt.Secret, { + expiresIn: '7d', + }); + + return res.json({ + token: token, + }); } ); diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index 6b64745f..b78fd7ae 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -9,6 +9,7 @@ import { getAllUsers, deleteAllUsers, updateProfile, + deleteUser, } from '../controller/userController'; import { @@ -25,6 +26,7 @@ userRouter.post('/login', Login); userRouter.post('/verify2FA/:userId', verify2FA); userRouter.get('/getAllUsers', getAllUsers); userRouter.delete('/deleteUsers', deleteAllUsers); +userRouter.delete('/delete/:userId', deleteUser); userRouter.put( '/activate/:userId', IsLoggedIn, @@ -39,7 +41,7 @@ userRouter.put( deactivateAccount ); userRouter.post('/recover', recoverPassword); -userRouter.put('/recover/confirm', updateNewPassword) +userRouter.put('/recover/confirm', updateNewPassword); -userRouter.put('/updateProfile/:id',updateProfile); +userRouter.put('/updateProfile/:id', updateProfile); export default userRouter;