Skip to content

Commit

Permalink
Merge branch 'ft-login-via-google-187584916' of https://github.com/at…
Browse files Browse the repository at this point in the history
…lp-rwanda/e-commerce-ninjas-bn into ft-login-via-google-187584916
  • Loading branch information
Jadowacu1 committed May 30, 2024
1 parent 64cf94d commit 69aef3a
Show file tree
Hide file tree
Showing 10 changed files with 2,232 additions and 1,824 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ env:
SMTP_HOST: ${{ secrets.SMTP_HOST }}
MAIL_ID: ${{ secrets.MAIL_ID }}
DB_HOST_TYPE: ${{ secrets.DB_HOST_TYPE }}

GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
Session_secret: ${{ secrets.Session_secret }}
jobs:
build:
runs-on: ubuntu-latest
Expand Down
3,800 changes: 1,991 additions & 1,809 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/mocha": "^10.0.6",
"@types/multer": "^1.4.11",
"@types/passport-google-oauth2": "^0.1.8",
"@types/sinon": "^17.0.3",
"bcrypt": "^5.1.1",
"chai": "^4.4.1",
Expand All @@ -67,6 +68,7 @@
"cross-env": "^7.0.3",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-session": "^1.18.0",
"http-status": "^1.7.4",
"husky": "^9.0.11",
"joi": "^17.13.1",
Expand All @@ -76,7 +78,9 @@
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.13",
"nodemon": "^3.1.0",
"nyc": "^15.1.0",
"passport": "^0.7.0",
"passport-google-oauth2": "^0.2.0",
"passport-google-oauth20": "^2.0.0",
"pg": "^8.11.5",
"pg-hstore": "^2.3.4",
"sequelize": "^6.37.3",
Expand All @@ -96,7 +100,7 @@
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6",
"@types/morgan": "^1.9.9",
"@types/node": "^20.12.12",
"@types/node": "^20.12.13",
"@types/nodemailer": "^6.4.15",
"@types/pg": "^8.11.6",
"@types/sequelize": "^4.28.20",
Expand All @@ -106,6 +110,7 @@
"@typescript-eslint/parser": "^7.10.0",
"eslint": "^8.57.0",
"eslint-plugin-import": "^2.29.1",
"lint-staged": "^15.2.2"
"lint-staged": "^15.2.2",
"nyc": "^15.1.0"
}
}
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ app.listen(PORT, () => {
console.log(`Server is running on the port ${PORT}`);
});

export default app;
export default app;
28 changes: 26 additions & 2 deletions src/modules/auth/controller/authControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import userRepositories from "../repository/authRepositories";
import { generateToken } from "../../../helpers";
import httpStatus from "http-status";
import { UsersAttributes } from "../../../databases/models/users";
import { IRequest } from "../../../types";
import { IRequest,userInfo } from "../../../types";

import authRepositories from "../repository/authRepositories";
import { sendVerificationEmail } from "../../../services/sendEmail";
Expand Down Expand Up @@ -57,4 +57,28 @@ const loginUser = async (req: Request, res: Response) => {
}


export default { registerUser, sendVerifyEmail, verifyEmail, loginUser }

const signInUser = async(req:Request,res:Response) => {
try{
const {email,firstName,lastName,picture,accToken} = req.user as userInfo;
const register:UsersAttributes = await authRepositories.createUser({
email:email,
firstName:firstName,
lastName:lastName,
password: accToken,
profilePicture:picture,
isGoogleAccount:true,
isVerified:true
});

const token = generateToken(register.id)
const session = {userId: register.id, device: req.headers["user-device"] , token: token, otp: null };
await authRepositories.createSession(session);

return res.status(200).json({status:200,token:token});
}catch(err){
return res.status(500).json({status:500,Message:err.message})
}
}

export default { registerUser, sendVerifyEmail, verifyEmail, loginUser,signInUser }
64 changes: 60 additions & 4 deletions src/modules/auth/test/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
/* eslint-disable no-shadow */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Request, Response } from "express";
import chai, { expect } from "chai";
import chaiHttp from "chai-http";
import sinon from "sinon";
import httpStatus from "http-status";
import app from "../../..";
import { isUserExist } from "../../../middlewares/validation";
import authRepositories from "../repository/authRepositories";
import Users from "../../../databases/models/users";
import Session from "../../../databases/models/session";
import { sendVerificationEmail, transporter } from "../../../services/sendEmail";

import passport, { PassportStatic } from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth2";
import session from "express-session";
import dotenv from "dotenv";
import googleAuth from "../../../services/googleAuth";
import { generateToken } from "../../../helpers";
import app from "../../../index";

chai.use(chaiHttp);
const router = () => chai.request(app);
Expand All @@ -20,11 +27,12 @@ let verifyToken: string | null = null;

describe("Authentication Test Cases", () => {

afterEach(async () => {
afterEach(async (done) => {
const tokenRecord = await Session.findOne({ where: { userId } });
if (tokenRecord) {
verifyToken = tokenRecord.dataValues.token;
}
done();
});

it("should register a new user", (done) => {
Expand Down Expand Up @@ -202,7 +210,7 @@ describe("isUserExist Middleware", () => {
expect(res).to.have.status(httpStatus.BAD_REQUEST);
expect(res.body).to.be.an("object");
expect(res.body).to.have.property("status", httpStatus.BAD_REQUEST);
expect(res.body).to.have.property("message", "Account already exists. Please verify your account");
expect(res.body).to.have.property("message", "Account already exists.");
done(err);
});
});
Expand Down Expand Up @@ -392,4 +400,52 @@ describe("sendVerificationEmail", () => {
}
});

});


describe("Google Authentication", () => {
describe("GET /api/auth/auth/google", () => {
it("should redirect to Google login page", async () => {

sinon.stub(passport, "authenticate").returns((req: Request, res: Response) => {

res.redirect("https://accounts.google.com/o/oauth2/v2/auth");
});


const res = await chai.request(app).get("/api/auth/auth/google");

expect(res).to.redirect;
expect(res).to.redirectTo("https://accounts.google.com/o/oauth2/v2/auth");


(passport.authenticate as sinon.SinonStub).restore();
});
});
});









describe("Serialize and Deserialize User", () => {
it("should serialize user", () => {
const user = { id: "user-id", email: "[email protected]" };
passport.serializeUser((user, done) => {
expect(user).to.deep.equal(user);
done(null, user);
});
});

it("should deserialize user", () => {
const user = { id: "user-id", email: "[email protected]" };
passport.deserializeUser((user, done) => {
expect(user).to.deep.equal(user);
done(null, user);
});
});
});
12 changes: 11 additions & 1 deletion src/routes/authRouter.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Router } from "express";
import googleAuth from "../services/googleAuth";
import authControllers from "../modules/auth/controller/authControllers";
import { validation, isUserExist, isAccountVerified, verifyUserCredentials } from "../middlewares/validation";
import { emailSchema, credentialSchema } from "../modules/auth/validation/authValidations";
import passport from "passport";
const router: Router = Router();

router.use(passport.initialize());
router.use(googleAuth.SESSION)



const router: Router = Router();

router.post("/register", validation(credentialSchema), isUserExist, authControllers.registerUser);
router.get("/verify-email/:token", isAccountVerified, authControllers.verifyEmail);
router.post("/send-verify-email", validation(emailSchema), isAccountVerified, authControllers.sendVerifyEmail);
router.post("/login", validation(credentialSchema), verifyUserCredentials, authControllers.loginUser);
router.get("/auth/google", googleAuth.googleVerify);
router.get("/auth/google/callback", googleAuth.googlecallback, googleAuth.authenticated,authControllers.signInUser);


export default router;
107 changes: 107 additions & 0 deletions src/services/googleAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/* eslint-disable @typescript-eslint/no-namespace */
/* eslint-disable no-shadow */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Request, Response, NextFunction } from "express";
import passport from "passport";
import session from "express-session";
import { Strategy as GoogleStrategy, VerifyCallback } from "passport-google-oauth2";
import dotenv from "dotenv";
import authRepositories from "../modules/auth/repository/authRepositories";
import { generateToken } from "../helpers";
import { userInfo } from "../types";

dotenv.config();

declare global {
namespace Express {
interface Request {
user?: any;
}
}
}
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID as string;
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET as string;

passport.use(
new GoogleStrategy(
{
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
// eslint-disable-next-line quotes

callbackURL: `${process.env.SERVER_URL_PRO}/api/auth/auth/google/callback`,

passReqToCallback: true
},
function (
request: Request,
accessToken: string,
refreshToken: string,
profile: any,
done: VerifyCallback
) {
const userId = profile.id;
const email = profile.emails?.[0].value || null;
const firstName = profile.name?.givenName || null;
const lastName = profile.name?.familyName || null;
const picture = profile.photos?.[0].value || null;
const accToken = accessToken;
const user = {
userId,
email,
firstName,
lastName,
picture,
accToken
};
return done(null, user);
}
)
);

passport.serializeUser((user, done) => {
done(null, user);
});

passport.deserializeUser((user, done) => {
done(null, user);
});

const googleVerify = passport.authenticate("google", {
scope: ["profile", "email"]
});

const googlecallback = passport.authenticate("google", {
failureRedirect: "/"
});

const SESSION = session({
secret: process.env.Session_secret,
resave: false,
saveUninitialized: true
});

const authenticated = async (req: Request,res: Response,next: NextFunction) => {
if ((req as any).isAuthenticated()) {
const { email } = req.user as userInfo;
const register = await authRepositories.findUserByAttributes("email", email);
if (register) {
const token = generateToken(register.id);
const sessions = { userId: register.id, device: req.headers["user-device"], token: token, otp: null };
await authRepositories.createSession(sessions);
return res.status(200).json({ status: 200, token: token });
} else {
next();
}
} else {
return res.json({ Error: "Something Went Wrong" });
}
};

export default {
passport,
googleVerify,
googlecallback,
SESSION,
authenticated
};
10 changes: 9 additions & 1 deletion src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,12 @@ export interface ILogin{

export interface IRequest extends Request{
loginUserId?: number;
}
}

export interface userInfo{
email:string,
firstName: string,
lastName: string,
picture: string,
accToken: string
}
16 changes: 15 additions & 1 deletion swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,20 @@
}
}
},
"/api/auth/auth/google": {
"get": {
"tags": [
"Authentication Routes"
],
"summary": "Login User via Google",
"description": "Redirects the user to the Google login page for authentication.",
"responses": {
"302": {
"description": "Redirects to the Google login page."
}
}
}
},
"/api/users/admin-update-user-status/{id}": {
"put": {
"tags": [
Expand Down Expand Up @@ -507,4 +521,4 @@
}
}
}
}
}

0 comments on commit 69aef3a

Please sign in to comment.