Skip to content

Commit 4090f3c

Browse files
committed
added google auth and its tests
1 parent 4d9c711 commit 4090f3c

File tree

14 files changed

+418
-10
lines changed

14 files changed

+418
-10
lines changed

config/custom-environment-variables.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ module.exports = {
3737
clientSecret: "GITHUB_CLIENT_SECRET",
3838
},
3939

40+
googleOauth: {
41+
clientId: "GOOGLE_CLIENT_ID",
42+
clientSecret: "GOOGLE_CLIENT_SECRET",
43+
},
44+
4045
githubAccessToken: "GITHUB_PERSONAL_ACCESS_TOKEN",
4146

4247
firestore: "FIRESTORE_CONFIG",

config/default.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ module.exports = {
3232
clientSecret: "<clientSecret>",
3333
},
3434

35+
googleOauth: {
36+
clientId: "<clientId>",
37+
clientSecret: "<clientSecret>",
38+
},
39+
3540
emailServiceConfig: {
3641
email: "<RDS_EMAIL>",
3742
password: "<EMAIL PASSWORD GENERATED AFTER 2FA>",

controllers/auth.js

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,83 @@ const {
99
USER_DOES_NOT_EXIST_ERROR,
1010
} = require("../constants/errorMessages");
1111

12+
const googleAuthLogin = (req, res, next) => {
13+
const { redirectURL } = req.query;
14+
return passport.authenticate("google", {
15+
scope: ["email"],
16+
state: redirectURL,
17+
})(req, res, next);
18+
};
19+
20+
const googleAuthCallback = (req, res, next) => {
21+
const rdsUiUrl = new URL(config.get("services.rdsUi.baseUrl"));
22+
let authRedirectionUrl = rdsUiUrl;
23+
24+
if ("state" in req.query) {
25+
try {
26+
const redirectUrl = new URL(req.query.state);
27+
28+
if (`.${redirectUrl.hostname}`.endsWith(`.${rdsUiUrl.hostname}`)) {
29+
// Matching *.realdevsquad.com
30+
authRedirectionUrl = redirectUrl;
31+
// console.log("redirect url is", authRedirectionUrl);
32+
// devMode = Boolean(redirectUrl.searchParams.get("dev"));
33+
} else {
34+
logger.error(`Malicious redirect URL provided URL: ${redirectUrl}, Will redirect to RDS`);
35+
}
36+
} catch (error) {
37+
logger.error("Invalid redirect URL provided", error);
38+
}
39+
}
40+
try {
41+
return passport.authenticate("google", { session: false }, async (err, accessToken, user) => {
42+
if (err) {
43+
logger.error(err);
44+
return res.boom.unauthorized("User cannot be authenticated");
45+
}
46+
// console.log("user", user);
47+
const userData = {
48+
email: user.emails[0].value,
49+
created_at: Date.now(),
50+
updated_at: null,
51+
};
52+
// console.log("userData", userData);
53+
54+
const userDataFromDB = await users.fetchUser({ email: userData.email });
55+
// console.log("userDataFromDB", userDataFromDB);
56+
57+
if (userDataFromDB.userExists) {
58+
if (userDataFromDB.user.roles.developer === true) {
59+
// console.log("hi");
60+
return res.boom.unauthorized("User is not allowed to login via Google");
61+
}
62+
}
63+
64+
const { userId, incompleteUserDetails } = await users.addOrUpdate(userData);
65+
// console.log(role);
66+
67+
const token = authService.generateAuthToken({ userId });
68+
69+
const cookieOptions = {
70+
domain: rdsUiUrl.hostname,
71+
expires: new Date(Date.now() + config.get("userToken.ttl") * 1000),
72+
httpOnly: true,
73+
secure: true,
74+
sameSite: "lax",
75+
};
76+
77+
res.cookie(config.get("userToken.cookieName"), token, cookieOptions);
78+
79+
if (incompleteUserDetails) authRedirectionUrl = "https://my.realdevsquad.com/new-signup";
80+
81+
return res.redirect(authRedirectionUrl);
82+
})(req, res, next);
83+
} catch (err) {
84+
logger.error(err);
85+
return res.boom.unauthorized("User cannot be authenticated");
86+
}
87+
};
88+
1289
/**
1390
* Makes authentication call to GitHub statergy
1491
*
@@ -56,7 +133,6 @@ const githubAuthCallback = (req, res, next) => {
56133
}
57134

58135
if (redirectUrl.searchParams.get("v2") === "true") isV2FlagPresent = true;
59-
60136
if (`.${redirectUrl.hostname}`.endsWith(`.${rdsUiUrl.hostname}`)) {
61137
// Matching *.realdevsquad.com
62138
authRedirectionUrl = redirectUrl;
@@ -74,14 +150,38 @@ const githubAuthCallback = (req, res, next) => {
74150
logger.error(err);
75151
return res.boom.unauthorized("User cannot be authenticated");
76152
}
153+
// console.log(accessToken);
154+
// console.log("user", user);
77155
userData = {
78156
github_id: user.username,
79157
github_display_name: user.displayName,
158+
email: user._json.email,
80159
github_created_at: Number(new Date(user._json.created_at).getTime()),
81160
github_user_id: user.id,
82161
created_at: Date.now(),
83162
updated_at: null,
84163
};
164+
// console.log(userData);
165+
166+
if (userData.email === null) {
167+
const res = await fetch("https://api.github.com/user/emails", {
168+
headers: {
169+
Authorization: `token ${accessToken}`,
170+
},
171+
});
172+
const emails = await res.json();
173+
// console.log("emails", emails);
174+
const primaryEmails = emails.filter((item) => item.primary);
175+
// console.log("primaryEmails", primaryEmails);
176+
177+
// Get the first primary email, if it exists
178+
if (primaryEmails.length > 0) {
179+
userData.email = primaryEmails[0].email;
180+
} else {
181+
userData.email = null;
182+
// console.log("userData.email", userData.email);
183+
}
184+
}
85185

86186
const { userId, incompleteUserDetails, role } = await users.addOrUpdate(userData);
87187

@@ -232,6 +332,8 @@ const fetchDeviceDetails = async (req, res) => {
232332
module.exports = {
233333
githubAuthLogin,
234334
githubAuthCallback,
335+
googleAuthLogin,
336+
googleAuthCallback,
235337
signout,
236338
storeUserDeviceInfo,
237339
updateAuthStatus,

middlewares/passport.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const passport = require("passport");
22
const GitHubStrategy = require("passport-github2").Strategy;
3+
const GoogleStrategy = require("passport-google-oauth20").Strategy;
34

45
try {
56
passport.use(
@@ -14,6 +15,18 @@ try {
1415
}
1516
)
1617
);
18+
passport.use(
19+
new GoogleStrategy(
20+
{
21+
clientID: config.get("googleOauth.clientId"),
22+
clientSecret: config.get("googleOauth.clientSecret"),
23+
callbackURL: `${config.get("services.rdsApi.baseUrl")}/auth/google/callback`,
24+
},
25+
(accessToken, refreshToken, profile, done) => {
26+
return done(null, accessToken, profile);
27+
}
28+
)
29+
);
1730
} catch (err) {
1831
logger.error("Error initialising passport:", err);
1932
}

models/users.js

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const { addLog } = require("../services/logService");
3737
const addOrUpdate = async (userData, userId = null) => {
3838
try {
3939
// userId exists Update user
40+
// console.log("userData entry", userData, userId);
41+
// console.log("0");
4042
if (userId !== null) {
4143
const user = await userModel.doc(userId).get();
4244
const isNewUser = !user.data();
@@ -63,13 +65,40 @@ const addOrUpdate = async (userData, userId = null) => {
6365
}
6466

6567
// userId is null, Add or Update user
66-
let user;
68+
let user = null;
69+
6770
if (userData.github_user_id) {
6871
user = await userModel.where("github_user_id", "==", userData.github_user_id).limit(1).get();
72+
// console.log("github_user_id query result:", user.size); // Log the result size
6973
}
70-
if (!user || (user && user.empty)) {
74+
75+
if (userData.github_id && (!user || (user && user.empty))) {
7176
user = await userModel.where("github_id", "==", userData.github_id).limit(1).get();
77+
// console.log("github_id query result:", user.size); // Log the result size
7278
}
79+
80+
if ((!user || (user && user.empty)) && userData.email) {
81+
// console.log("Checking email of the users, please check email for the user");
82+
user = await userModel.where("email", "==", userData.email).limit(1).get();
83+
// console.log("email query result:", user.size, user.empty); // Log the result size
84+
}
85+
86+
// if (userData.github_user_id) {
87+
// user = await userModel.where("github_user_id", "==", userData.github_user_id).limit(1).get();
88+
// }
89+
// if (!user && userData.github_id) {
90+
// user = await userModel.where("github_id", "==", userData.github_id).limit(1).get();
91+
// }
92+
93+
// if (!user && userData.email) {
94+
// console.log("checkingemail of the users please check email for the user");
95+
// user = await userModel.where("email", "==", userData.email).limit(1).get();
96+
// }
97+
98+
// if (user && user.empty) {
99+
// user = null;
100+
// }
101+
// console.log("2");
73102
if (user && !user.empty && user.docs !== null) {
74103
await userModel.doc(user.docs[0].id).set({ ...userData, updated_at: Date.now() }, { merge: true });
75104

@@ -87,9 +116,10 @@ const addOrUpdate = async (userData, userId = null) => {
87116
incompleteUserDetails: user.docs[0].data().incompleteUserDetails,
88117
updated_at: Date.now(),
89118
role: Object.values(AUTHORITIES).find((role) => data.roles[role]) || AUTHORITIES.USER,
119+
roles: data.roles,
90120
};
91121
}
92-
122+
// console.log("3");
93123
// Add new user
94124
/*
95125
Adding default archived role enables us to query for only
@@ -98,7 +128,9 @@ const addOrUpdate = async (userData, userId = null) => {
98128
*/
99129
userData.roles = { archived: false, in_discord: false };
100130
userData.incompleteUserDetails = true;
131+
// console.log("userData last mein", userData);
101132
const userInfo = await userModel.add(userData);
133+
// console.log("4");
102134
return {
103135
isNewUser: true,
104136
role: AUTHORITIES.USER,
@@ -309,7 +341,7 @@ const fetchUsers = async (usernames = []) => {
309341
* @param { Object }: Object with username and userId, any of the two can be used
310342
* @return {Promise<{userExists: boolean, user: <userModel>}|{userExists: boolean, user: <userModel>}>}
311343
*/
312-
const fetchUser = async ({ userId = null, username = null, githubUsername = null, discordId = null }) => {
344+
const fetchUser = async ({ userId = null, username = null, githubUsername = null, discordId = null, email = null }) => {
313345
try {
314346
let userData, id;
315347
if (username) {
@@ -334,6 +366,13 @@ const fetchUser = async ({ userId = null, username = null, githubUsername = null
334366
id = doc.id;
335367
userData = doc.data();
336368
});
369+
} else if (email) {
370+
const user = await userModel.where("email", "==", email).limit(1).get();
371+
// console.log("hihiijfoiafoieuiofeuofueoiurioeajoeifeoiuroie");
372+
user.forEach((doc) => {
373+
id = doc.id;
374+
userData = doc.data();
375+
});
337376
}
338377

339378
if (userData && userData.disabled_roles !== undefined) {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"nodemailer-mock": "^2.0.6",
4141
"passport": "0.7.0",
4242
"passport-github2": "0.1.12",
43+
"passport-google-oauth20": "^2.0.0",
4344
"rate-limiter-flexible": "5.0.3",
4445
"winston": "3.13.0"
4546
},

routes/auth.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ router.get("/github/login", auth.githubAuthLogin);
99

1010
router.get("/github/callback", auth.githubAuthCallback);
1111

12+
router.get("/google/login", auth.googleAuthLogin);
13+
14+
router.get("/google/callback", auth.googleAuthCallback);
15+
1216
router.get("/signout", auth.signout);
1317

1418
router.get("/qr-code-auth", userDeviceInfoValidator.validateFetchingUserDocument, auth.fetchUserDeviceInfo);

test/config/test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ module.exports = {
3030
identity_store_id: "test-identity-store-id",
3131
},
3232

33+
googleOauth: {
34+
clientId: "cliendId",
35+
clientSecret: "clientSecret",
36+
},
3337
firestore: `{
3438
"type": "service_account",
3539
"project_id": "test-project-id-for-emulator",

test/fixtures/auth/githubUserInfo.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ module.exports = () => {
4343
company: null,
4444
blog: "",
4545
location: null,
46-
email: null,
46+
4747
hireable: null,
4848
bio: null,
4949
twitter_username: null,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* User info for Google auth response
3+
* Multiple responses can be added to the array if required
4+
*
5+
* @return {Object}
6+
*/
7+
module.exports = () => {
8+
return [
9+
{
10+
id: "1234567890",
11+
displayName: "Google User",
12+
emails: [{ value: "[email protected]", verified: true }],
13+
photos: [
14+
{
15+
value: "https://lh3.googleusercontent.com/a-/test",
16+
},
17+
],
18+
provider: "google",
19+
_raw: `{
20+
'"sub": "1234567890",\n' +
21+
'"picture": "https://lh3.googleusercontent.com/a-/test",\n' +
22+
'"email": "[email protected]",\n' +
23+
'"email_verified": true\n' +
24+
}`,
25+
_json: {
26+
sub: "1234567890",
27+
picture: "https://lh3.googleusercontent.com/a-/test",
28+
29+
email_verified: true,
30+
},
31+
},
32+
{
33+
34+
roles: {
35+
in_discord: true,
36+
archived: false,
37+
},
38+
incompleteUserDetails: false,
39+
updated_at: Date.now(),
40+
created_at: Date.now(),
41+
},
42+
];
43+
};

0 commit comments

Comments
 (0)