@@ -9,6 +9,120 @@ const {
9
9
USER_DOES_NOT_EXIST_ERROR ,
10
10
} = require ( "../constants/errorMessages" ) ;
11
11
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
+
12
126
/**
13
127
* Makes authentication call to GitHub statergy
14
128
*
@@ -41,33 +155,7 @@ const githubAuthLogin = (req, res, next) => {
41
155
*/
42
156
const githubAuthCallback = ( req , res , next ) => {
43
157
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 ) ;
71
159
try {
72
160
return passport . authenticate ( "github" , { session : false } , async ( err , accessToken , user ) => {
73
161
if ( err ) {
@@ -77,23 +165,33 @@ const githubAuthCallback = (req, res, next) => {
77
165
userData = {
78
166
github_id : user . username ,
79
167
github_display_name : user . displayName ,
168
+ email : user . _json . email ,
80
169
github_created_at : Number ( new Date ( user . _json . created_at ) . getTime ( ) ) ,
81
170
github_user_id : user . id ,
82
171
created_at : Date . now ( ) ,
83
172
updated_at : null ,
84
173
} ;
85
174
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
+
86
190
const { userId, incompleteUserDetails, role } = await users . addOrUpdate ( userData ) ;
87
191
88
192
const token = authService . generateAuthToken ( { userId } ) ;
89
193
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 ( ) ;
97
195
// respond with a cookie
98
196
res . cookie ( config . get ( "userToken.cookieName" ) , token , cookieOptions ) ;
99
197
@@ -232,6 +330,8 @@ const fetchDeviceDetails = async (req, res) => {
232
330
module . exports = {
233
331
githubAuthLogin,
234
332
githubAuthCallback,
333
+ googleAuthLogin,
334
+ googleAuthCallback,
235
335
signout,
236
336
storeUserDeviceInfo,
237
337
updateAuthStatus,
0 commit comments