Skip to content

Commit

Permalink
fix(google-auth): fixes google authentication issue
Browse files Browse the repository at this point in the history
  • Loading branch information
aimedivin committed Jul 25, 2024
1 parent 19e0b84 commit 34d8736
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 59 deletions.
127 changes: 126 additions & 1 deletion src/__test__/userServices.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<Request>;
let res: Partial<Response>;

const userId = uuid();
const user1Id = uuid();
const user2Id = uuid();
const user3Id = uuid();
const user4Id = uuid();

const getAccessToken = (id: string, email: string) => {
return jwt.sign(
Expand Down Expand Up @@ -86,17 +92,41 @@ const sampleUser3: UserInterface = {
role: 'VENDOR',
};

const sampleUser4: UserInterface = {
id: user4Id,
firstName: 'user4',
lastName: 'user',
email: '[email protected]',
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 () => {
Expand Down Expand Up @@ -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: '[email protected]',
Expand Down Expand Up @@ -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: '[email protected]',
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: '[email protected]',
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: '[email protected]',
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 protected]`);
});

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`);
});

});
});
4 changes: 4 additions & 0 deletions src/controllers/authController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
};
62 changes: 4 additions & 58 deletions src/routes/UserRoutes.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -17,17 +14,14 @@ import {
getUserByIdController,
getUserProfileController,
userUpdateProfilePictureController,
googleOAuthController,
} from '../controllers';

import { activateUser, disactivateUser, userProfileUpdate } from '../controllers/index';
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();

Expand All @@ -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;
1 change: 1 addition & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
43 changes: 43 additions & 0 deletions src/services/userServices/googleAuthservice.ts
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit 34d8736

Please sign in to comment.