Skip to content

Commit

Permalink
google authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
GSinseswa721 committed May 20, 2024
1 parent 033cde2 commit 6aa2cd7
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 18 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ env:
CLOUDNARY_API_KEY: ${{secrets.CLOUDNARY_API_KEY}}
CLOUDINARY_CLOUD_NAME: ${{secrets.CLOUDINARY_CLOUD_NAME}}
CLOUDINARY_API_SECRET: ${{secrets.CLOUDINARY_API_SECRET}}
GOOGLE_CLIENT_ID: ${{secrets.GOOGLE_CLIENT_ID}}
GOOGLE_CLIENT_SECRET: ${{secrets.GOOGLE_CLIENT_SECRET}}


jobs:
build-lint-test-coverage:
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"cross-env": "^7.0.3",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-session": "^1.18.0",
"express-winston": "^4.2.0",
"highlight.js": "^11.9.0",
"joi": "^17.13.1",
Expand All @@ -43,6 +44,8 @@
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.13",
"nodemon": "^3.1.0",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0",
"pg": "^8.11.5",
"reflect-metadata": "^0.2.2",
"source-map-support": "^0.5.21",
Expand All @@ -66,12 +69,14 @@
"@types/eslint": "^8.56.10",
"@types/eslint__js": "^8.42.3",
"@types/express": "^4.17.21",
"@types/express-session": "^1.18.0",
"@types/jest": "^29.5.12",
"@types/jsend": "^1.0.32",
"@types/jsonwebtoken": "^9.0.6",
"@types/morgan": "^1.9.9",
"@types/node": "^20.12.7",
"@types/nodemailer": "^6.4.15",
"@types/passport-google-oauth20": "^2.0.16",
"@types/reflect-metadata": "^0.1.0",
"@types/supertest": "^6.0.2",
"@types/uuid": "^9.0.8",
Expand Down
37 changes: 37 additions & 0 deletions src/__test__/oauth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import request from 'supertest';
import { app, server } from '../index';
import { createConnection, getConnection, getConnectionOptions, getRepository } from 'typeorm';
import { User } from '../entities/User';

beforeAll(async () => {
// Connect to the test database
const connectionOptions = await getConnectionOptions();

await createConnection({ ...connectionOptions, name: 'testConnection' });
});

afterAll(async () => {
const connection = getConnection('testConnection');
const userRepository = connection.getRepository(User);

// Delete all records from the User
await userRepository.delete({});

// Close the connection to the test database
await connection.close();

server.close();
});
describe('authentication routes test',() => {
it('should redirect to the google authentication page',async() => {
const response = await request(app)
.get('/user/google-auth');
expect(response.statusCode).toBe(302)
})
it('should redirect after google authentication', async() => {
const response = await request(app)
.get('/user/auth/google/callback');
expect(response.statusCode).toBe(302)
})
});

7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import router from './routes';
import { addDocumentation } from './startups/docs';
import 'reflect-metadata';
import cookieParser from 'cookie-parser';
import session from "express-session";
import passport from 'passport';

import { CustomError, errorHandler } from './middlewares/errorHandler';
import morgan from 'morgan';
Expand All @@ -13,6 +15,11 @@ dotenv.config();

export const app = express();
const port = process.env.PORT || 8000;
app.use(session({
secret: 'keyboard cat'
}))
app.use(passport.initialize())
app.use(passport.session())
app.use(express.json());
app.use(cookieParser());
app.use(cors({ origin: '*' }));
Expand Down
34 changes: 22 additions & 12 deletions src/routes/ProductRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Router } from 'express';
import { RequestHandler, Router } from 'express';

import { productStatus } from '../controllers/index';
import { hasRole } from '../middlewares/roleCheck';
Expand All @@ -18,17 +18,27 @@ import {
} from '../controllers';
const router = Router();

// router.get('/all', listAllProducts);
// router.get('/recommended', authMiddleware as any, hasRole('BUYER'), getRecommendedProducts);
// router.get('/collection', authMiddleware as any, hasRole('VENDOR'), readProducts);
// router.get('/', authMiddleware as any, hasRole('BUYER'), readProducts);
// router.get('/:id', singleProduct)
// router.get('/collection/:id', authMiddleware as any, hasRole('VENDOR'), readProduct);
// router.post('/', authMiddleware as any, hasRole('VENDOR'), upload.array('images', 10), createProduct);
// router.put('/:id', authMiddleware as any, hasRole('VENDOR'), upload.array('images', 10), updateProduct);
// router.delete('/images/:id', authMiddleware as any, hasRole('VENDOR'), removeProductImage);
// router.delete('/:id', authMiddleware as any, hasRole('VENDOR'), deleteProduct);
// router.put('/availability/:id', authMiddleware as any, hasRole('VENDOR'), productStatus);
router.get('/all', listAllProducts);
router.get('/recommended', authMiddleware, hasRole('BUYER'), getRecommendedProducts);
router.get('/collection', authMiddleware, hasRole('VENDOR'), readProducts);
router.get('/', authMiddleware, hasRole('BUYER'), readProducts);
router.get('/:id', singleProduct)
router.get('/collection/:id', authMiddleware, hasRole('VENDOR'), readProduct);
router.post('/', authMiddleware, hasRole('VENDOR'), upload.array('images', 10), createProduct);
router.put('/:id', authMiddleware, hasRole('VENDOR'), upload.array('images', 10), updateProduct);
router.delete('/images/:id', authMiddleware, hasRole('VENDOR'), removeProductImage);
router.delete('/:id', authMiddleware, hasRole('VENDOR'), deleteProduct);
router.put('/availability/:id', authMiddleware, hasRole('VENDOR'), productStatus);

router.get('/recommended', authMiddleware as RequestHandler, hasRole('BUYER'), getRecommendedProducts);
router.get('/collection', authMiddleware as RequestHandler, hasRole('VENDOR'), readProducts);
router.get('/', authMiddleware as RequestHandler, hasRole('BUYER'), readProducts);
router.get('/:id', singleProduct);
router.get('/collection/:id', authMiddleware as RequestHandler, hasRole('VENDOR'), readProduct);
router.post('/', authMiddleware as RequestHandler, hasRole('VENDOR'), upload.array('images', 10), createProduct);
router.put('/:id', authMiddleware as RequestHandler, hasRole('VENDOR'), upload.array('images', 10), updateProduct);
router.delete('/images/:id', authMiddleware as RequestHandler, hasRole('VENDOR'), removeProductImage);
router.delete('/:id', authMiddleware as RequestHandler, hasRole('VENDOR'), deleteProduct);
router.put('/availability/:id', authMiddleware as RequestHandler, hasRole('VENDOR'), productStatus);

export default router;
39 changes: 38 additions & 1 deletion src/routes/UserRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Router } from 'express';
import { responseError } from '../utils/response.utils';
import { UserInterface } from '../entities/User';
import jwt from 'jsonwebtoken'
import {
disable2FA,
enable2FA,
Expand All @@ -15,7 +18,8 @@ import {
import { activateUser, disactivateUser, userProfileUpdate } from '../controllers/index';
import { hasRole } from '../middlewares/roleCheck';
import { isTokenValide } from '../middlewares/isValid';

import passport from 'passport';
import "../utils/auth";
const router = Router();

router.post('/register', userRegistration);
Expand All @@ -32,4 +36,37 @@ router.post('/password/reset', userPasswordReset);
router.post('/password/reset/link', sendPasswordResetLink);
router.put('/update', userProfileUpdate);

router.get('/google-auth', passport.authenticate('google', { scope: ['profile', 'email'] }));
router.get("/auth/google/callback",
passport.authenticate("google", {
successRedirect: "/user/login/success",
failureRedirect: "/user/login/failed"
})
);
router.get("/login/success", async (req, res) => {
const user = req.user as UserInterface;
if(!user){
responseError(res, 404, 'user not found')
}
const payload = {
id: user?.id,
email: user?.email,
role: user?.role
}
const token = jwt.sign(payload, process.env.JWT_SECRET as string,{expiresIn: '24h'})
res.status(200).json({
status: 'success',
data:{
token: token,
message: "Login success"
}
})
});
router.get("/login/failed", async (req, res) => {
res.status(401).json({
status: false,
message: "Login failed"
});
});

export default router;
10 changes: 5 additions & 5 deletions src/routes/wishListRoute.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Router } from 'express';
import { RequestHandler, Router } from 'express';
import { authMiddleware } from '../middlewares/verifyToken';
import { hasRole } from '../middlewares';
import { checkUserStatus } from '../middlewares/isAllowed';
import { wishlistAddProduct,wishlistRemoveProduct,wishlistGetProducts,wishlistClearAllProducts } from '../controllers/wishListController';

const router = Router();

router.post('/add/:id', authMiddleware, checkUserStatus, hasRole('BUYER'), wishlistAddProduct);
router.get('/',authMiddleware, checkUserStatus, hasRole('BUYER'),wishlistGetProducts);
router.delete('/delete/:id',authMiddleware, checkUserStatus, hasRole('BUYER'),wishlistRemoveProduct);
router.delete('/clearAll',authMiddleware, checkUserStatus, hasRole('BUYER'),wishlistClearAllProducts);
router.post('/add/:id', authMiddleware as RequestHandler, checkUserStatus, hasRole('BUYER'), wishlistAddProduct);
router.get('/',authMiddleware as RequestHandler, checkUserStatus, hasRole('BUYER'),wishlistGetProducts);
router.delete('/delete/:id',authMiddleware as RequestHandler, checkUserStatus, hasRole('BUYER'),wishlistRemoveProduct);
router.delete('/clearAll',authMiddleware as RequestHandler, checkUserStatus, hasRole('BUYER'),wishlistClearAllProducts);

export default router;
72 changes: 72 additions & 0 deletions src/utils/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* eslint-disable camelcase */
import passport from 'passport';
import { Strategy } from "passport-google-oauth20";
import { User } from '../entities/User';
import { getRepository } from 'typeorm';
import bcrypt from 'bcrypt';
import "../utils/auth";
passport.use(
new Strategy(
{
clientID: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
callbackURL: 'http://localhost:6890/user/auth/google/callback/',
scope: ['email', 'profile'],
},
async (accessToken: any, refreshToken: any, profile: any, cb: any) => {
const userRepository = getRepository(User);
const { family_name,
name,
picture,
email,
email_verified

} = profile._json;
const { familyName, givenName } = profile.name;

if (email || givenName || family_name || picture) {
try {
// Check for existing user
const existingUser = await userRepository.findOneBy({ email });

if (existingUser) {
return await cb(null, existingUser);
}
const saltRounds = 10;
const hashedPassword = await bcrypt.hash("password", saltRounds);
const newUser = new User();
newUser.firstName = givenName;
newUser.lastName = family_name ?? familyName ?? "undefined";
newUser.email = email;
newUser.userType = 'Buyer';
newUser.photoUrl = picture;
newUser.gender = "Not specified";
newUser.phoneNumber = "Not specified";
newUser.password = hashedPassword;
newUser.verified = email_verified;

await userRepository.save(newUser);
return await cb(null, newUser);
} catch (error) {
console.error(error);
return await cb(error, null);
}
}
return await cb(null, profile, { message: 'Missing required profile information' });
}
)
);

passport.serializeUser((user: any, cb) => {
cb(null, user.id);
});

passport.deserializeUser(async (id: any, cb) => {
const userRepository = getRepository(User);
try {
const user = await userRepository.findOneBy({id});
cb(null, user);
} catch (error) {
cb(error);
}
});

0 comments on commit 6aa2cd7

Please sign in to comment.