@@ -9,6 +9,120 @@ 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+ function handleRedirectUrl ( req ) {
21+ const rdsUiUrl = new URL ( config . get ( "services.rdsUi.baseUrl" ) ) ;
22+ let authRedirectionUrl = rdsUiUrl ;
23+ let isMobileApp = false ;
24+ let isV2FlagPresent = false ;
25+ let devMode = false ;
26+
27+ if ( "state" in req . query ) {
28+ try {
29+ const redirectUrl = new URL ( req . query . state ) ;
30+ if ( redirectUrl . searchParams . get ( "isMobileApp" ) === "true" ) {
31+ isMobileApp = true ;
32+ redirectUrl . searchParams . delete ( "isMobileApp" ) ;
33+ }
34+
35+ if ( `.${ redirectUrl . hostname } ` . endsWith ( `.${ rdsUiUrl . hostname } ` ) ) {
36+ // Matching *.realdevsquad.com
37+ authRedirectionUrl = redirectUrl ;
38+ devMode = Boolean ( redirectUrl . searchParams . get ( "dev" ) ) ;
39+ } else {
40+ logger . error ( `Malicious redirect URL provided URL: ${ redirectUrl } , Will redirect to RDS` ) ;
41+ }
42+ if ( redirectUrl . searchParams . get ( "v2" ) === "true" ) {
43+ isV2FlagPresent = true ;
44+ }
45+ } catch ( error ) {
46+ logger . error ( "Invalid redirect URL provided" , error ) ;
47+ }
48+ }
49+ return {
50+ authRedirectionUrl,
51+ isMobileApp,
52+ isV2FlagPresent,
53+ devMode,
54+ } ;
55+ }
56+
57+ const getAuthCookieOptions = ( ) => {
58+ const rdsUiUrl = new URL ( config . get ( "services.rdsUi.baseUrl" ) ) ;
59+ return {
60+ domain : rdsUiUrl . hostname ,
61+ expires : new Date ( Date . now ( ) + config . get ( "userToken.ttl" ) * 1000 ) ,
62+ httpOnly : true ,
63+ secure : true ,
64+ sameSite : "lax" ,
65+ } ;
66+ } ;
67+
68+ async function handleGoogleLogin ( req , res , user , authRedirectionUrl ) {
69+ try {
70+ if ( ! user . emails || user . emails . length === 0 ) {
71+ logger . error ( "Google login failed: No emails found in user data" ) ;
72+ return res . boom . unauthorized ( "No email found in Google account" ) ;
73+ }
74+ const primaryEmail = user . emails . find ( ( email ) => email . verified === true ) ;
75+ if ( ! primaryEmail ) {
76+ logger . error ( "Google login failed: No verified email found" ) ;
77+ return res . boom . unauthorized ( "No verified email found in Google account" ) ;
78+ }
79+
80+ const userData = {
81+ email : primaryEmail . value ,
82+ created_at : Date . now ( ) ,
83+ updated_at : null ,
84+ } ;
85+
86+ const userDataFromDB = await users . fetchUser ( { email : userData . email } ) ;
87+
88+ if ( userDataFromDB . userExists ) {
89+ if ( userDataFromDB . user . roles ?. developer ) {
90+ const errorMessage = encodeURIComponent ( "Google login is restricted for developer role." ) ;
91+ const separator = authRedirectionUrl . search ? "&" : "?" ;
92+ return res . redirect ( `${ authRedirectionUrl } ${ separator } error=${ errorMessage } ` ) ;
93+ }
94+ }
95+
96+ const { userId, incompleteUserDetails } = await users . addOrUpdate ( userData ) ;
97+
98+ const token = authService . generateAuthToken ( { userId } ) ;
99+
100+ const cookieOptions = getAuthCookieOptions ( ) ;
101+
102+ res . cookie ( config . get ( "userToken.cookieName" ) , token , cookieOptions ) ;
103+
104+ if ( incompleteUserDetails ) {
105+ authRedirectionUrl = "https://my.realdevsquad.com/new-signup" ;
106+ }
107+
108+ return res . redirect ( authRedirectionUrl ) ;
109+ } catch ( err ) {
110+ logger . error ( "Unexpected error during Google login" , err ) ;
111+ return res . boom . unauthorized ( "User cannot be authenticated" ) ;
112+ }
113+ }
114+
115+ const googleAuthCallback = ( req , res , next ) => {
116+ const { authRedirectionUrl } = handleRedirectUrl ( req ) ;
117+ return passport . authenticate ( "google" , { session : false } , async ( err , accessToken , user ) => {
118+ if ( err ) {
119+ logger . error ( err ) ;
120+ return res . boom . unauthorized ( "User cannot be authenticated" ) ;
121+ }
122+ return await handleGoogleLogin ( req , res , user , authRedirectionUrl ) ;
123+ } ) ( req , res , next ) ;
124+ } ;
125+
12126/**
13127 * Makes authentication call to GitHub statergy
14128 *
@@ -41,33 +155,7 @@ const githubAuthLogin = (req, res, next) => {
41155 */
42156const githubAuthCallback = ( req , res , next ) => {
43157 let userData ;
44- let isMobileApp = false ;
45- const rdsUiUrl = new URL ( config . get ( "services.rdsUi.baseUrl" ) ) ;
46- let authRedirectionUrl = rdsUiUrl ;
47- let devMode = false ;
48- let isV2FlagPresent = false ;
49-
50- if ( "state" in req . query ) {
51- try {
52- const redirectUrl = new URL ( req . query . state ) ;
53- if ( redirectUrl . searchParams . get ( "isMobileApp" ) === "true" ) {
54- isMobileApp = true ;
55- redirectUrl . searchParams . delete ( "isMobileApp" ) ;
56- }
57-
58- if ( redirectUrl . searchParams . get ( "v2" ) === "true" ) isV2FlagPresent = true ;
59-
60- if ( `.${ redirectUrl . hostname } ` . endsWith ( `.${ rdsUiUrl . hostname } ` ) ) {
61- // Matching *.realdevsquad.com
62- authRedirectionUrl = redirectUrl ;
63- devMode = Boolean ( redirectUrl . searchParams . get ( "dev" ) ) ;
64- } else {
65- logger . error ( `Malicious redirect URL provided URL: ${ redirectUrl } , Will redirect to RDS` ) ;
66- }
67- } catch ( error ) {
68- logger . error ( "Invalid redirect URL provided" , error ) ;
69- }
70- }
158+ let { authRedirectionUrl, isMobileApp, isV2FlagPresent, devMode } = handleRedirectUrl ( req ) ;
71159 try {
72160 return passport . authenticate ( "github" , { session : false } , async ( err , accessToken , user ) => {
73161 if ( err ) {
@@ -77,23 +165,33 @@ const githubAuthCallback = (req, res, next) => {
77165 userData = {
78166 github_id : user . username ,
79167 github_display_name : user . displayName ,
168+ email : user . _json . email ,
80169 github_created_at : Number ( new Date ( user . _json . created_at ) . getTime ( ) ) ,
81170 github_user_id : user . id ,
82171 created_at : Date . now ( ) ,
83172 updated_at : null ,
84173 } ;
85174
175+ if ( ! userData . email ) {
176+ const githubBaseUrl = config . get ( "githubApi.baseUrl" ) ;
177+ const res = await fetch ( `${ githubBaseUrl } /user/emails` , {
178+ headers : {
179+ Authorization : `token ${ accessToken } ` ,
180+ } ,
181+ } ) ;
182+ const emails = await res . json ( ) ;
183+ const primaryEmails = emails . filter ( ( item ) => item . primary ) ;
184+
185+ if ( primaryEmails . length > 0 ) {
186+ userData . email = primaryEmails [ 0 ] . email ;
187+ }
188+ }
189+
86190 const { userId, incompleteUserDetails, role } = await users . addOrUpdate ( userData ) ;
87191
88192 const token = authService . generateAuthToken ( { userId } ) ;
89193
90- const cookieOptions = {
91- domain : rdsUiUrl . hostname ,
92- expires : new Date ( Date . now ( ) + config . get ( "userToken.ttl" ) * 1000 ) ,
93- httpOnly : true ,
94- secure : true ,
95- sameSite : "lax" ,
96- } ;
194+ const cookieOptions = getAuthCookieOptions ( ) ;
97195 // respond with a cookie
98196 res . cookie ( config . get ( "userToken.cookieName" ) , token , cookieOptions ) ;
99197
@@ -232,6 +330,8 @@ const fetchDeviceDetails = async (req, res) => {
232330module . exports = {
233331 githubAuthLogin,
234332 githubAuthCallback,
333+ googleAuthLogin,
334+ googleAuthCallback,
235335 signout,
236336 storeUserDeviceInfo,
237337 updateAuthStatus,
0 commit comments