Skip to content

Commit 2f57a31

Browse files
faid-terenceCalebgisa72
authored andcommitted
Merge pull request #128 from atlp-rwanda/fix-google-auth-3
fixes google authentication issue
2 parents 19e0b84 + 34d8736 commit 2f57a31

File tree

6 files changed

+194
-71
lines changed

6 files changed

+194
-71
lines changed

src/__test__/userServices.test.ts

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import request from 'supertest';
22
import { app, server } from '../index';
3-
import { createConnection, getRepository } from 'typeorm';
3+
import { getRepository } from 'typeorm';
44
import { User, UserInterface } from '../entities/User';
55

66
import { cleanDatabase } from './test-assets/DatabaseCleanup';
@@ -9,11 +9,17 @@ import { dbConnection } from '../startups/dbConnection';
99

1010
import bcrypt from 'bcrypt';
1111
import jwt from 'jsonwebtoken';
12+
import googleAuth from '../services/userServices/googleAuthservice';
13+
import { Request, Response } from 'express';
14+
15+
let req: Partial<Request>;
16+
let res: Partial<Response>;
1217

1318
const userId = uuid();
1419
const user1Id = uuid();
1520
const user2Id = uuid();
1621
const user3Id = uuid();
22+
const user4Id = uuid();
1723

1824
const getAccessToken = (id: string, email: string) => {
1925
return jwt.sign(
@@ -86,17 +92,41 @@ const sampleUser3: UserInterface = {
8692
role: 'VENDOR',
8793
};
8894

95+
const sampleUser4: UserInterface = {
96+
id: user4Id,
97+
firstName: 'user4',
98+
lastName: 'user',
99+
100+
password: '',
101+
userType: 'Admin',
102+
verified: true,
103+
twoFactorEnabled: true,
104+
twoFactorCode: '123456',
105+
twoFactorCodeExpiresAt: new Date(Date.now() + 10 * 60 * 1000),
106+
gender: 'Male',
107+
phoneNumber: '126380996347',
108+
photoUrl: 'https://example.com/photo.jpg',
109+
role: 'ADMIN',
110+
};
111+
112+
89113
beforeAll(async () => {
90114
const connection = await dbConnection();
91115
sampleUser.password = await bcrypt.hash('password', 10);
92116
sampleUser2.password = await bcrypt.hash('password', 10);
93117
sampleUser3.password = await bcrypt.hash('password', 10);
118+
sampleUser4.password = await bcrypt.hash('password', 10);
94119

95120
const userRepository = connection?.getRepository(User);
96121
await userRepository?.save({ ...sampleUser });
97122
await userRepository?.save({ ...sampleUser1 });
98123
await userRepository?.save({ ...sampleUser2 });
99124
await userRepository?.save({ ...sampleUser3 });
125+
await userRepository?.save({ ...sampleUser4 });
126+
127+
res = {
128+
redirect: jest.fn(),
129+
};
100130
});
101131

102132
afterAll(async () => {
@@ -131,6 +161,34 @@ describe('User service Test', () => {
131161
});
132162
});
133163

164+
it('admin should get all registered user', async () => {
165+
// Arrange
166+
167+
// Act
168+
const res = await request(app).get('/user/allUsers').set(
169+
{
170+
'authorization': `Bearer ${getAccessToken(sampleUser4.id!, sampleUser4.email)}`
171+
}
172+
)
173+
// Assert
174+
expect(res.status).toBe(200);
175+
expect(res.body.users).toBeDefined();
176+
});
177+
178+
it('admin should be able to get data for a single user', async () => {
179+
// Arrange
180+
181+
// Act
182+
const res = await request(app).get(`/user/single/${sampleUser.id}`).set(
183+
{
184+
'authorization': `Bearer ${getAccessToken(sampleUser4.id!, sampleUser4.email)}`
185+
}
186+
)
187+
// Assert
188+
expect(res.status).toBe(200);
189+
expect(res.body.user).toBeDefined();
190+
});
191+
134192
it('should Login a user, with valid credentials', async () => {
135193
const res = await request(app).post('/user/login').send({
136194
@@ -533,4 +591,71 @@ describe('User service Test', () => {
533591
expect(res.body).toEqual({ status: 'error', message: 'Incorrect email or password' });
534592
}, 10000);
535593
});
594+
595+
describe('google OAuth controller', () => {
596+
it('should redirect with error status, when something went wrong on server', async () => {
597+
await googleAuth(req as Request, res as Response);
598+
expect(res.redirect).toHaveBeenCalledWith(`${process.env.CLIENT_URL}/login/google-auth?status=error`);
599+
});
600+
it('should redirect with success status', async () => {
601+
req = {
602+
user: {
603+
id: '123',
604+
firstName: 'sample',
605+
lastName: 'User',
606+
607+
role: 'user',
608+
status: 'active',
609+
twoFactorEnabled: false,
610+
phoneNumber: '1234567890',
611+
},
612+
};
613+
614+
await googleAuth(req as Request, res as Response);
615+
expect(res.redirect).toHaveBeenCalled();
616+
});
617+
618+
it('should redirect with userSuspended status', async () => {
619+
req = {
620+
user: {
621+
id: '123',
622+
firstName: 'sample',
623+
lastName: 'User',
624+
625+
role: 'user',
626+
status: 'suspended',
627+
twoFactorEnabled: false,
628+
phoneNumber: '1234567890',
629+
},
630+
};
631+
632+
await googleAuth(req as Request, res as Response);
633+
expect(res.redirect).toHaveBeenCalledWith(`${process.env.CLIENT_URL}/login/google-auth?status=userSuspended`);
634+
});
635+
636+
it('should redirect with otp status', async () => {
637+
req = {
638+
user: {
639+
id: '123',
640+
firstName: 'sample',
641+
lastName: 'User',
642+
643+
role: 'user',
644+
status: 'active',
645+
twoFactorEnabled: true,
646+
phoneNumber: '1234567890',
647+
},
648+
};
649+
650+
await googleAuth(req as Request, res as Response);
651+
expect(res.redirect).toHaveBeenCalledWith(`${process.env.CLIENT_URL}/login/google-auth?status=otp&[email protected]`);
652+
});
653+
654+
it('should redirect with userNotFound status', async () => {
655+
req.user = undefined;
656+
await googleAuth(req as Request, res as Response);
657+
expect(res.redirect).toHaveBeenCalledWith(`${process.env.CLIENT_URL}/login/google-auth?status=userNotFound`);
658+
});
659+
660+
});
536661
});

src/controllers/authController.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import getAllUsers from '../services/userServices/getAllUsers';
1818
import getUserById from '../services/userServices/getUserById';
1919
import getUserProfile from '../services/userServices/getUserProfile';
2020
import userUpdateProfilePicture from '../services/userServices/userUpdateProfileImage';
21+
import googleAuth from '../services/userServices/googleAuthservice';
2122

2223
export const userRegistration = async (req: Request, res: Response) => {
2324
await userRegistrationService(req, res);
@@ -87,3 +88,6 @@ export const getUserProfileController = async (req: Request, res: Response) => {
8788
export const userUpdateProfilePictureController = async (req: Request, res: Response) => {
8889
await userUpdateProfilePicture(req, res);
8990
};
91+
export const googleOAuthController = async (req: Request, res: Response) => {
92+
await googleAuth(req, res);
93+
};

src/routes/UserRoutes.ts

Lines changed: 4 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
import { RequestHandler, Router } from 'express';
2-
import { responseError } from '../utils/response.utils';
3-
import { UserInterface } from '../entities/User';
4-
import jwt from 'jsonwebtoken';
52
import {
63
disable2FA,
74
enable2FA,
@@ -17,17 +14,14 @@ import {
1714
getUserByIdController,
1815
getUserProfileController,
1916
userUpdateProfilePictureController,
17+
googleOAuthController,
2018
} from '../controllers';
2119

2220
import { activateUser, disactivateUser, userProfileUpdate } from '../controllers/index';
2321
import { hasRole } from '../middlewares/roleCheck';
2422
import upload from '../middlewares/multer';
2523
import passport from 'passport';
2624
import '../utils/auth';
27-
import { start2FAProcess } from '../services/userServices/userStartTwoFactorAuthProcess';
28-
import { otpTemplate } from '../helper/emailTemplates';
29-
import { sendOTPEmail } from '../services/userServices/userSendOTPEmail';
30-
import { sendOTPSMS } from '../services/userServices/userSendOTPMessage';
3125
import { authMiddleware } from '../middlewares/verifyToken';
3226
const router = Router();
3327

@@ -53,57 +47,9 @@ router.get('/google-auth', passport.authenticate('google', { scope: ['profile',
5347
router.get(
5448
'/auth/google/callback',
5549
passport.authenticate('google', {
56-
successRedirect: `${process.env.CLIENT_URL}/login/google-auth`,
57-
failureRedirect: `${process.env.CLIENT_URL}/login/google-auth`,
58-
})
50+
failureRedirect: `${process.env.CLIENT_URL}/login/google-auth?status='GoogleOAuthFailure'`,
51+
}),
52+
googleOAuthController
5953
);
60-
router.get('/login/success', async (req, res) => {
61-
const user = req.user as UserInterface;
62-
63-
if (!user) {
64-
responseError(res, 404, 'user not found');
65-
return;
66-
}
67-
68-
if (user.status === 'suspended') {
69-
return res.status(400).json({ status: 'error', message: 'Your account has been suspended' });
70-
}
71-
72-
if (!user.twoFactorEnabled) {
73-
const payload = {
74-
id: user?.id,
75-
firstName: user.firstName,
76-
lastName: user.lastName,
77-
email: user?.email,
78-
role: user?.role,
79-
};
80-
const token = jwt.sign(payload, process.env.JWT_SECRET as string, { expiresIn: '24h' });
81-
return res.status(200).json({
82-
status: 'success',
83-
data: {
84-
token: token,
85-
message: 'Login success',
86-
},
87-
});
88-
}
89-
const otpCode = await start2FAProcess(user.email);
90-
const OTPEmailcontent = otpTemplate(user.firstName, otpCode.toString());
91-
await sendOTPEmail('Login OTP Code', user.email, OTPEmailcontent);
92-
await sendOTPSMS(user.phoneNumber, otpCode.toString());
93-
return res.status(200).json({
94-
status: 'success',
95-
data: {
96-
email: user.email,
97-
message: 'Please provide the OTP sent to your email or phone',
98-
},
99-
});
100-
});
101-
102-
router.get('/login/failed', async (req, res) => {
103-
res.status(401).json({
104-
status: false,
105-
message: 'Login failed',
106-
});
107-
});
10854

10955
export default router;

src/services/couponServices/buyerApplyCoupon.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ export const buyerApplyCouponService = async (req: Request, res: Response) => {
2828
if (coupon.usageTimes == coupon.maxUsageLimit) {
2929
return res.status(400).json({ message: 'Coupon Discount Ended' });
3030
}
31+
32+
if (req.user?.id) {
33+
if (coupon.usedBy.includes(req.user.id)) {
34+
return res.status(400).json({ message: 'You already used this coupon discount' });
35+
}
36+
}
3137
}
3238
const couponProductId = coupon.product.id;
3339

@@ -79,22 +85,20 @@ export const buyerApplyCouponService = async (req: Request, res: Response) => {
7985
await sendNotification({
8086
content: `Coupon Code successfully activated discount on product: ${couponCartItem.product.name}`,
8187
type: 'coupon',
82-
user: cart.user
83-
})
88+
user: cart.user,
89+
});
8490

8591
await sendNotification({
8692
content: `Buyer: "${cart?.user.firstName} ${cart?.user.lastName}" used coupon and got discount on product: "${couponCartItem.product.name}"`,
87-
type:'coupon',
88-
user: coupon.vendor
93+
type: 'coupon',
94+
user: coupon.vendor,
8995
});
9096

91-
return res
92-
.status(200)
93-
.json({
94-
message: `Coupon Code successfully activated discount on product: ${couponCartItem.product.name}`,
95-
amountDiscounted: amountReducted,
96-
});
97+
return res.status(200).json({
98+
message: `Coupon Code successfully activated discount on product: ${couponCartItem.product.name}`,
99+
amountDiscounted: amountReducted,
100+
});
97101
} catch (error) {
98-
return responseError(res, 500, (error as Error).message);
99-
}
102+
return responseError(res, 500, (error as Error).message);
103+
}
100104
};

src/services/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export * from './userServices/userLoginService';
99
export * from './userServices/userResendOTP';
1010
export * from './userServices/logoutServices';
1111
export * from './userServices/userProfileUpdateServices';
12+
export * from './userServices/googleAuthservice';
1213

1314
// Vendor product services
1415
export * from './productServices/createProduct';
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Request, Response } from 'express';
2+
import { responseError } from '../../utils/response.utils';
3+
import { UserInterface } from '../../entities/User';
4+
import jwt from 'jsonwebtoken';
5+
import { start2FAProcess } from './userStartTwoFactorAuthProcess';
6+
import { otpTemplate } from '../../helper/emailTemplates';
7+
import { sendOTPEmail } from './userSendOTPEmail';
8+
import { sendOTPSMS } from './userSendOTPMessage';
9+
10+
const googleAuth = async (req: Request, res: Response) => {
11+
try {
12+
const user = req.user as UserInterface;
13+
if (!user) {
14+
return res.redirect(`${process.env.CLIENT_URL}/login/google-auth?status=userNotFound`);
15+
}
16+
17+
if (user.status === 'suspended') {
18+
return res.redirect(`${process.env.CLIENT_URL}/login/google-auth?status=userSuspended`);
19+
}
20+
21+
if (!user.twoFactorEnabled) {
22+
const payload = {
23+
id: user?.id,
24+
firstName: user.firstName,
25+
lastName: user.lastName,
26+
email: user?.email,
27+
role: user?.role,
28+
};
29+
const token = jwt.sign(payload, process.env.JWT_SECRET as string, { expiresIn: '24h' });
30+
return res.redirect(`${process.env.CLIENT_URL}/login/google-auth?status=success&token=${token}&role=${user.role?.toLowerCase()}`);
31+
}
32+
33+
const otpCode = await start2FAProcess(user.email);
34+
const OTPEmailcontent = otpTemplate(user.firstName, otpCode.toString());
35+
await sendOTPEmail('Login OTP Code', user.email, OTPEmailcontent);
36+
await sendOTPSMS(user.phoneNumber, otpCode.toString());
37+
return res.redirect(`${process.env.CLIENT_URL}/login/google-auth?status=otp&email=${user.email}`);
38+
} catch (error) {
39+
return res.redirect(`${process.env.CLIENT_URL}/login/google-auth?status=error`);
40+
}
41+
};
42+
43+
export default googleAuth;

0 commit comments

Comments
 (0)