@@ -407,14 +407,13 @@ def linkedin_callback(code: str = None, state: str = None, redirect_uri: str = N
407407
408408
409409# User settings endpoints
410+ # SECURITY: Only safe fields are accepted from frontend
410411class UserSettingsRequest (BaseModel ):
411412 user_id : str
412- linkedin_client_id : Optional [str ] = None
413- linkedin_client_secret : Optional [str ] = None
414- linkedin_user_urn : Optional [str ] = None
415- groq_api_key : Optional [str ] = None
416413 github_username : Optional [str ] = None
417- unsplash_access_key : Optional [str ] = None
414+ onboarding_complete : Optional [bool ] = None
415+ # NOTE: API keys are managed server-side via environment variables
416+ # Frontend NEVER sends credentials to this endpoint
418417
419418
420419class AuthRefreshRequest (BaseModel ):
@@ -454,40 +453,66 @@ def save_settings(settings: UserSettingsRequest):
454453
455454@app .get ("/api/settings/{user_id}" )
456455def get_settings (user_id : str ):
457- """Get user settings by user ID"""
456+ """
457+ Get user settings by user ID.
458+
459+ SECURITY: Returns ONLY safe, non-sensitive data.
460+ No credentials, tokens, or API keys are returned.
461+ """
458462 if not get_user_settings :
459463 return {"error" : "User settings service not available" }
460464 try :
461465 settings = get_user_settings (user_id )
462466 if settings :
463- # Return masked versions of secrets so frontend knows they're saved
464- # but doesn't expose the actual values
465- groq_key = settings .get ("groq_api_key" )
466- unsplash_key = settings .get ("unsplash_access_key" )
467- linkedin_id = settings .get ("linkedin_client_id" )
468- linkedin_secret = settings .get ("linkedin_client_secret" )
469-
467+ # SECURITY: Only return safe data, no credentials
470468 return {
471469 "user_id" : settings .get ("user_id" ),
472470 "github_username" : settings .get ("github_username" ) or "" ,
473- # Return masked versions for display
474- "groq_api_key" : f"{ groq_key [:8 ]} ...{ groq_key [- 4 :]} " if groq_key and len (groq_key ) > 12 else ("••••••••" if groq_key else "" ),
475- "unsplash_access_key" : f"{ unsplash_key [:8 ]} ...{ unsplash_key [- 4 :]} " if unsplash_key and len (unsplash_key ) > 12 else ("••••••••" if unsplash_key else "" ),
476- "linkedin_client_id" : linkedin_id or "" ,
477- "linkedin_client_secret" : "••••••••" if linkedin_secret else "" ,
478- # Also keep the boolean flags for compatibility
479- "has_linkedin" : bool (linkedin_id ),
480- "has_groq" : bool (groq_key ),
481- "has_unsplash" : bool (unsplash_key ),
471+ "onboarding_complete" : settings .get ("onboarding_complete" , False ),
482472 "subscription_tier" : settings .get ("subscription_tier" ) or "free" ,
483473 "subscription_status" : settings .get ("subscription_status" ) or "active" ,
484- "subscription_expires_at" : settings .get ("subscription_expires_at" ),
485474 }
486475 return {"error" : "User not found" }
487476 except Exception as e :
488477 return {"error" : str (e )}
489478
490479
480+ @app .get ("/api/connection-status/{user_id}" )
481+ def get_connection_status_endpoint (user_id : str ):
482+ """
483+ Get connection status for a user.
484+
485+ SECURITY: Returns ONLY boolean status and public identifiers.
486+ No tokens or credentials are ever returned.
487+ """
488+ try :
489+ # Import get_connection_status from token_store
490+ from services .token_store import get_connection_status
491+
492+ status = get_connection_status (user_id )
493+
494+ # Also get github_username from settings
495+ github_username = ''
496+ if get_user_settings :
497+ settings = get_user_settings (user_id )
498+ if settings :
499+ github_username = settings .get ('github_username' , '' )
500+
501+ return {
502+ "linkedin_connected" : status .get ("linkedin_connected" , False ),
503+ "linkedin_urn" : status .get ("linkedin_urn" , "" ),
504+ "github_connected" : bool (github_username ),
505+ "github_username" : github_username ,
506+ "token_expires_at" : status .get ("token_expires_at" ),
507+ }
508+ except Exception as e :
509+ return {
510+ "linkedin_connected" : False ,
511+ "github_connected" : False ,
512+ "error" : str (e )
513+ }
514+
515+
491516# GitHub activity endpoints
492517@app .get ("/api/github/activity/{username}" )
493518def github_activity (username : str , limit : int = 10 ):
0 commit comments