diff --git a/src/__test__/userServices.test.ts b/src/__test__/userServices.test.ts index e8fc8a9..b60dd85 100644 --- a/src/__test__/userServices.test.ts +++ b/src/__test__/userServices.test.ts @@ -1,6 +1,6 @@ import request from 'supertest'; import { app, server } from '../index'; -import { createConnection, getRepository } from 'typeorm'; +import { getRepository } from 'typeorm'; import { User, UserInterface } from '../entities/User'; import { cleanDatabase } from './test-assets/DatabaseCleanup'; @@ -9,11 +9,17 @@ import { dbConnection } from '../startups/dbConnection'; import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; +import googleAuth from '../services/userServices/googleAuthservice'; +import { Request, Response } from 'express'; + +let req: Partial; +let res: Partial; const userId = uuid(); const user1Id = uuid(); const user2Id = uuid(); const user3Id = uuid(); +const user4Id = uuid(); const getAccessToken = (id: string, email: string) => { return jwt.sign( @@ -86,17 +92,41 @@ const sampleUser3: UserInterface = { role: 'VENDOR', }; +const sampleUser4: UserInterface = { + id: user4Id, + firstName: 'user4', + lastName: 'user', + email: 'admin@example.com', + password: '', + userType: 'Admin', + verified: true, + twoFactorEnabled: true, + twoFactorCode: '123456', + twoFactorCodeExpiresAt: new Date(Date.now() + 10 * 60 * 1000), + gender: 'Male', + phoneNumber: '126380996347', + photoUrl: 'https://example.com/photo.jpg', + role: 'ADMIN', +}; + + beforeAll(async () => { const connection = await dbConnection(); sampleUser.password = await bcrypt.hash('password', 10); sampleUser2.password = await bcrypt.hash('password', 10); sampleUser3.password = await bcrypt.hash('password', 10); + sampleUser4.password = await bcrypt.hash('password', 10); const userRepository = connection?.getRepository(User); await userRepository?.save({ ...sampleUser }); await userRepository?.save({ ...sampleUser1 }); await userRepository?.save({ ...sampleUser2 }); await userRepository?.save({ ...sampleUser3 }); + await userRepository?.save({ ...sampleUser4 }); + + res = { + redirect: jest.fn(), + }; }); afterAll(async () => { @@ -131,6 +161,34 @@ describe('User service Test', () => { }); }); + it('admin should get all registered user', async () => { + // Arrange + + // Act + const res = await request(app).get('/user/allUsers').set( + { + 'authorization': `Bearer ${getAccessToken(sampleUser4.id!, sampleUser4.email)}` + } + ) + // Assert + expect(res.status).toBe(200); + expect(res.body.users).toBeDefined(); + }); + + it('admin should be able to get data for a single user', async () => { + // Arrange + + // Act + const res = await request(app).get(`/user/single/${sampleUser.id}`).set( + { + 'authorization': `Bearer ${getAccessToken(sampleUser4.id!, sampleUser4.email)}` + } + ) + // Assert + expect(res.status).toBe(200); + expect(res.body.user).toBeDefined(); + }); + it('should Login a user, with valid credentials', async () => { const res = await request(app).post('/user/login').send({ email: 'user@example.com', @@ -533,4 +591,71 @@ describe('User service Test', () => { expect(res.body).toEqual({ status: 'error', message: 'Incorrect email or password' }); }, 10000); }); + + describe('google OAuth controller', () => { + it('should redirect with error status, when something went wrong on server', async () => { + await googleAuth(req as Request, res as Response); + expect(res.redirect).toHaveBeenCalledWith(`${process.env.CLIENT_URL}/login/google-auth?status=error`); + }); + it('should redirect with success status', async () => { + req = { + user: { + id: '123', + firstName: 'sample', + lastName: 'User', + email: 'samleuser@example.com', + role: 'user', + status: 'active', + twoFactorEnabled: false, + phoneNumber: '1234567890', + }, + }; + + await googleAuth(req as Request, res as Response); + expect(res.redirect).toHaveBeenCalled(); + }); + + it('should redirect with userSuspended status', async () => { + req = { + user: { + id: '123', + firstName: 'sample', + lastName: 'User', + email: 'samleuser@example.com', + role: 'user', + status: 'suspended', + twoFactorEnabled: false, + phoneNumber: '1234567890', + }, + }; + + await googleAuth(req as Request, res as Response); + expect(res.redirect).toHaveBeenCalledWith(`${process.env.CLIENT_URL}/login/google-auth?status=userSuspended`); + }); + + it('should redirect with otp status', async () => { + req = { + user: { + id: '123', + firstName: 'sample', + lastName: 'User', + email: 'samleuser@example.com', + role: 'user', + status: 'active', + twoFactorEnabled: true, + phoneNumber: '1234567890', + }, + }; + + await googleAuth(req as Request, res as Response); + expect(res.redirect).toHaveBeenCalledWith(`${process.env.CLIENT_URL}/login/google-auth?status=otp&email=samleuser@example.com`); + }); + + it('should redirect with userNotFound status', async () => { + req.user = undefined; + await googleAuth(req as Request, res as Response); + expect(res.redirect).toHaveBeenCalledWith(`${process.env.CLIENT_URL}/login/google-auth?status=userNotFound`); + }); + + }); }); diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index eefdf0d..6f013cf 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -18,6 +18,7 @@ import getAllUsers from '../services/userServices/getAllUsers'; import getUserById from '../services/userServices/getUserById'; import getUserProfile from '../services/userServices/getUserProfile'; import userUpdateProfilePicture from '../services/userServices/userUpdateProfileImage'; +import googleAuth from '../services/userServices/googleAuthservice'; export const userRegistration = async (req: Request, res: Response) => { await userRegistrationService(req, res); @@ -87,3 +88,6 @@ export const getUserProfileController = async (req: Request, res: Response) => { export const userUpdateProfilePictureController = async (req: Request, res: Response) => { await userUpdateProfilePicture(req, res); }; +export const googleOAuthController = async (req: Request, res: Response) => { + await googleAuth(req, res); +}; diff --git a/src/routes/UserRoutes.ts b/src/routes/UserRoutes.ts index 6a0cdd9..d4eec2b 100644 --- a/src/routes/UserRoutes.ts +++ b/src/routes/UserRoutes.ts @@ -1,7 +1,4 @@ import { RequestHandler, Router } from 'express'; -import { responseError } from '../utils/response.utils'; -import { UserInterface } from '../entities/User'; -import jwt from 'jsonwebtoken'; import { disable2FA, enable2FA, @@ -17,6 +14,7 @@ import { getUserByIdController, getUserProfileController, userUpdateProfilePictureController, + googleOAuthController, } from '../controllers'; import { activateUser, disactivateUser, userProfileUpdate } from '../controllers/index'; @@ -24,10 +22,6 @@ import { hasRole } from '../middlewares/roleCheck'; import upload from '../middlewares/multer'; import passport from 'passport'; import '../utils/auth'; -import { start2FAProcess } from '../services/userServices/userStartTwoFactorAuthProcess'; -import { otpTemplate } from '../helper/emailTemplates'; -import { sendOTPEmail } from '../services/userServices/userSendOTPEmail'; -import { sendOTPSMS } from '../services/userServices/userSendOTPMessage'; import { authMiddleware } from '../middlewares/verifyToken'; const router = Router(); @@ -53,57 +47,9 @@ router.get('/google-auth', passport.authenticate('google', { scope: ['profile', router.get( '/auth/google/callback', passport.authenticate('google', { - successRedirect: `${process.env.CLIENT_URL}/login/google-auth`, - failureRedirect: `${process.env.CLIENT_URL}/login/google-auth`, - }) + failureRedirect: `${process.env.CLIENT_URL}/login/google-auth?status='GoogleOAuthFailure'`, + }), + googleOAuthController ); -router.get('/login/success', async (req, res) => { - const user = req.user as UserInterface; - - if (!user) { - responseError(res, 404, 'user not found'); - return; - } - - if (user.status === 'suspended') { - return res.status(400).json({ status: 'error', message: 'Your account has been suspended' }); - } - - if (!user.twoFactorEnabled) { - const payload = { - id: user?.id, - firstName: user.firstName, - lastName: user.lastName, - email: user?.email, - role: user?.role, - }; - const token = jwt.sign(payload, process.env.JWT_SECRET as string, { expiresIn: '24h' }); - return res.status(200).json({ - status: 'success', - data: { - token: token, - message: 'Login success', - }, - }); - } - const otpCode = await start2FAProcess(user.email); - const OTPEmailcontent = otpTemplate(user.firstName, otpCode.toString()); - await sendOTPEmail('Login OTP Code', user.email, OTPEmailcontent); - await sendOTPSMS(user.phoneNumber, otpCode.toString()); - return res.status(200).json({ - status: 'success', - data: { - email: user.email, - message: 'Please provide the OTP sent to your email or phone', - }, - }); -}); - -router.get('/login/failed', async (req, res) => { - res.status(401).json({ - status: false, - message: 'Login failed', - }); -}); export default router; diff --git a/src/services/index.ts b/src/services/index.ts index bd8a4ac..363a4d8 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -9,6 +9,7 @@ export * from './userServices/userLoginService'; export * from './userServices/userResendOTP'; export * from './userServices/logoutServices'; export * from './userServices/userProfileUpdateServices'; +export * from './userServices/googleAuthservice'; // Vendor product services export * from './productServices/createProduct'; diff --git a/src/services/userServices/googleAuthservice.ts b/src/services/userServices/googleAuthservice.ts new file mode 100644 index 0000000..e37e5be --- /dev/null +++ b/src/services/userServices/googleAuthservice.ts @@ -0,0 +1,43 @@ +import { Request, Response } from 'express'; +import { responseError } from '../../utils/response.utils'; +import { UserInterface } from '../../entities/User'; +import jwt from 'jsonwebtoken'; +import { start2FAProcess } from './userStartTwoFactorAuthProcess'; +import { otpTemplate } from '../../helper/emailTemplates'; +import { sendOTPEmail } from './userSendOTPEmail'; +import { sendOTPSMS } from './userSendOTPMessage'; + +const googleAuth = async (req: Request, res: Response) => { + try { + const user = req.user as UserInterface; + if (!user) { + return res.redirect(`${process.env.CLIENT_URL}/login/google-auth?status=userNotFound`); + } + + if (user.status === 'suspended') { + return res.redirect(`${process.env.CLIENT_URL}/login/google-auth?status=userSuspended`); + } + + if (!user.twoFactorEnabled) { + const payload = { + id: user?.id, + firstName: user.firstName, + lastName: user.lastName, + email: user?.email, + role: user?.role, + }; + const token = jwt.sign(payload, process.env.JWT_SECRET as string, { expiresIn: '24h' }); + return res.redirect(`${process.env.CLIENT_URL}/login/google-auth?status=success&token=${token}&role=${user.role?.toLowerCase()}`); + } + + const otpCode = await start2FAProcess(user.email); + const OTPEmailcontent = otpTemplate(user.firstName, otpCode.toString()); + await sendOTPEmail('Login OTP Code', user.email, OTPEmailcontent); + await sendOTPSMS(user.phoneNumber, otpCode.toString()); + return res.redirect(`${process.env.CLIENT_URL}/login/google-auth?status=otp&email=${user.email}`); + } catch (error) { + return res.redirect(`${process.env.CLIENT_URL}/login/google-auth?status=error`); + } +}; + +export default googleAuth; \ No newline at end of file