@@ -189,105 +189,79 @@ async def handle_github_callback(
189189
190190 current_time = datetime .now (timezone .utc )
191191
192- # Check if user already exists by email (for account linking)
193- existing_user_by_email = None
194- if email :
195- existing_user_by_email = await self .users_collection .find_one ({
196- "email" : email ,
197- "googleId" : {"$exists" : True },
198- "githubId" : {"$exists" : False }
199- })
200-
201- if existing_user_by_email :
202- # User exists via Google, potential account linking scenario
203- logger .info (f"Found existing Google user with matching email: { email } " )
204-
205- # Store pending linking data in a temporary JWT token
206- import base64
207- import json
192+ # No automatic account linking by email - users must manually link via settings
193+ # Normal GitHub user creation/update
194+ try :
195+ # Ensure username uniqueness by checking and appending numbers if needed
196+ # This prevents conflicts when a Google user already has this username
197+ base_username = username
198+ counter = 1
199+ while await self .users_collection .find_one ({"username" : username , "githubId" : {"$ne" : github_user_id }}):
200+ username = f"{ base_username } { counter } "
201+ counter += 1
208202
209- pending_data = {
210- "existing_user_id " : str ( existing_user_by_email [ "_id" ]) ,
211- "existing_username " : existing_user_by_email . get ( "username" ),
212- "existing_avatar " : existing_user_by_email . get ( "googleAvatarUrl" ) ,
203+ set_payload = {
204+ "name " : name ,
205+ "githubAvatarUrl " : avatar_url , # Store GitHub avatar separately
206+ "email " : email ,
213207 "githubId" : github_user_id ,
214- "github_username" : username ,
215- "github_name" : name ,
216- "github_email" : email ,
217- "github_avatar" : avatar_url ,
218- "github_token" : github_token ,
219- "exp" : (datetime .now (timezone .utc ) + timedelta (minutes = 10 )).timestamp ()
208+ "githubUsername" : username , # Store provider-specific username
209+ "githubAccessToken" : github_token , # Store the token for API calls
210+ "updatedAt" : current_time ,
211+ "lastLoginAt" : current_time ,
220212 }
221-
222- pending_token = jwt .encode (pending_data , config_settings .FLASK_SECRET_KEY , algorithm = config_settings .ALGORITHM )
223-
224- # Redirect to frontend with pending_link query parameter
225- frontend_url = f"{ frontend_url } ?pending_link={ pending_token } "
226-
227- redirect_response = RedirectResponse (url = frontend_url )
228- return redirect_response
229- else :
230- # Normal GitHub user creation/update
231- try :
232- set_payload = {
233- "name" : name ,
234- "githubAvatarUrl" : avatar_url , # Store GitHub avatar separately
235- "email" : email ,
236- "githubId" : github_user_id ,
237- "githubAccessToken" : github_token , # Store the token for API calls
238- "updatedAt" : current_time ,
239- "lastLoginAt" : current_time ,
240- }
241213
242- set_on_insert_payload = {
243- "username" : username ,
244- "createdAt" : current_time ,
245- "isAdmin" : False ,
246- # Set default privacy settings for new users
247- "showEmail" : True ,
248- "showGithub" : True ,
249- "preferredAvatarSource" : "github" , # Default to GitHub avatar
250- }
214+ set_on_insert_payload = {
215+ "username" : username ,
216+ "createdAt" : current_time ,
217+ "isAdmin" : False ,
218+ # Set default privacy settings for new users
219+ "showEmail" : True ,
220+ "showGithub" : True ,
221+ "preferredAvatarSource" : "github" , # Default to GitHub avatar
222+ }
251223
252- user_document = await self .users_collection .find_one_and_update (
253- {"username" : username },
254- {
255- "$set" : set_payload ,
256- "$setOnInsert" : set_on_insert_payload
257- },
258- upsert = True ,
259- return_document = ReturnDocument .AFTER
224+ # Match by githubId ONLY to prevent automatic account merging
225+ user_document = await self .users_collection .find_one_and_update (
226+ {"githubId" : github_user_id },
227+ {
228+ "$set" : set_payload ,
229+ "$setOnInsert" : set_on_insert_payload
230+ },
231+ upsert = True ,
232+ return_document = ReturnDocument .AFTER
233+ )
234+
235+ # Compute primary avatar_url based on preference
236+ preferred_source = user_document .get ("preferredAvatarSource" , "github" )
237+ if preferred_source == "google" and user_document .get ("googleAvatarUrl" ):
238+ computed_avatar = user_document .get ("googleAvatarUrl" )
239+ else :
240+ computed_avatar = user_document .get ("githubAvatarUrl" )
241+
242+ # Update with computed avatar_url
243+ if computed_avatar :
244+ await self .users_collection .update_one (
245+ {"_id" : user_document ["_id" ]},
246+ {"$set" : {"avatarUrl" : computed_avatar }}
260247 )
261-
262- # Compute primary avatar_url based on preference
263- preferred_source = user_document .get ("preferredAvatarSource" , "github" )
264- if preferred_source == "google" and user_document .get ("googleAvatarUrl" ):
265- computed_avatar = user_document .get ("googleAvatarUrl" )
266- else :
267- computed_avatar = user_document .get ("githubAvatarUrl" )
268-
269- # Update with computed avatar_url
270- if computed_avatar :
271- await self .users_collection .update_one (
272- {"_id" : user_document ["_id" ]},
273- {"$set" : {"avatarUrl" : computed_avatar }}
274- )
275- user_document ["avatarUrl" ] = computed_avatar
276-
277- if not user_document :
278- logger .error ("GitHubOAuthService: Failed to upsert user document, find_one_and_update returned None unexpectedly." )
279- return RedirectResponse (url = f"{ frontend_url } /?login_error=database_user_op_failed" , status_code = 307 )
280-
281- logger .info (f"GitHubOAuthService: User { username } (DB ID: { user_document ['_id' ]} , GitHub ID: { user_document .get ('github_id' )} ) upserted successfully." )
248+ user_document ["avatarUrl" ] = computed_avatar
249+
250+ if not user_document :
251+ logger .error ("GitHubOAuthService: Failed to upsert user document, find_one_and_update returned None unexpectedly." )
252+ return RedirectResponse (url = f"{ frontend_url } /?login_error=database_user_op_failed" , status_code = 307 )
253+
254+ logger .info (f"GitHubOAuthService: User { user_document .get ('username' )} (DB ID: { user_document ['_id' ]} , GitHub ID: { user_document .get ('githubId' )} ) upserted successfully." )
282255
283- except Exception as db_exc :
284- logger .error (f"Database operation error during user upsert: { db_exc } " )
285- return RedirectResponse (url = f"{ frontend_url } /?login_error=database_user_op_generic_error" , status_code = 307 )
256+ except Exception as db_exc :
257+ logger .error (f"Database operation error during user upsert: { db_exc } " )
258+ return RedirectResponse (url = f"{ frontend_url } /?login_error=database_user_op_generic_error" , status_code = 307 )
286259
287260 user_id_str = str (user_document ["_id" ])
261+ username_from_db = user_document ["username" ]
288262 access_token_payload = {
289263 "sub" : user_id_str ,
290- "username" : username ,
264+ "username" : username_from_db ,
291265 "githubId" : github_user_id ,
292266 }
293267 access_token = create_access_token (data = access_token_payload )
@@ -324,5 +298,5 @@ async def handle_github_callback(
324298 path = "/" ,
325299 secure = True if config_settings .ENV_TYPE == "production" else False
326300 )
327- logger .info (f"Successfully authenticated user { username } . Redirecting to frontend. Cookies being set: Access, Refresh, CSRF." )
301+ logger .info (f"Successfully authenticated user { username_from_db } . Redirecting to frontend. Cookies being set: Access, Refresh, CSRF." )
328302 return redirect_response
0 commit comments