@@ -19,6 +19,23 @@ def ensure_user_in_organizations(provider_user_id, email, name, provider, pictur
1919 Uses MERGE for atomic operations and better performance.
2020 Returns (is_new_user, user_info)
2121 """
22+ # Input validation
23+ if not provider_user_id or not email or not provider :
24+ logging .error ("Missing required parameters: provider_user_id=%s, email=%s, provider=%s" ,
25+ provider_user_id , email , provider )
26+ return False , None
27+
28+ # Validate email format (basic check)
29+ if "@" not in email or "." not in email :
30+ logging .error ("Invalid email format: %s" , email )
31+ return False , None
32+
33+ # Validate provider is in allowed list
34+ allowed_providers = ["google" , "github" ]
35+ if provider not in allowed_providers :
36+ logging .error ("Invalid provider: %s" , provider )
37+ return False , None
38+
2239 try :
2340 # Select the Organizations graph
2441 organizations_graph = db .select_graph ("Organizations" )
@@ -98,13 +115,28 @@ def ensure_user_in_organizations(provider_user_id, email, name, provider, pictur
98115 logging .error ("Failed to create/update identity and user: email=%s" , email )
99116 return False , None
100117
101- except Exception as e :
118+ except ( AttributeError , ValueError , KeyError ) as e :
102119 logging .error ("Error managing user in Organizations graph: %s" , e )
103120 return False , None
121+ except Exception as e :
122+ logging .error ("Unexpected error managing user in Organizations graph: %s" , e )
123+ return False , None
104124
105125
106126def update_identity_last_login (provider , provider_user_id ):
107127 """Update the last login timestamp for an existing identity"""
128+ # Input validation
129+ if not provider or not provider_user_id :
130+ logging .error ("Missing required parameters: provider=%s, provider_user_id=%s" ,
131+ provider , provider_user_id )
132+ return
133+
134+ # Validate provider is in allowed list
135+ allowed_providers = ["google" , "github" ]
136+ if provider not in allowed_providers :
137+ logging .error ("Invalid provider: %s" , provider )
138+ return
139+
108140 try :
109141 organizations_graph = db .select_graph ("Organizations" )
110142 update_query = """
@@ -118,9 +150,12 @@ def update_identity_last_login(provider, provider_user_id):
118150 })
119151 logging .info ("Updated last login for identity: provider=%s, provider_user_id=%s" ,
120152 provider , provider_user_id )
121- except Exception as e :
153+ except ( AttributeError , ValueError , KeyError ) as e :
122154 logging .error ("Error updating last login for identity %s/%s: %s" ,
123155 provider , provider_user_id , e )
156+ except Exception as e :
157+ logging .error ("Unexpected error updating last login for identity %s/%s: %s" ,
158+ provider , provider_user_id , e )
124159
125160
126161def validate_and_cache_user ():
@@ -129,92 +164,122 @@ def validate_and_cache_user():
129164 Returns (user_info, is_authenticated) tuple.
130165 Supports both Google and GitHub OAuth.
131166 """
132- # Check for cached user info from either provider
133- user_info = session .get ("user_info" )
134- token_validated_at = session .get ("token_validated_at" , 0 )
135- current_time = time .time ()
136-
137- # Use cached user info if it's less than 15 minutes old
138- if user_info and (current_time - token_validated_at ) < 900 : # 15 minutes
139- return user_info , True
140-
141- # Check Google OAuth first
142- if google .authorized :
143- try :
144- resp = google .get ("/oauth2/v2/userinfo" )
145- if resp .ok :
146- google_user = resp .json ()
147- # Normalize user info structure
148- user_info = {
149- "id" : google_user .get ("id" ),
150- "name" : google_user .get ("name" ),
151- "email" : google_user .get ("email" ),
152- "picture" : google_user .get ("picture" ),
153- "provider" : "google"
154- }
155- session ["user_info" ] = user_info
156- session ["token_validated_at" ] = current_time
157- return user_info , True
158- except (requests .RequestException , KeyError , ValueError ) as e :
159- logging .warning ("Google OAuth validation error: %s" , e )
160-
161- # Check GitHub OAuth
162- if github .authorized :
163- try :
164- # Get user profile
165- resp = github .get ("/user" )
166- if resp .ok :
167- github_user = resp .json ()
168-
169- # Get user email (GitHub may require separate call for email)
170- email_resp = github .get ("/user/emails" )
171- email = None
172- if email_resp .ok :
173- emails = email_resp .json ()
174- # Find primary email
175- for email_obj in emails :
176- if email_obj .get ("primary" , False ):
177- email = email_obj .get ("email" )
178- break
179-
180- # If no primary email found, use the first one
181- if not email and emails :
182- email = emails [0 ].get ("email" )
183-
184- # Normalize user info structure
185- user_info = {
186- "id" : str (github_user .get ("id" )), # Convert to string for consistency
187- "name" : github_user .get ("name" ) or github_user .get ("login" ),
188- "email" : email ,
189- "picture" : github_user .get ("avatar_url" ),
190- "provider" : "github"
191- }
192- session ["user_info" ] = user_info
193- session ["token_validated_at" ] = current_time
194- return user_info , True
195- except (requests .RequestException , KeyError , ValueError ) as e :
196- logging .warning ("GitHub OAuth validation error: %s" , e )
197-
198- # If no valid authentication found, clear session
199- session .clear ()
200- return None , False
167+ try :
168+ # Check for cached user info from either provider
169+ user_info = session .get ("user_info" )
170+ token_validated_at = session .get ("token_validated_at" , 0 )
171+ current_time = time .time ()
172+
173+ # Use cached user info if it's less than 15 minutes old
174+ if user_info and (current_time - token_validated_at ) < 900 : # 15 minutes
175+ return user_info , True
176+
177+ # Check Google OAuth first
178+ if google .authorized :
179+ try :
180+ resp = google .get ("/oauth2/v2/userinfo" )
181+ if resp .ok :
182+ google_user = resp .json ()
183+ # Validate required fields
184+ if not google_user .get ("id" ) or not google_user .get ("email" ):
185+ logging .warning ("Invalid Google user data received" )
186+ session .clear ()
187+ return None , False
188+
189+ # Normalize user info structure
190+ user_info = {
191+ "id" : str (google_user .get ("id" )), # Ensure string type
192+ "name" : google_user .get ("name" , "" ),
193+ "email" : google_user .get ("email" ),
194+ "picture" : google_user .get ("picture" , "" ),
195+ "provider" : "google"
196+ }
197+ session ["user_info" ] = user_info
198+ session ["token_validated_at" ] = current_time
199+ return user_info , True
200+ except (requests .RequestException , KeyError , ValueError ) as e :
201+ logging .warning ("Google OAuth validation error: %s" , e )
202+ session .clear ()
203+
204+ # Check GitHub OAuth
205+ if github .authorized :
206+ try :
207+ # Get user profile
208+ resp = github .get ("/user" )
209+ if resp .ok :
210+ github_user = resp .json ()
211+
212+ # Validate required fields
213+ if not github_user .get ("id" ):
214+ logging .warning ("Invalid GitHub user data received" )
215+ session .clear ()
216+ return None , False
217+
218+ # Get user email (GitHub may require separate call for email)
219+ email_resp = github .get ("/user/emails" )
220+ email = None
221+ if email_resp .ok :
222+ emails = email_resp .json ()
223+ # Find primary email
224+ for email_obj in emails :
225+ if email_obj .get ("primary" , False ):
226+ email = email_obj .get ("email" )
227+ break
228+
229+ # If no primary email found, use the first one
230+ if not email and emails :
231+ email = emails [0 ].get ("email" )
232+
233+ if not email :
234+ logging .warning ("No email found for GitHub user" )
235+ session .clear ()
236+ return None , False
237+
238+ # Normalize user info structure
239+ user_info = {
240+ "id" : str (github_user .get ("id" )), # Convert to string for consistency
241+ "name" : github_user .get ("name" ) or github_user .get ("login" , "" ),
242+ "email" : email ,
243+ "picture" : github_user .get ("avatar_url" , "" ),
244+ "provider" : "github"
245+ }
246+ session ["user_info" ] = user_info
247+ session ["token_validated_at" ] = current_time
248+ return user_info , True
249+ except (requests .RequestException , KeyError , ValueError ) as e :
250+ logging .warning ("GitHub OAuth validation error: %s" , e )
251+ session .clear ()
252+
253+ # If no valid authentication found, clear session
254+ session .clear ()
255+ return None , False
256+
257+ except Exception as e :
258+ logging .error ("Unexpected error in validate_and_cache_user: %s" , e )
259+ session .clear ()
260+ return None , False
201261
202262
203263def token_required (f ):
204264 """Decorator to protect routes with token authentication"""
205265
206266 @wraps (f )
207267 def decorated_function (* args , ** kwargs ):
208- user_info , is_authenticated = validate_and_cache_user ()
268+ try :
269+ user_info , is_authenticated = validate_and_cache_user ()
209270
210- if not is_authenticated :
211- return jsonify (message = "Unauthorized - Please log in" ), 401
271+ if not is_authenticated :
272+ return jsonify (message = "Unauthorized - Please log in" ), 401
212273
213- g .user_id = user_info .get ("id" )
214- if not g .user_id :
215- session .clear ()
216- return jsonify (message = "Unauthorized - Invalid user" ), 401
274+ g .user_id = user_info .get ("id" )
275+ if not g .user_id :
276+ session .clear ()
277+ return jsonify (message = "Unauthorized - Invalid user" ), 401
217278
218- return f (* args , ** kwargs )
279+ return f (* args , ** kwargs )
280+ except Exception as e :
281+ logging .error ("Unexpected error in token_required: %s" , e )
282+ session .clear ()
283+ return jsonify (message = "Unauthorized - Authentication error" ), 401
219284
220285 return decorated_function
0 commit comments