From 0e21eacd528a0f2e2b1c6d5315b4057e3f9eae63 Mon Sep 17 00:00:00 2001 From: Chamika J <75464293+chamikaJ@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:40:06 +0530 Subject: [PATCH 1/4] feat(auth): implement mobile Google authentication endpoint - Added `googleMobileAuth` method in `AuthController` to handle mobile Google sign-in. - Validates ID token and checks for email verification before proceeding. - Handles user registration and login, creating a session for authenticated users. - Updated API routes to include the new mobile authentication endpoint. --- .../src/controllers/auth-controller.ts | 62 +++++++++++++++++++ worklenz-backend/src/routes/auth/index.ts | 3 + 2 files changed, 65 insertions(+) diff --git a/worklenz-backend/src/controllers/auth-controller.ts b/worklenz-backend/src/controllers/auth-controller.ts index 4fea4f59..ed6705c3 100644 --- a/worklenz-backend/src/controllers/auth-controller.ts +++ b/worklenz-backend/src/controllers/auth-controller.ts @@ -181,4 +181,66 @@ export default class AuthController extends WorklenzControllerBase { res.status(500).send(new ServerResponse(false, null, DEFAULT_ERROR_MESSAGE)); } } + + @HandleExceptions({logWithError: "body"}) + public static async googleMobileAuth(req: IWorkLenzRequest, res: IWorkLenzResponse) { + const {idToken} = req.body; + + if (!idToken) { + return res.status(400).send(new ServerResponse(false, null, "ID token is required")); + } + + try { + const response = await axios.get(`https://oauth2.googleapis.com/tokeninfo?id_token=${idToken}`); + const profile = response.data; + + if (!profile.email_verified) { + return res.status(400).send(new ServerResponse(false, null, "Email not verified")); + } + + // Check for existing local account + const localAccountResult = await db.query("SELECT 1 FROM users WHERE email = $1 AND password IS NOT NULL AND is_deleted IS FALSE;", [profile.email]); + if (localAccountResult.rowCount) { + return res.status(400).send(new ServerResponse(false, null, `No Google account exists for email ${profile.email}.`)); + } + + // Check if user exists + const userResult = await db.query( + "SELECT id, google_id, name, email, active_team FROM users WHERE google_id = $1 OR email = $2;", + [profile.sub, profile.email] + ); + + let user; + if (userResult.rowCount) { + // Existing user - login + user = userResult.rows[0]; + } else { + // New user - register + const googleUserData = { + id: profile.sub, + displayName: profile.name, + email: profile.email, + picture: profile.picture + }; + + const registerResult = await db.query("SELECT register_google_user($1) AS user;", [JSON.stringify(googleUserData)]); + user = registerResult.rows[0].user; + } + + // Create session + req.login(user, (err) => { + if (err) { + log_error(err); + return res.status(500).send(new ServerResponse(false, null, "Authentication failed")); + } + + user.build_v = FileConstants.getRelease(); + return res.status(200).send(new AuthResponse("Login Successful!", true, user, null, "User successfully logged in")); + }); + + } catch (error) { + log_error(error); + return res.status(400).send(new ServerResponse(false, null, "Invalid ID token")); + } + } } diff --git a/worklenz-backend/src/routes/auth/index.ts b/worklenz-backend/src/routes/auth/index.ts index 1d34fb27..8d6d8057 100644 --- a/worklenz-backend/src/routes/auth/index.ts +++ b/worklenz-backend/src/routes/auth/index.ts @@ -55,6 +55,9 @@ authRouter.get("/google/verify", (req, res) => { })(req, res); }); +// Mobile Google Sign-In +authRouter.post("/google/mobile", safeControllerFunction(AuthController.googleMobileAuth)); + // Passport logout authRouter.get("/logout", AuthController.logout); From 8188b5c3815519d9d373a0e6f27faebb53aace67 Mon Sep 17 00:00:00 2001 From: Chamika J <75464293+chamikaJ@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:44:34 +0530 Subject: [PATCH 2/4] feat(auth): enhance Google authentication validation - Added validation for token audience, issuer, and expiry in the `googleMobileAuth` method of `AuthController`. - Improved error handling for invalid tokens and expired sessions, ensuring robust authentication flow. --- .../src/controllers/auth-controller.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/worklenz-backend/src/controllers/auth-controller.ts b/worklenz-backend/src/controllers/auth-controller.ts index ed6705c3..70056b47 100644 --- a/worklenz-backend/src/controllers/auth-controller.ts +++ b/worklenz-backend/src/controllers/auth-controller.ts @@ -194,6 +194,21 @@ export default class AuthController extends WorklenzControllerBase { const response = await axios.get(`https://oauth2.googleapis.com/tokeninfo?id_token=${idToken}`); const profile = response.data; + // Validate token audience (client ID) + if (profile.aud !== process.env.GOOGLE_CLIENT_ID) { + return res.status(400).send(new ServerResponse(false, null, "Invalid token audience")); + } + + // Validate token issuer + if (!['https://accounts.google.com', 'accounts.google.com'].includes(profile.iss)) { + return res.status(400).send(new ServerResponse(false, null, "Invalid token issuer")); + } + + // Check token expiry + if (Date.now() >= profile.exp * 1000) { + return res.status(400).send(new ServerResponse(false, null, "Token expired")); + } + if (!profile.email_verified) { return res.status(400).send(new ServerResponse(false, null, "Email not verified")); } @@ -210,7 +225,7 @@ export default class AuthController extends WorklenzControllerBase { [profile.sub, profile.email] ); - let user; + let user: any; if (userResult.rowCount) { // Existing user - login user = userResult.rows[0]; From 01ce34f3d8dd532566694328a077cd11c94e1f0f Mon Sep 17 00:00:00 2001 From: Chamika J <75464293+chamikaJ@users.noreply.github.com> Date: Mon, 4 Aug 2025 16:54:17 +0530 Subject: [PATCH 3/4] feat(auth): enhance token audience validation for Google authentication - Updated the `googleMobileAuth` method in `AuthController` to accept multiple client IDs (web, Android, iOS) for token audience validation. - Improved error handling for invalid token audiences, ensuring a more flexible and robust authentication process. --- worklenz-backend/src/controllers/auth-controller.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/worklenz-backend/src/controllers/auth-controller.ts b/worklenz-backend/src/controllers/auth-controller.ts index 70056b47..d1505dc2 100644 --- a/worklenz-backend/src/controllers/auth-controller.ts +++ b/worklenz-backend/src/controllers/auth-controller.ts @@ -194,13 +194,19 @@ export default class AuthController extends WorklenzControllerBase { const response = await axios.get(`https://oauth2.googleapis.com/tokeninfo?id_token=${idToken}`); const profile = response.data; - // Validate token audience (client ID) - if (profile.aud !== process.env.GOOGLE_CLIENT_ID) { + // Validate token audience (client ID) - accept web, Android, and iOS client IDs + const allowedClientIds = [ + process.env.GOOGLE_CLIENT_ID, // Web client ID + process.env.GOOGLE_ANDROID_CLIENT_ID, // Android client ID + process.env.GOOGLE_IOS_CLIENT_ID, // iOS client ID + ].filter(Boolean); // Remove undefined values + + if (!allowedClientIds.includes(profile.aud)) { return res.status(400).send(new ServerResponse(false, null, "Invalid token audience")); } // Validate token issuer - if (!['https://accounts.google.com', 'accounts.google.com'].includes(profile.iss)) { + if (!["https://accounts.google.com", "accounts.google.com"].includes(profile.iss)) { return res.status(400).send(new ServerResponse(false, null, "Invalid token issuer")); } From f84d83429562a705657103a7e5a09a8a1bd12610 Mon Sep 17 00:00:00 2001 From: Chamika J <75464293+chamikaJ@users.noreply.github.com> Date: Mon, 4 Aug 2025 17:02:48 +0530 Subject: [PATCH 4/4] feat(auth): add logging for token audience validation in Google authentication - Introduced console logs in the `googleMobileAuth` method to display the token audience, allowed client IDs, and the status of relevant environment variables. - This enhancement aids in debugging and ensures better visibility into the authentication process. --- worklenz-backend/src/controllers/auth-controller.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/worklenz-backend/src/controllers/auth-controller.ts b/worklenz-backend/src/controllers/auth-controller.ts index d1505dc2..da26936a 100644 --- a/worklenz-backend/src/controllers/auth-controller.ts +++ b/worklenz-backend/src/controllers/auth-controller.ts @@ -201,6 +201,13 @@ export default class AuthController extends WorklenzControllerBase { process.env.GOOGLE_IOS_CLIENT_ID, // iOS client ID ].filter(Boolean); // Remove undefined values + console.log("Token audience (aud):", profile.aud); + console.log("Allowed client IDs:", allowedClientIds); + console.log("Environment variables check:"); + console.log("- GOOGLE_CLIENT_ID:", process.env.GOOGLE_CLIENT_ID ? "Set" : "Not set"); + console.log("- GOOGLE_ANDROID_CLIENT_ID:", process.env.GOOGLE_ANDROID_CLIENT_ID ? "Set" : "Not set"); + console.log("- GOOGLE_IOS_CLIENT_ID:", process.env.GOOGLE_IOS_CLIENT_ID ? "Set" : "Not set"); + if (!allowedClientIds.includes(profile.aud)) { return res.status(400).send(new ServerResponse(false, null, "Invalid token audience")); }