Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ft google auth #187419170 #34

Merged
merged 3 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions __test__/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,17 @@ describe("Testing user Routes", () => {
let token: any;
describe("Testing user authentication", () => {
test("should return 201 and create a new user when registering successfully", async () => {
const response = await request(app).post("/api/v1/users/register").send(userData);
const response = await request(app)
.post("/api/v1/users/register")
.send(userData);
expect(response.status).toBe(201);
}, 20000);

test("should return 409 when registering with an existing email", async () => {
User.create(userData);
const response = await request(app).post("/api/v1/users/register").send(userData);
const response = await request(app)
.post("/api/v1/users/register")
.send(userData);
expect(response.status).toBe(409);
}, 20000);

Expand All @@ -65,7 +69,9 @@ describe("Testing user Routes", () => {
name: "",
username: "existinguser",
};
const response = await request(app).post("/api/v1/users/register").send(userData);
const response = await request(app)
.post("/api/v1/users/register")
.send(userData);

expect(response.status).toBe(400);
}, 20000);
Expand Down Expand Up @@ -172,3 +178,25 @@ describe("Testing user Routes", () => {
expect(response.status).toBe(400);
});
});

describe("Testing user authentication", () => {
test("should return 200 when password is updated", async () => {
const response = await request(app)
.get("/login")
expect(response.status).toBe(200)
expect(response.text).toBe('<a href="/api/v1/users/auth/google"> Click to Login </a>')
});
test("should return a redirect to Google OAuth when accessing /auth/google", async () => {
const response = await request(app).get("/api/v1/users/auth/google");
expect(response.status).toBe(302);
expect(response.headers.location).toContain("https://accounts.google.com/o/oauth2");
});

test("should handle Google OAuth callback and redirect user appropriately", async () => {
const callbackFnMock = jest.fn();

const response = await request(app).get("/api/v1/users/auth/google/callback");
expect(response.status).toBe(302);
});

})
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@
"@types/cors": "^2.8.17",
"@types/dotenv": "^8.2.0",
"@types/express": "^4.17.21",
"@types/express-session": "^1.18.0",
"@types/jest": "^29.5.12",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.12.7",
"@types/nodemailer": "^6.4.14",
"@types/passport": "^1.0.16",
"@types/passport-google-oauth20": "^2.0.14",
"@types/supertest": "^6.0.2",
"@types/swagger-ui-express": "^4.1.6",
"@typescript-eslint/eslint-plugin": "^7.7.1",
Expand Down Expand Up @@ -77,11 +80,14 @@
"dotenv": "^16.4.5",
"email-validator": "^2.0.4",
"express": "^4.19.2",
"express-session": "^1.18.0",
"husky": "^9.0.11",
"joi": "^17.13.0",
"jsonwebtoken": "^9.0.2",
"nodemailer": "^6.9.13",
"lint-staged": "^15.2.2",
"nodemailer": "^6.9.13",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0",
"path": "^0.12.7",
"pg": "^8.11.5",
"pg-hstore": "^2.3.4",
Expand Down
32 changes: 32 additions & 0 deletions src/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import passport from "passport";
import { Strategy as GoogleStrategy, Profile } from "passport-google-oauth20";
import { env } from "../utils/env";


passport.use(
new GoogleStrategy(
{
clientID: env.clientId,
clientSecret: env.clientSecret,
callbackURL: env.callbackURL,
},
async (
accessToken: string,
refreshToken: string,
profile: Profile,
done
) => {
console.log(profile);
return done(null, profile);
}
)
);

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

passport.deserializeUser((user, done) => {
//@ts-ignore
done(null, user);
});
54 changes: 51 additions & 3 deletions src/controllers/userControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { generateToken } from "../utils/jsonwebtoken";
import * as mailService from "../services/mail.service";
import { IUser, STATUS, SUBJECTS } from "../types";
import { comparePasswords } from "../utils/comparePassword";
import { loggedInUser } from "../services/user.service";
import { createUserService, getUserByEmail, updateUserPassword } from "../services/user.service";
import { createUserService, getUserByEmail, updateUserPassword,loggedInUser } from "../services/user.service";
import { hashedPassword } from "../utils/hashPassword";
import Token, { TokenAttributes } from "../sequelize/models/Token";
import User from "../sequelize/models/users";
Expand Down Expand Up @@ -99,6 +98,7 @@ export const updatePassword = async (req: Request, res: Response) => {
try {
// @ts-ignore
const { user } = req;
// @ts-ignore
const isPasswordValid = await comparePasswords(oldPassword, user.password);
if (!isPasswordValid) {
return res.status(400).json({ message: "Old password is incorrect" });
Expand All @@ -107,12 +107,13 @@ export const updatePassword = async (req: Request, res: Response) => {
if (newPassword !== confirmPassword) {
return res.status(400).json({ message: "New password and confirm password do not match" });
}

// @ts-ignore
if (await comparePasswords(newPassword, user.password)) {
return res.status(400).json({ message: "New password is similar to the old one. Please use a new password" });
}

const password = await hashedPassword(newPassword);
// @ts-ignore
const update = await updateUserPassword(user, password);
if(update){
return res.status(200).json({ message: "Password updated successfully" });
Expand Down Expand Up @@ -162,3 +163,50 @@ export const tokenVerification = async (req: any, res: Response) => {
});
}
};
export const handleSuccess = async (req: Request, res: Response) => {
// @ts-ignore
const user: UserProfile = req.user;

try {
let token;
let foundUser: any = await User.findOne({
where: { email: user.emails[0].value }
});

if (!foundUser) {
const newUser:IUser = await User.create({
name: user.displayName,
email: user.emails[0].value,
username: user.name.familyName,
// @ts-ignore
password: null,
});
token = await generateToken(newUser);
foundUser = newUser;
} else {
token = await generateToken(foundUser);
}

return res.status(200).json({
token: token,
message: 'success'
});
} catch (error: any) {
return res.status(500).json({
message: error.message,
    });
  }
};


export const handleFailure = async (req: Request, res: Response) => {
try {
res.status(401).json({
message: "unauthorized",
});
} catch (error: any) {
res.status(500).json({
message: error.message,
});
}
};
3 changes: 3 additions & 0 deletions src/routes/homeRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@ homeRoute.get("/", (req: Request, res: Response) => {
});
}
});
homeRoute.get("/login", (req: Request, res: Response) => {
res.send('<a href="/api/v1/users/auth/google"> Click to Login </a>')
});

export default homeRoute;
13 changes: 11 additions & 2 deletions src/routes/userRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import { Router } from "express";
import { fetchAllUsers, createUserController, userLogin, updatePassword, tokenVerification } from "../controllers/userControllers";
import { fetchAllUsers, createUserController, userLogin, updatePassword, tokenVerification, handleSuccess, handleFailure } from "../controllers/userControllers";
import { emailValidation, validateSchema } from "../middleware/validator";
import signUpSchema from "../schemas/signUpSchema";
import { isLoggedIn } from "../middlewares/isLoggedIn";
import { passwordUpdateSchema } from "../schemas/passwordUpdate";
import { isTokenFound } from "../middlewares/isTokenFound";
import { authenticateUser, callbackFn } from "../services/user.service";
require("../auth/auth");

const userRoutes = Router();

userRoutes.get("/", fetchAllUsers);
userRoutes.post("/login", userLogin);
userRoutes.put("/passwordupdate", isLoggedIn, validateSchema(passwordUpdateSchema), updatePassword)
userRoutes.post('/login',userLogin);
userRoutes.post("/register", emailValidation, validateSchema(signUpSchema), createUserController);
userRoutes.put("/passwordupdate", isLoggedIn, validateSchema(passwordUpdateSchema), updatePassword);
userRoutes.post("/2fa-verify", isTokenFound, tokenVerification);

userRoutes.get("/auth/google", authenticateUser);
userRoutes.get("/auth/google/callback", callbackFn);
userRoutes.get("/auth/google/success", handleSuccess);
userRoutes.get("/auth/google/failure", handleFailure);


export default userRoutes;
2 changes: 1 addition & 1 deletion src/sequelize/migrations/20240416115110-create-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module.exports = {
},
password: {
type: Sequelize.STRING,
allowNull: false,
allowNull: true,
},
createdAt: {
allowNull: false,
Expand Down
4 changes: 2 additions & 2 deletions src/sequelize/models/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface UserAttributes {
username: string;
email: string;
role?: string[];
password: string;
password: string | undefined;
createdAt?: Date;
updatedAt?: Date;
}
Expand Down Expand Up @@ -48,7 +48,7 @@ User.init(
defaultValue: ["buyer"],
},
password: {
allowNull: false,
allowNull: true,
type: DataTypes.STRING,
},
createdAt: {
Expand Down
10 changes: 10 additions & 0 deletions src/services/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import User from "../sequelize/models/users";
import { hashedPassword } from "../utils/hashPassword";
import passport from "passport";
import { Op } from "sequelize";

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

export const callbackFn = passport.authenticate("google", {
successRedirect: "/api/v1/users/auth/google/success",
failureRedirect: "/api/v1/users/auth/google/failure",
});

export const getAllUsers = async () => {
try {
const users = await User.findAll();
Expand Down
5 changes: 4 additions & 1 deletion src/utils/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ export const env = {
smtp_port: process.env.SMTP_PORT,
smtp_user: process.env.SMTP_USER as string,
smtp_password: process.env.SMTP_PASS as string,
};
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
callbackURL: process.env.GOOGLE_CALLBACK_URL as string,
};
16 changes: 16 additions & 0 deletions src/utils/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import cors from "cors";
import appROutes from "../routes";
import homeRoute from "../routes/homeRoutes";
import docRouter from "../docs/swagger";
import passport from "passport";
import session from "express-session";

const app = express();

Expand All @@ -11,6 +13,20 @@ app.use(express.urlencoded({ extended: true }));

app.use(cors());

app.use(
session({
secret: "eagles.team1",
resave: false,
saveUninitialized: false,
cookie: {
secure: false,
},
})
);

app.use(passport.initialize());
app.use(passport.session());

app.use("/", homeRoute);
app.use("/api/v1", appROutes);
app.use("/docs", docRouter);
Expand Down
Loading